GtBridge.Eval (gt_bridge v0.17.2)

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

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

t()

@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

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()]

eval(pid, code, command_id)

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

eval_stateless(code, command_id, port)

@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.

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})

notify(obj, id, port)

@spec notify(term(), String.t(), pos_integer()) :: term()

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)