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
Returns a specification to start this module under a supervisor.
I evaluate code in a fresh process with no per-page bindings.
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.
Remove an object from the registry. Called by GT when a proxy object is garbage collected.
Types
@type t() :: %GtBridge.Eval{ bindings: Code.binding() | nil, env: Macro.Env.t() | nil, port: non_neg_integer(), registered_ids: MapSet.t(non_neg_integer()) }
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec complete(GenServer.server(), String.t(), String.t() | nil) :: [String.t()]
@spec eval(GenServer.server(), String.t(), String.t() | nil) :: any()
@spec eval_stateless(String.t(), String.t() | nil, pos_integer() | nil) :: :ok
I evaluate code in a fresh process with no per-page bindings.
GT-side evaluateAndWait calls that don't pass a sessionId (view-
block fetches, proxy-GC finalizers, browser fan-out queries) used
to land on a single shared "default" Eval GenServer, which
serialized them through one mailbox and cascaded any slow call
into a Cowboy worker timeout storm.
Per-page snippet evals still use the session-bound eval/3 path so
bindings persist across snippets on the same page. This stateless
path is for everything else — parallelism is bounded only by the
BEAM scheduler, not by a shared GenServer.
The eval string is expected to call GtBridge.Eval.notify/3 itself
(same protocol as the session path) to deliver its result back to
GT via /EVAL. Registered objects in ObjectRegistry live until GT
GCs the corresponding proxy and fires Eval.remove/1.
@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()
@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.
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})
@spec notify(term(), String.t(), pos_integer()) :: term()
@spec remove(non_neg_integer()) :: :ok
Remove an object from the registry. Called by GT when a proxy object is garbage collected.