Left (Error "Timeout") when using Aff.timeout in nested race conditions
0 reputation · 21 Feb 2022, 10:54 UTC
0 reputation · 21 Feb 2022, 10:54 UTC
When using PureScript's Aff monad, a timeout implemented with Aff.timeout cancels the underlying fiber and runs its finalizers before returning a Left (Error \"Timeout\") result. The goal is to understand whether this cancellation behaves as an immediate asynchronous interrupt that can skip synchronous, effect‑free sections, or as a synchronous operation that guarantees finalizer execution even in nested race constructs. This distinction affects resource safety when multiple timeouts race with each other or with other computations.
race where the losing branch is cancelled?Aff.timeout calls where the outer timeout fires before the inner one?34775 reputation · 21 Feb 2022, 19:55 UTC
In PureScript's Aff, cancellation is not an immediate asynchronous interrupt that can slice through synchronous JavaScript execution. Because Aff is built upon the JavaScript event loop, a fiber can only be interrupted at asynchronous boundaries (yield points). Synchronous, effect-free sections of code (pure computations) will execute to completion before the fiber checks for an interruption signal.
Finalizers (registered via bracket or finally) are guaranteed to run when a fiber is cancelled, including when it is the losing branch of a race. When Aff.timeout triggers, it interrupts the underlying fiber; this interruption propagates through the fiber tree, triggering all registered finalizers in the order of their nesting (innermost to outermost).
When nesting Aff.timeout calls, the behavior is determined by which timer expires first:
Left (Error "Timeout"). The outer timeout continues to run, but it receives the result of the inner operation immediately.Left (Error "Timeout").To verify resource safety in your specific nested structure, you can use a simple logging bracket:
-- Use a bracket to verify finalizer execution during race/timeout
bracket
(log "Acquiring Resource")
(\_ -> log "Releasing Resource")
(\_ -> Aff.sleep (1000 * milliseconds))
If "Releasing Resource" appears regardless of whether the inner or outer timeout wins, your resource safety is intact.
To provide a more precise answer regarding your specific resource leak or race, please specify if you are using any uninterruptible blocks or FFI calls that block the JavaScript main thread, as these can prevent Aff.timeout from triggering.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 21 Feb 2022, 13:15 UTC
When an outer Aff.timeout fires before an inner one, the cancellation propagates asynchronously only after the current synchronous step. The inner fiber is cancelled first, its bracket or finally block runs immediately, then the outer fiber’s finalizer executes. This guarantees that resources acquired in the inner branch are released before the outer branch completes.
Aff.race
(Aff.timeout 1000 (bracket acquire release work))
(Aff.sleep 2000)
Hence, finalizers run innermost‑to‑outermost, regardless of which timeout expires first.