Errors, Cancellation, Resources, and Shutdown
Errors cross an intentional mapping boundary
An op commonly returns Result<R, E> where E implements Deno’s JavaScript
error classification contract. The generated/runtime mapping chooses the
JavaScript error class, message, and stack behavior. A filesystem error is not
merely formatted text; callers should receive a useful JavaScript exception.
The runtime also distinguishes larger failure scopes:
- one op rejects one promise;
- an unhandled promise rejection follows language policy;
- module evaluation can fail;
- a worker can report a terminal error to its host;
- an isolate/runtime failure can end the process-level operation.
Permission denial is an ordinary operation failure
Filesystem and network ops check permission immediately before capability use. For TCP connection, Deno checks the requested hostname and then checks resolved IP candidates as well. This prevents a name-resolution path from bypassing network policy.
Cancellation is cooperative and cross-layer
For readTextFile({ signal }):
- JavaScript creates a
CancelHandleresource. - Rust retrieves it before the relevant await.
or_cancelobserves either operation completion or cancellation.- Closing the resource signals the pending operation.
- Rust removes the temporary resource on every exit path.
- JavaScript removes its listener in
finally.
Cancellation does not preempt arbitrary Rust instructions. It is observed at a future boundary designed to handle it.
Resource close is stronger than map removal
Removing rid → resource stops future JavaScript lookup. It may not destroy the
resource immediately because pending ops can hold cloned Rcs. The Resource
trait’s close method lets a type cancel outstanding work as part of closure.
TcpStreamResource explicitly cancels read/write operations. The source even
documents a subtle method-resolution failure that once made close call the
trait’s no-op default instead of the concrete cancellation behavior. This is a
good production lesson: cleanup paths deserve focused tests because code that
looks like ownership release may not yet release the operating-system resource.
Event-loop completion is a liveness proof
The runtime tracks pending ops, refed ops, timers, module evaluation, dynamic
imports, background tasks, promise events, external operations, and live native
handles. Returning Ready(Ok(())) means none of the work that should keep the
program alive remains.
That is stronger than “the queue was empty during this poll.”
Worker shutdown closes admission and wakes ownership
A Web Worker has a control path and termination waker. Its event-loop poll first
checks termination, registers for later termination, and delegates ordinary
progress to JsRuntime. The host receives structured close or terminal-error
events.
The general pattern is:
record stop intent → wake blocked event loop → stop admitting work
→ cancel/close owned resources → report terminal outcome
Reliable async systems represent shutdown as an ownership protocol, not merely as a boolean checked occasionally.