One readTextFile, Fully Traced
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.