Warning: Effeck is v0. The language will change in major, breaking ways. It is not suitable for production use.

23. Boundaries and finalization

Effeck
const approved = handle(() => {
  const viaMap = ids.map((id) => Approval.approve(id))                    // EK4301: a JavaScript frame
  const viaFork = forEach(ids, (id) => Approval.approve(id), {
    concurrency: 4                                                        // EK4201: other fibers
  })
  return forEach(ids, (id) => Approval.approve(id))                // fine: same fiber, Effect frames
}, Approval, { approve: (_request, k) => resume(k, true) })

Three calls look alike and meet three different boundaries. A capture moves frames between a perform site and its prompt, so whatever sits between them decides whether the capture can happen. The compiler rejects the first two; the runtime would otherwise fail them with UnhandledOperation and HandlerNotOnThisFiber.

Execution boundaries

BoundaryCreated byCapture through itState it restoresOwner of what it delimits
Runner (program entry)Effect.runPromise, NodeRuntime.runMain, a test runner such as it.effectCreates a fiber root for a program value. Effeck code never creates roots by calling; only runners and fork do (Chapter 13)The runner
Fiber rootRunners, forkStops every capture. Runtime operations (wait, sleep, yield) park the whole stack here.NoneThe scheduler
Prompthandle, the loop of a for...of over a producer (Chapter 26)Stops capture for its operations. Other operations pass through; this prompt then moves with the captured segment.Γₒ, ιₒThe clause invocation, or a Scope after transfer
Scope regionA block that declares using or calls forkTransparent: its Final frame moves with a captured segment and closes the Scope when the segment unwindsThe block: its using acquisitions and forked fibers, nothing else
Catch, Final, Restore, Masktry/catch, finally, provide, masksTransparent: the frames move with a captured segment and act when it unwindsTheir own
Owner, ClauseCaptureTransparent to outer capturesNoneIts continuation
Foreign JavaScript frameA call into untransformed JavaScript: an Array.prototype.map callback, Effect.runSyncOpaque. An operation performed below it cannot reach a handler above it. runSync starts a new root with an empty Context, so the operation is unhandled there (foreign-frame test).
TypeScript Effect codeA TypeScript function returning EffectTransparent on the same fiber (T7a), but its body can fork where the checker cannot see, so a TypeScript function whose R holds a control operation is EK4202
Plain JavaScript regionCompiler optimization (Chapter 15)Must contain no capture or pause point
Program runCalling a programEach run allocates fresh frames and continuations. Nothing crosses runs; a continuation from one attempt cannot be resumed in another (T11).
Workflow executionEffect's effect/workflow: Workflow.execute, toLayerThe body runs on a forked child (workflow/Workflow.ts:695-780), so a handler outside it cannot be reached. Durable suspension interrupts that fiber (Workflow.ts:1031-1036): held continuations are abandoned through their owners. Every execution, first or replay, is a fresh program run, and only activities and durable state are journaled (D42)The workflow engine
Forkfork, concurrent combinatorsA new root. Context crosses as a snapshot (F7); prompts do not (T8; probes in Chapter 16).The block region, a Scope, the parent fiber, or nothing

Regions and ownership

A block opens a Scope region when it directly declares using or calls fork; the region closes when the block exits, by any path. Only the using acquisitions and the forked fibers of that block belong to it. Any other acquisition in the block keeps Scope in the function's requirements, so the caller owns it (D14, D19). Whole-block ownership was the alternative, and review probe R8 shows why it was rejected: it closes a connection the function returns to its caller.

Finalization

ExitWhat runsEvidence
ReturnFinal frames in LIFO order with SuccessEffect OnExit, internal/effect.ts:4173-4214 [Effect]
Expected failureUnwind to the nearest Catch whose cause is made only of expected failures; Final frames on the way run first with Failure(Fail e)[Effect]; K14a
Expected failure plus a defect or interruptionUnwind past every Catch; Final frames run; the cause keeps every reasonK14b, K14c [Kernel]
DefectUnwind past every Catch; Final frames run[Effect]
InterruptionLike a defect. While the fiber has a pending interruption π and is interruptible, even cause-level catchCause frames are skipped (internal/core.ts:611-627). A self-raised interrupt cause with no π, such as Effect.interrupt, can still be caught by catchCause (conformance test D04), but not by source catch. Final frames run masked[Effect]
AbandonmentThe segment unwinds as if interrupted. Finalizers see an interruption Exit, catchCause inside the segment does not run, and the abandoner continues only after the unwinding finishes.T3 [Kernel]
Abandonment inside a maskA recovery handler inside the mask may observe the abort, but the abort re-raises when the mask ends. A segment entirely inside a mask that swallows the abort and returns normally is a defect, AbandonedContinuationContinued, at every owner: clause invocation, abandon, and Scope (kernel-edges; A04).[Kernel]
Interruption while a continuation is heldClause finalizers, then the held continuation's finalizers, then outer finalizersT4 [Kernel]
Finalizer failure during abandonmentA release that dies fails the boundary that abandoned: the handle (kernel-edges), the caller of abandon (A02), the closing Scope region (A01), or the exiting fiber (K13d). The abort itself is not reported; a real pending interruption is reported beside the failure (K12a-K12e)[Kernel]
The capturing fiber exits while it still holds transferred continuationsEach is abandoned on that fiber, newest first, before the fiber's exit is publishedK13a-K13d [Kernel]
A finalizer that performs a control operation handled outside itWorks when the outer handler resumes, even mid-abandonment: the abort moves with the capture and returns at resume (I11). If the outer handler abandons instead, the finalizer is cut short. Effeck therefore requires a finalizer's control operations to be handled inside the finalizer [Rule; checker]I11, I06 [Kernel]

JavaScript finally inside an Effect.gen body is not a finalizer: Effect never calls the generator's return() or throw() (F1, F2, F3). Effeck finally and using therefore compile to Final frames, never to JavaScript finally, in every function that compiles to frames (Chapter 26).