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

Build a Smaller Deno Runtime Boundary

Reconstruction projectPreserve the architectural centerAnalysis first

Do not begin by embedding V8. First rebuild the architectural center with a small command language and a local event loop.

What are we preserving?

  • a dynamic caller-facing command becomes a typed native operation;
  • native services and permissions live in explicit runtime state;
  • several operations can be pending concurrently;
  • results return through correlation IDs on the event-loop owner;
  • long-lived native objects live behind typed resource IDs;
  • cancellation and close wake pending operations;
  • a second worker owns separate state and communicates by messages.

Suggested construction sequence

1. Define one concrete operation

Accept Command::ReadText { path }. Validate it, check a simple ReadPermissions value, and return Result<String, OpError>.

2. Separate submission from completion

Assign a PromiseId, put the typed read future into FuturesUnordered, and store a callback or oneshot sender that knows how to deliver that ID’s result. Drive the set from a poll_fn-based event loop.

3. Add a resource table

Implement:

type ResourceId = u32;

trait Resource: Any {
    fn close(&self) {}
}

Store unlike resources behind Rc<dyn Resource>. Provide generic add<T> and get<T> methods with checked downcasting. Add an in-memory duplex resource before using a real socket.

4. Add cancellation

Create a cancellation resource for one read, race it against a delay, and prove that every success, error, abort, and dropped-caller path removes the temporary entry.

5. Make event-loop liveness explicit

Track refed pending operations separately from unrefed background work. Write tests showing when the event loop may exit and when it must stay alive.

6. Add a worker boundary

Run a second local runtime with independent state. Exchange owned messages over bounded channels. Do not share the resource table. This demonstrates why parallel language workers need a message boundary.

7. Compare with production Deno

Only now inspect what Deno adds: V8 handle safety, op2 code generation, zero-copy buffers, many extension families, JavaScript error classes, permission prompts, module loading, inspector integration, web compatibility, platform-specific I/O, and carefully tuned event-loop phase semantics.

Tests that matter more than features

  • Two ops complete out of submission order but reach the right callers.
  • A permission failure performs no I/O.
  • Looking up a resource as the wrong type returns an error.
  • Closing a resource wakes a pending read.
  • Dropping one result observer does not stop unrelated operations.
  • Unrefed work does not keep an otherwise idle loop alive.
  • Worker termination wakes a parked event loop.

Rebuild the op/resource/event-loop relationship, not a toy imitation of Deno’s public JavaScript syntax.