EvalTask
One eval scheduled on a lane. Compilation runs OFF the tick (pure computation) on the compiler thread (ReplBridge.COMPILER); once compiled the task enters the lane and is EXECUTED ON THE TICK THREAD by the pump — so the snippet runs with the real server/client/render thread identity (ensureOnSameThread passes), not a surrogate thread.
Cross-tick work is native Kotlin: a snippet whose value is an iterator { ... yield() ... } hands back an IterEval, and the pump advances that iterator one element per tick on the tick thread, so yield() suspends the coroutine and resumes it on that same thread. println and whatever yield renders both accumulate in out, which is returned when the iterator is exhausted.
The result is an Outcome (text + isError), so every way an eval can end — clean finish, runtime throw, compile failure, cancellation, reap — reports the right tools/call isError AND keeps whatever was printed before it ended. Partial output is never dropped.
Ending early is not cleaning up: a cross-tick body stopped mid-series still has its finally blocks ahead of it (needsUnwind). The driver runs them through unwind — a tick lane after its live evals, a parallel worker straight away.
Constructors
Properties
Which lane's guard this eval's bytecode is woven against (baked in at compile), or null for no guard at all — the off-tick ParallelLane, which has no watchdog.
This eval's token, baked into its woven guard so only THIS eval's code answers to a kill. 1-based, which is what leaves 0 free as TimeoutGuard's "nobody".
Ended early with an McpScope body still suspended at a yield, so its finally blocks have not run (other builders: IterEval.scope). Driver's thread only — the body's state is the driver's, unsynchronized.
Hands the compiled task to its driver. Runs on the compiler thread, so it must not block: every compile still queued waits behind it.
Functions
cancel for a caller that will never read the outcome — a client that hung up — sealing the output unread (Capture.dispose): left open, a writer that outlived the eval grows it for good. Seals even when the eval had already finished, since nobody reads that outcome either.
The step threw and no inner guard owned it — in practice EvalRender.stack itself failing, since every script throw is answered before this. Completing the future is the whole job; the alternative is a caller blocked on it forever. So NOTHING here may throw: the render is deferred onto that caller, which unlike a tick thread has somewhere to put a second failure. Here rather than in the driver because out is private, and no ending may drop it.
Normal end, called by the driver once pumpStep reports done. A no-op after a kill, cancel, fail or reportTimeout: those complete the future directly and leave finalResult null.
The scriptguard threw into the step that just ran and nothing reported it, so something swallowed it: the run carried on past an interruption and whatever it produced means nothing. End the eval with the watchdog's report, keeping what it had printed. Not always a swallow, though — a blocking call (Thread.sleep, a lock, IO) gives the guard no point to fire at, so the step can return intact; that value is dropped all the same, on purpose — handing it back is the one answer that leaves the caller no way to learn the tick stood frozen for the whole step.
Run a needsUnwind body's finally blocks, once, on the driver's thread: the body runs on whatever thread resumes it, so never from cancel, whose caller is the HTTP thread. Skipped once authorization is gone — a revoke ends the snippet's code, cleanup included. A cleanup that throws is only logged (class name, as the pump logs): the caller already has its answer.
Captured output so far + a terminal tail marker, so a reap or cancel returns the same shape the normal-finish and mid-drive-throw paths do. Exactly one of those paths ever runs: the loser's deferred text supplier is never invoked, because completing an already-done future is a no-op.