pub struct DefaultHandler {
pub sink: Box<dyn IoSink>,
pub read_root: Option<PathBuf>,
pub budget_remaining: Arc<AtomicU64>,
pub budget_ceiling: Option<u64>,
pub program: Option<Arc<Program>>,
pub chat_registry: Option<Arc<ChatRegistry>>,
pub mcp_clients: McpClientCache,
pub streams: Arc<Mutex<StreamRegistry>>,
pub next_stream_id: Arc<AtomicU64>,
pub requested_exit: Arc<AtomicI64>,
pub program_args: Vec<String>,
pub approval_sink: Box<dyn ApprovalSink>,
/* private fields */
}Fields§
§sink: Box<dyn IoSink>§read_root: Option<PathBuf>Optional read root for io.read — when set, io.read("p") resolves
to read_root.join(p). Lets tests run without touching the real fs.
budget_remaining: Arc<AtomicU64>Per-run budget pool (#225). Arc<AtomicU64> so parallel
branches share one counter without locking. Initialized to
the policy ceiling at handler construction; each call to a
function with declared [budget(N)] deducts N atomically
via note_call_budget. Cloning the handler is intentional
for net.serve / chat handlers — they share the same pool.
budget_ceiling: Option<u64>The original ceiling that budget_remaining started at, kept
for diagnostics so a BudgetExceeded error can report
(used, ceiling) rather than just “exceeded by N”.
program: Option<Arc<Program>>Shared reference to the program, needed by net.serve so the
handler can spin up fresh VMs to dispatch incoming requests.
None if the handler was constructed without a program.
chat_registry: Option<Arc<ChatRegistry>>Chat registry; populated by net.serve_ws’s per-message
dispatch so chat.broadcast / chat.send work from inside
a handler invocation.
mcp_clients: McpClientCacheLRU cache of agent.call_mcp clients keyed by the
command-line string (#197). Avoids spawn-per-call cost
when an agent invokes the same MCP server in tight loops.
Capped — when the cache is full, the least-recently-used
entry is dropped (its subprocess is reaped on Drop).
streams: Arc<Mutex<StreamRegistry>>Stream registry for agent.cloud_stream / stream.next /
stream.collect (#305 slice 3). Keyed by an opaque handle
id; values are the producer iterators. Wrapped in
Arc<Mutex<…>> so par_map workers can share the same
stream pool (when slice-2’s per-worker handler split chains
the registry through).
next_stream_id: Arc<AtomicU64>Monotonic counter for handing out fresh stream handle ids.
requested_exit: Arc<AtomicI64>The status std.process.exit asked to terminate with (#754).
Arc-shared for the same reason budget_remaining is: a
par_map worker gets a cloned handler, and an exit called from
a worker has to reach the VM that will actually return. Held
per-handler instead, an exit inside parallel work would be
dropped on the floor — the failure mode being that a program
signalling failure silently exits 0.
NO_EXIT means “not requested”. The first writer wins, so a
racing second exit cannot overwrite the status the program
already stopped with.
program_args: Vec<String>Arguments passed after -- in lex run <file> -- [args...].
Returned by io.argv() so Lex main functions can read CLI flags.
approval_sink: Box<dyn ApprovalSink>Host boundary for approval.request. Defaults to
NullApprovalSink (always refuses) so a handler must opt in
via with_approval_sink before [approval] calls can succeed.
Implementations§
Source§impl DefaultHandler
impl DefaultHandler
pub fn new(policy: Policy) -> Self
pub fn with_approval_sink(self, sink: Box<dyn ApprovalSink>) -> Self
Sourcepub fn active_arena(&self) -> Option<&Arena>
pub fn active_arena(&self) -> Option<&Arena>
Read-only access to the currently-active request arena, if
any. None outside a request scope. The follow-on slice
that routes Value allocations consults this from the VM
path; today it has no callers in tree but is exercised in
tests.
Sourcepub fn arena_stack_depth(&self) -> usize
pub fn arena_stack_depth(&self) -> usize
Test-only: depth of the arena stack. Lets tests confirm the
net.serve_fn request loop pushes/pops symmetrically.
pub fn with_program(self, program: Arc<Program>) -> Self
pub fn with_chat_registry(self, registry: Arc<ChatRegistry>) -> Self
pub fn with_sink(self, sink: Box<dyn IoSink>) -> Self
pub fn with_read_root(self, root: PathBuf) -> Self
pub fn with_program_args(self, args: Vec<String>) -> Self
Trait Implementations§
Source§impl EffectHandler for DefaultHandler
impl EffectHandler for DefaultHandler
Source§fn enter_request_scope(&mut self) -> u64
fn enter_request_scope(&mut self) -> u64
Push a fresh per-request arena onto the stack (#463
scaffolding). Returns the scope id; pair with
exit_request_scope(id) to drop it.
Source§fn exit_request_scope(&mut self, scope_id: u64)
fn exit_request_scope(&mut self, scope_id: u64)
Drop the arena associated with scope_id. Mismatched pairs
(exit called with a scope id we don’t recognize, or out-of-
order exit) are tolerated as no-ops rather than panicking —
runtime layer should pair them strictly but a stray exit
shouldn’t crash a live server.
Source§fn note_call_budget(&mut self, cost: u64) -> Result<(), String>
fn note_call_budget(&mut self, cost: u64) -> Result<(), String>
Per-call budget enforcement (#225). VM calls this before
invoking any function whose signature declares [budget(N)].
The cost N is deducted atomically from the shared pool;
returning Err aborts the call before any frame is pushed.
Source§fn take_exit(&mut self) -> Option<i32>
fn take_exit(&mut self) -> Option<i32>
list.par_map worker-handler factory (#305 slice 2).
Builds a fresh DefaultHandler per worker that shares the
budget pool with the parent (Arc<AtomicU64>) so a parallel
batch can’t escape the run-wide budget ceiling. Other state
is intentionally split per-worker:
sink: aStdoutSinkper worker. Tests that capture output via aSharedSinkwrapped inArc<Mutex<…>>see each worker as a fresh handler. Print interleaving on stdout is acceptable; tests that need ordered capture run workloads serially anyway.mcp_clients: a fresh per-worker LRU cache. The parent’s subprocess handles can’t be shared across threads without mutex-serialising every MCP call, which would defeat the parallelism. Cache hit rate is sub-optimal across the first call per worker; warmed caches still amortise within a worker.chat_registry: clonedArc<ChatRegistry>so all workers route into the same chat dispatch layer.program: clonedArc<Program>sonet.serve(if a worker invokes it) sees the same compiled program.
fn dispatch( &mut self, kind: &str, op: &str, args: Vec<Value>, ) -> Result<Value, String>
Source§fn spawn_for_worker(&self) -> Option<Box<dyn EffectHandler + Send>>
fn spawn_for_worker(&self) -> Option<Box<dyn EffectHandler + Send>>
list.par_map worker-handler factory (#305 slice 2). Read moreAuto Trait Implementations§
impl !RefUnwindSafe for DefaultHandler
impl !Sync for DefaultHandler
impl !UnwindSafe for DefaultHandler
impl Freeze for DefaultHandler
impl Send for DefaultHandler
impl Unpin for DefaultHandler
impl UnsafeUnpin for DefaultHandler
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more