Skip to content
LogoLogo

VIP-22: interruption blocks: uninterruptible, restore, interruptible

Status: accepted by Will, 2026-10-28.

Decision

uninterruptible {
  try charge(card)
  restore { try notify(user) }          // interruptible again inside
  try record(receipt)
}
interruptible { try poll() }
 
// exactly this Effect TS
yield* Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
  yield* charge(card)
  yield* restore(Effect.gen(function* () { yield* notify(user) }))
  yield* record(receipt)
}))
yield* Effect.interruptible(Effect.gen(function* () { yield* poll() }))
  • These are statements inside an effect function or block, like provide (VIP-12). Each is exact sugar for Effect's function.
  • restore { … } is allowed only inside an uninterruptible block; anywhere else it is a compile error. It selects the innermost enclosing uninterruptible block in its source activation. That entry state may already be uninterruptible; restore, interruptible and provide blocks do not rebind the capability.
  • Amended 2026-10-29: these are ordinary blocks. return, break and continue inside them reach the enclosing function or loop, as in any TypeScript block. (for concurrent and for try keep their own rule, because their bodies run once per item.) Recorded under Will's standing approval of recommendations.
  • Amended 2026-10-30: restore follows source activations. Capturing an outer mask across an effect function declaration or expression, arrow, method, producer or effect block is EK2202: the nested Effect can run after the mask ends. That activation may introduce its own uninterruptible mask and restore it. Transparent regions and for try callback lowering add no source activation boundary; the following for concurrent restriction remains. See Interruption blocks.
  • Amended 2026-10-30: restore in a for concurrent body. restore inside any for concurrent head binding initialization or body is a compile error (EK2202), whatever the limit: each item runs as a separate Effect that the enclosing mask does not govern. The limit and the iterable run outside the item region and keep their enclosing rule. Head binding initialization added 2026-10-31: it runs in the same item generator. Mask per item with Effect.uninterruptibleMask inside the body, or use a plain for. Decided by the agent on 2026-10-30 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 51a).
  • Under VIP-6, the keywords remove the globals of the same name, so Effect's functions are written Effect.uninterruptible and Effect.interruptible.
  • Ambiguity: none. These are contextual keywords followed by { on the same line.
  • Amended 2026-10-30: observer payloads and source completions. A provide or interruption region preserves its source completion separately from the primitive Effect success value. Normal completion and outward break/continue give primitive success undefined; a source return value gives that exact value, and bare return gives undefined. Nested regions apply this at each boundary. Finalizers observe these generator payloads, never compiler control records; a normal source value still contributes to an enclosing do. Real returned Effects and record-shaped objects remain data. Completion state is fresh for each execution/item, and failed, dying or interrupted bodies/finalizers transfer no pending source completion. This preserves the original generator counterpart and Effect's own finalization rules; see Region bodies.

History

About 20 findings, all about masking during using acquisitions. VIP-8 removed using's Effect rules, so none apply.

  • Amended 2026-10-30: ordinary-block completion values. A transparent block preserves the source completion's kind, value (including empty) and target. A surrounding do can observe its normal value or a value carried through a consumed jump; generated callbacks do not discard it. The compiler's transport is its choice; transfer the completion only after the region Effect and its finalizers succeed. Failures and finalization retain Effect's rules.

Prior art

Haskell's mask $ \restore -> …, Cats Effect's uncancelable { poll => }, ZIO's uninterruptibleMask, Kotlin's withContext(NonCancellable) { }.

Will's words

"ok we are on a streak of me accepting your recomendation. Keep showing them to me 1 at a time but assume I accept now. I will interrupt you if I need to"