Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

One readTextFile, Fully Traced

Annotated execution traceJS Promise → Rust Future → JSRevision 89f33cb

Consider:

const text = await Deno.readTextFile("config.json");

1. The public JavaScript wrapper prepares cancellation

runtime/js/90_deno_ns.js exposes the function implemented in ext/fs/30_fs.js. The wrapper normalizes the path. If the caller supplied an AbortSignal, it creates a temporary cancellation resource and installs an abort handler that closes that resource.

The wrapper then awaits op_fs_read_file_text_async(path, cancelRid) inside a try/finally. Cleanup removes the listener and rechecks the signal even when the native op fails.

2. Generated op glue converts the call

The Rust function is annotated #[op2(stack_trace)]. The macro-generated glue converts the JavaScript string and optional small integer into:

pub async fn op_fs_read_file_text_async(
    state: Rc<RefCell<OpState>>,
    path: String,
    cancel_rid: Option<ResourceId>,
) -> Result<FastString, FsOpsError>

The public language boundary is dynamic, but the implementation immediately regains concrete Rust types.

3. Rust validates authority before I/O

Before awaiting, the op borrows OpState, clones the configured filesystem service, optionally retrieves the CancelHandle, and asks PermissionsContainer::check_open for read access.

Notice the short borrow scope. The RefCell borrow is not held across the file future’s .await. Owned/cloned values cross the suspension point instead.

4. The file future enters the op driver

The generated async-op path associates the future with an op ID and JavaScript promise ID. FuturesUnorderedDriver first polls it once. If it completes immediately, Deno can return without another event-loop trip. Otherwise the driver erases the concrete future into its arena and adds it to a FuturesUnordered submission set.

The future is pending, but the V8 isolate is free to do other work.

5. Tokio and the filesystem implementation make progress

The concrete FileSystem implementation determines whether work uses async OS I/O or an appropriate blocking bridge. The important contract at this layer is the Rust Future: it returns Pending with a registered Waker, then becomes ready after the underlying operation can progress.

If cancellation was configured, or_cancel(cancel_handle) races completion against the cancellation signal.

6. Completion returns to the Deno event loop

The driver pushes a completed op into its local completion queue and wakes the outer event-loop future. On a later poll, Deno removes the completion, maps its FastString or FsOpsError into a V8 value, and settles the matching promise. The event loop performs a microtask checkpoint, allowing the suspended JavaScript async function to resume.

7. Both layers clean up

Rust removes and closes the temporary cancellation resource on its completion path. JavaScript’s finally removes the abort listener and gives an observed abort precedence as the public API specifies.

JS wrapper owns AbortSignal listener
Rust op owns filesystem future
Op driver owns pending execution
Promise ID owns result correlation
Event loop owns re-entry into V8

No response thread reaches into V8. Completion is a wake-up plus a later, ordered event-loop action.