EvalTask

internal class EvalTask(val code: String, val onCompiled: (EvalTask) -> Unit, val epoch: Long, val guardLane: GuardLane?)

One eval scheduled on a lane. Compilation runs OFF the tick (pure computation) on a short-lived thread; 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.

Constructors

Link copied to clipboard
constructor(code: String, onCompiled: (EvalTask) -> Unit, epoch: Long, guardLane: GuardLane?)

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard
private val code: String
Link copied to clipboard
private var dead: Boolean
Link copied to clipboard
val epoch: Long

The lane epoch this task was submitted against; if its heartbeat stopped for good while it was still compiling, it's reaped unrun.

Link copied to clipboard

Whether this step already ended in a reported error — on a tripped frame, that the scriptguard's throw survived and the normal completion path owns the report.

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
private val guardLane: GuardLane?

Which timeout kill-id field this lane's scripts read (baked in at compile), or null for no guard at all — the off-tick ParallelLane, which has no watchdog.

Link copied to clipboard
private var handle: Any?
Link copied to clipboard
val id: Int

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

Link copied to clipboard
private var iterEval: IterEval?
Link copied to clipboard
private val onCompiled: (EvalTask) -> Unit
Link copied to clipboard
private val out: Capture

This eval's println sink, taken once at the end. Owned here, not inside the REPL host, so that BOTH shapes — single-tick and cross-tick — can still report partial output on a kill, cancel or timeout.

Link copied to clipboard
private var started: Boolean
Link copied to clipboard
private var worker: Thread?

The dedicated worker thread driving this eval off-tick (ParallelLane); null for the heartbeat lanes, whose evals run on the (never-interruptible) tick thread. Set once, before the worker starts.

Functions

Link copied to clipboard

Publish the dedicated off-tick worker before it starts, so a cancel racing the first step has a thread to interrupt. Only ParallelLane calls this; the heartbeat lanes leave worker null.

Link copied to clipboard
fun cancel()

Client-initiated cancellation (notifications/cancelled). Completes the future now, not next tick, with whatever the eval had printed, tagged (cancelled) and marked isError. Idempotent; a no-op if the eval already finished.

Link copied to clipboard
fun fail(t: Throwable)

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.

Link copied to clipboard
fun finish()

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.

Link copied to clipboard
fun kill(reason: String)

Reap: this eval's lane will never step it again. Stop driving and complete with a "killed" result — isError rather than silence, which would leak the blocked request — keeping whatever it had printed. Stops the eval by the same two mechanisms as cancel.

Link copied to clipboard
fun nudge()

Re-interrupt a worker that outlived its kill. kill / cancel bail on an already-completed future, so their single interrupt() is all an eval ever gets — and a step that swallowed it, or wasn't blocked yet, needs another. Only pokes an already-dead task; a live one is still authorized.

Link copied to clipboard

Advance one step, ON the tick thread (the pump calls this). The first call executes the snippet; if it returned an iterator, each later call drives one element (resuming the coroutine on this thread), with output going to out via println. @return true when finished.

Link copied to clipboard
fun reportTimeout(stepNanos: Long)

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.

Link copied to clipboard
fun start()

Compile off the tick, then enqueue. The pump does the rest, on the tick thread.

Link copied to clipboard
private fun withOutput(tail: String): String

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.