GtBridge.Eval (gt_bridge v0.19.4)

Copy Markdown View Source

I am a per-session evaluation GenServer.

Each instance corresponds to a GT view's evaluation context (LeSharedSnippetContext). All snippets within the same view share one Eval process (same bindings).

I track object IDs registered in GtBridge.ObjectRegistry during my lifetime. When I terminate (session closed), I batch-remove all tracked objects from the registry.

Cleanup

GT's BeamSessionFinalizer sends POST /SESSION_CLOSE when the per-view GtSharedVariablesBindings is GC'd (page/inspector closed). The router calls EvalRegistry.remove/1 which terminates me, and terminate/2 batch-removes all tracked objects from the registry.

Summary

Functions

I stop the eval running under command_id and answer its caller a GtBridge.Eval.Error of kind :cancelled.

Returns a specification to start this module under a supervisor.

I encode an evaluation result for the wire.

I evaluate in the calling process and return the value, so the answer can travel back in the HTTP response rather than as a second message.

I drain and return all messages received by the eval process.

I return the current bindings as a map of name→serialized value. Internal bindings (:port, :command_id, :pid) are filtered out. Non-primitive values are registered in ObjectRegistry.

I return documentation for a module, function, or type.

I register each element of list and return what GT dresses as a proxy, leaving primitives inline.

Remove an object from the registry. Called by GT when a proxy object is garbage collected.

Types

t()

@type t() :: %GtBridge.Eval{
  bindings: Code.binding() | nil,
  env: Macro.Env.t() | nil,
  locals: [{atom(), arity()}],
  port: non_neg_integer(),
  registered_ids: MapSet.t(non_neg_integer()),
  running: %{
    optional(reference()) => {String.t() | nil, Task.t(), GenServer.from()}
  }
}

Functions

cancel(pid, command_id)

@spec cancel(GenServer.server(), String.t() | nil) :: :ok

I stop the eval running under command_id and answer its caller a GtBridge.Eval.Error of kind :cancelled.

I am how a stopped eval on the GT side stops costing anything here. Nothing else can interrupt user code: it runs in a task I own, and killing that task is the only way to take the session back.

I answer :ok whether or not there was anything to kill, so a cancel that races the eval's own completion is not an error.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

complete(pid, code_prefix, source \\ nil)

@spec complete(GenServer.server(), String.t(), String.t() | nil) :: [String.t()]

encode_result(obj)

@spec encode_result(term()) :: String.t()

I encode an evaluation result for the wire.

I register complex values in GtBridge.ObjectRegistry and send an %{exid, exclass} reference, so GT receives a proxy it can inspect lazily rather than a fully materialised copy. Primitives travel by value.

eval(pid, code, command_id)

@spec eval(GenServer.server(), String.t(), String.t() | nil) :: any()

eval_stateless_sync(code, command_id, port)

@spec eval_stateless_sync(String.t(), String.t() | nil, pos_integer() | nil) :: term()

I evaluate in the calling process and return the value, so the answer can travel back in the HTTP response rather than as a second message.

A request already runs in its own process, so running inline gives the same isolation a spawned Task would.

flush()

@spec flush() :: [term()]

I drain and return all messages received by the eval process.

Like IEx's flush/0. Useful when user code subscribes the eval process to event brokers and you want to see what arrived.

flush()

get_bindings(pid)

@spec get_bindings(GenServer.server()) :: map()

I return the current bindings as a map of name→serialized value. Internal bindings (:port, :command_id, :pid) are filtered out. Non-primitive values are registered in ObjectRegistry.

h(other)

(macro)

I return documentation for a module, function, or type.

Bound as h in every eval session. Because I am a macro, I can parse dot-syntax like h(Enum.map) and h(Enum.map/2).

h(Enum)
h(Enum.map)
h(Enum.map/2)
h({Enum, :map})
h({Enum, :map, 2})

references(list)

@spec references(list()) :: list()

I register each element of list and return what GT dresses as a proxy, leaving primitives inline.

GtBridge.Serializer.to_json inlines a struct as a plain map, so a list fetched through it arrives as data with no remote identity. I give each element the same %{exclass, exid} reference an eval result gets, in one call rather than one per element.

[1, URI.parse("http://a.b")] |> references()

remove(id)

@spec remove(non_neg_integer()) :: :ok

Remove an object from the registry. Called by GT when a proxy object is garbage collected.

start_link(init_args)