Skip to content

Commit

Permalink
πŸš‚ Notebook in separate process
Browse files Browse the repository at this point in the history
Stop button is almost there!
  • Loading branch information
fonsp committed Mar 31, 2020
1 parent fb61c9b commit 1d9e7e0
Show file tree
Hide file tree
Showing 11 changed files with 433 additions and 218 deletions.
3 changes: 2 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ name = "Pluto"
uuid = "c3e4b0f8-55cb-11ea-2926-15256bba5781"
license = "MIT"
authors = ["Fons van der Plas <[email protected]>", "MikoΕ‚aj Bochenski <[email protected]>"]
version = "0.4.3"
version = "0.5.0"

[deps]
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
Expand Down
2 changes: 1 addition & 1 deletion src/Pluto.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const VERSION_STR = 'v' * Pkg.TOML.parsefile(joinpath(PKG_ROOT_DIR, "Project.tom
\n"""

include("./react/ExploreExpression.jl")
include("./webserver/FormatOutput.jl")
using .ExploreExpression
include("./react/Cell.jl")
include("./react/Notebook.jl")
Expand All @@ -22,7 +23,6 @@ include("./react/React.jl")

include("./webserver/NotebookServer.jl")
include("./webserver/Static.jl")
include("./webserver/FormatOutput.jl")
include("./webserver/Dynamic.jl")

end
29 changes: 6 additions & 23 deletions src/react/Cell.jl
Original file line number Diff line number Diff line change
@@ -1,43 +1,26 @@
using UUIDs


"The building block of `Notebook`s. Contains both code and output."
mutable struct Cell
"because Cells can be reordered, they get a UUID. The JavaScript frontend indexes cells using the UUID."
uuid::UUID
code::String
parsedcode::Any
output::Any
output_repr::Union{String, Nothing}
error_repr::Union{String, Nothing}
repr_mime::MIME
runtime::Union{Missing,UInt64}
errormessage::Any
symstate::SymbolsState
resolved_funccalls::Set{Symbol}
resolved_symstate::SymbolsState
module_usings::Set{Expr}
end

Cell(uuid, code) = Cell(uuid, code, nothing, nothing, missing, nothing, SymbolsState(), Set{Symbol}(), SymbolsState(), Set{Expr}())
Cell(uuid, code) = Cell(uuid, code, nothing, nothing, nothing, MIME("text/plain"), missing, SymbolsState(), Set{Symbol}(), SymbolsState(), Set{Expr}())

"Turn a `Cell` into an object that can be serialized using `JSON.json`, to be sent to the client."
function serialize(cell::Cell)
Dict(:uuid => string(cell.uuid), :code => cell.code)# , :output => cell.output)
end

createcell_fromcode(code::String) = Cell(uuid1(), code)

function relay_output!(cell::Cell, output::Any)
cell.output = output
cell.errormessage = nothing
end

function relay_error!(cell::Cell, message::String)
cell.output = nothing
cell.errormessage = message
Dict(:uuid => string(cell.uuid), :code => cell.code)
end

relay_error!(cell::Cell, err::Exception) = relay_error!(cell, sprint(showerror, err))
function relay_error!(cell::Cell, err::Exception, backtrace::Array{Base.StackTraces.StackFrame,1})
until = findfirst(sf -> sf.func == :run_single!, backtrace)
backtrace_trimmed = until === nothing ? backtrace : backtrace[1:until-1]
relay_error!(cell, sprint(showerror, err, backtrace_trimmed))
end
createcell_fromcode(code::String) = Cell(uuid1(), code)
10 changes: 8 additions & 2 deletions src/react/Notebook.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@ mutable struct Notebook

# buffer must contain all undisplayed outputs
pendingupdates::Channel

executetoken::Channel
end
# We can keep 128 updates pending. After this, any put! calls (i.e. calls that push an update to the notebook) will simply block, which is fine.
# This does mean that the Notebook can't be used if nothing is clearing the update channel.
Notebook(path::String, cells::Array{Cell,1}, uuid) = Notebook(path, cells, uuid, nothing, Channel(128))
Notebook(path::String, cells::Array{Cell,1}) = Notebook(path, cells, uuid4(), nothing, Channel(128))
Notebook(path::String, cells::Array{Cell,1}, uuid) = let
et = Channel{Nothing}(1)
put!(et, nothing)
Notebook(path, cells, uuid, nothing, Channel(128), et)
end
Notebook(path::String, cells::Array{Cell,1}) = Notebook(path, cells, uuid1())

function selectcell_byuuid(notebook::Notebook, uuid::UUID)::Union{Cell,Nothing}
cellIndex = findfirst(c->c.uuid == uuid, notebook.cells)
Expand Down
108 changes: 75 additions & 33 deletions src/react/React.jl
Original file line number Diff line number Diff line change
@@ -1,30 +1,57 @@
import Base: showerror

abstract type ReactivityError <: Exception end

struct CircularReferenceError <: ReactivityError
syms::Set{Symbol}
end

struct MultipleDefinitionsError <: ReactivityError
syms::Set{Symbol}
end

function showerror(io::IO, cre::CircularReferenceError)
print(io, "Circular references among $(join(cre.syms, ", ", " and ")).")
end

function showerror(io::IO, mde::MultipleDefinitionsError)
print(io, "Multiple definitions for $(join(mde.syms, ", ", " and ")).\nCombine all definitions into a single reactive cell using a `begin` ... `end` block.") # TODO: hint about mutable globals
end


"Sends `error` to the frontend without backtrace. Runtime errors are handled by `WorkspaceManager.eval_fetch_in_workspace` - this function is for Reactivity errors."
function relay_reactivity_error!(cell::Cell, error::Exception)
cell.output_repr = nothing
cell.error_repr, cell.repr_mime = format_output(error)
end


function run_single!(initiator, notebook::Notebook, cell::Cell)
workspace = WorkspaceManager.get_workspace(notebook)
starttime = time_ns()
try
# deleted_refs = setdiff(cell.resolved_symstate.references, cell.resolved_symstate.assignments) ∩ workspace.deleted_vars
deleted_refs = cell.resolved_symstate.references ∩ workspace.deleted_vars
if !isempty(deleted_refs)
deleted_refs |> first |> UndefVarError |> throw
end
starttime = time_ns()
output = Core.eval(workspace.workspace_module, cell.parsedcode)
cell.runtime = time_ns() - starttime

relay_output!(cell, output)
output, errored = WorkspaceManager.eval_fetch_in_workspace(notebook, cell.parsedcode)
cell.runtime = time_ns() - starttime

if errored
cell.output_repr = nothing
cell.error_repr = output[1]
cell.repr_mime = output[2]
else
cell.output_repr = output[1]
cell.error_repr = nothing
cell.repr_mime = output[2]
WorkspaceManager.undelete_vars(notebook, cell.resolved_symstate.assignments)
# TODO: capture stdout and display it somehwere, but let's keep using the actual terminal for now
catch err
cell.runtime = time_ns() - starttime
bt = stacktrace(catch_backtrace())
relay_error!(cell, err, bt)
end
# TODO: capture stdout and display it somehwere, but let's keep using the actual terminal for now

end

"Run a cell and all the cells that depend on it"
function run_reactive!(initiator, notebook::Notebook, cell::Cell)
# This guarantees that we are the only run_reactive! that is running cells right now:
token = take!(notebook.executetoken)

workspace = WorkspaceManager.get_workspace(notebook)

cell.parsedcode = Meta.parse(cell.code, raise=false)
cell.module_usings = ExploreExpression.compute_usings(cell.parsedcode)

Expand Down Expand Up @@ -96,30 +123,45 @@ function run_reactive!(initiator, notebook::Notebook, cell::Cell)
(keys(c.resolved_symstate.funcdefs) for c in will_update)...
)

WorkspaceManager.delete_vars(notebook, to_delete_vars)
WorkspaceManager.delete_funcs(notebook, to_delete_funcs)
WorkspaceManager.delete_vars(workspace, to_delete_vars)
WorkspaceManager.delete_funcs(workspace, to_delete_funcs)

for to_run in will_update
if to_run in reassigned
assigned_multiple = let
other_modifiers = setdiff(competing_modifiers, [to_run])
union((to_run.resolved_symstate.assignments ∩ c.resolved_symstate.assignments for c in other_modifiers)...)
end
relay_error!(to_run, "Multiple definitions for $(join(assigned_multiple, ", ", " and "))")
elseif to_run in cyclic
assigned_cyclic = let
referenced_during_cycle = union((c.resolved_symstate.references for c in cyclic)...)
assigned_during_cycle = union((c.resolved_symstate.assignments for c in cyclic)...)

referenced_during_cycle ∩ assigned_during_cycle
end
relay_error!(to_run, "Cyclic references: $(join(assigned_cyclic, ", ", " and "))")
assigned_multiple = if to_run in reassigned
other_modifiers = setdiff(competing_modifiers, [to_run])
union((to_run.resolved_symstate.assignments ∩ c.resolved_symstate.assignments for c in other_modifiers)...)
else
[]
end

assigned_cyclic = if to_run in cyclic
referenced_during_cycle = union((c.resolved_symstate.references for c in cyclic)...)
assigned_during_cycle = union((c.resolved_symstate.assignments for c in cyclic)...)

referenced_during_cycle ∩ assigned_during_cycle
else
[]
end

deleted_refs = let
to_run.resolved_symstate.references ∩ workspace.deleted_vars
end

if length(assigned_multiple) > 0
relay_reactivity_error!(to_run, assigned_multiple |> MultipleDefinitionsError)
elseif length(assigned_cyclic) > 1
relay_reactivity_error!(to_run, assigned_cyclic |> CircularReferenceError)
elseif length(deleted_refs) > 0
relay_reactivity_error!(to_run, deleted_refs |> first |> UndefVarError)
else
run_single!(initiator, notebook, to_run)
end

putnotebookupdates!(notebook, clientupdate_cell_output(initiator, notebook, to_run))
end

put!(notebook.executetoken, token)

return will_update
end

Expand Down
Loading

4 comments on commit 1d9e7e0

@fonsp
Copy link
Owner Author

@fonsp fonsp commented on 1d9e7e0 Mar 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#38

@fonsp
Copy link
Owner Author

@fonsp fonsp commented on 1d9e7e0 Mar 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#34

@fonsp
Copy link
Owner Author

@fonsp fonsp commented on 1d9e7e0 Mar 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/11850

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.5.0 -m "<description of version>" 1d9e7e0f867d6cb6009a8cb7dd3c5ace23f59313
git push origin v0.5.0

Please sign in to comment.