perf research: ext/http (Deno.serve)#8
Draft
crowlbot wants to merge 57 commits into
Draft
Conversation
…34208) ## Summary Enables `parallel/test-crypto-keygen-async-rsa.js` in the node_compat suite. The legacy PEM (Proc-Type/DEK-Info) encrypted RSA private key import/export paths required by this test were already implemented in `parse_legacy_encrypted_pem` and `encrypt_private_key_pem` (`ext/node_crypto/keys.rs`); the test was left ignored after the EC counterpart was enabled in denoland#33769. Verified locally against a 512-bit RSA key generated via `generateKeyPair('rsa', { ..., privateKeyEncoding: { type: 'pkcs1', format: 'pem', cipher: 'aes-256-cbc', passphrase: 'secret' }})`: - Generated PEM matches `pkcs1EncExp('AES-256-CBC')` (Proc-Type / DEK-Info headers, 64-char base64 body). - Sign without passphrase throws `ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED` — matches the OpenSSL 3 expectation in the test. - `publicEncrypt`/`privateDecrypt` and sign/verify round-trip correctly when the passphrase is supplied. ## Test plan - [x] `cargo test --test node_compat test-crypto-keygen-async-rsa` passes locally. Closes denoland/orchid#134 Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com>
## Summary - Enable `parallel/test-https-connecting-to-http.js` in `tests/node_compat/config.jsonc`. - The underlying TLS handshake error path already propagates the failure as an `error` event on the `https.get()` request (rustls' `InvalidMessage` is mapped to Node's `ERR_SSL_WRONG_VERSION_NUMBER` in `ext/node/ops/tls_wrap.rs`, and `_tls_wrap.js`'s `onerror` destroys the socket which surfaces via `_http_client.js`'s `socketErrorListener`). Only the config entry needed flipping. Closes denoland/orchid#133 ## Test plan - [x] `cargo test --test node_compat -- test-https-connecting-to-http` passes (verified 5 consecutive runs). - [x] Manual repro of the test scenario prints `Got expected error: received corrupt message of type InvalidContentType (SSL routines:ssl3_get_record:wrong version number) code=ERR_SSL_WRONG_VERSION_NUMBER`, matching Node's user-facing error code. Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com>
## Summary
`crypto.generateKeyPair('dsa', { modulusLength: N })` previously only
worked for the four fixed sizes the Rust `dsa` crate exposes via
`KeySize` (1024/160, 2048/224, 2048/256, 3072/256). Anything else —
including the 2049/256 pair Node.js's test suite exercises — was
rejected with `TypeError: Invalid modulusLength+divisorLength
combination`.
This patch:
- Vendors the `(p, q, g)` parameter generation in
`ext/node_crypto/keys.rs` so it accepts arbitrary `L` and `N`. The
algorithm is the same one the `dsa` crate uses internally (FIPS 186-4
§A.1.1.2 for the prime pair, Appendix A.2.1 for the unverified
generator). The resulting components are handed to
`dsa::Components::from_components` — that constructor is public, so we
sidestep the crate's `pub(crate)` `KeySize` fields without forking the
crate or pinning on an upstream PR.
- Adds basic input validation (L > N ≥ 2, both fit in `u32`).
- Enables `parallel/test-crypto-keygen-bit-length.js` in
`tests/node_compat/config.jsonc`.
Verified locally: DSA 2049/256 keygen succeeds (~1.7s), sign/verify
round-trips, and PKCS#8 PEM export works.
Closes denoland/orchid#135
## Test plan
- [x] `cargo test --test node_compat -- test-crypto-keygen-bit-length`
passes
- [x] `cargo clippy -p deno_node_crypto --all-targets` clean
- [x] DSA 2049/256 sign+verify round-trip works
- [x] DSA 2049/256 PKCS#8 PEM export works
Co-authored-by: divybot <divybot@users.noreply.github.com>
Co-authored-by: Divy Srivastava <me@littledivy.com>
…enoland#34204) Adds the missing CDP command. When a session opts in via NodeRuntime.notifyWhenWaitingForDisconnect(enabled: true), the process-exit handler now emits NodeRuntime.waitingForDisconnect to it instead of the generic Runtime.executionContextDestroyed, matching Node's behavior where the two notifications are mutually exclusive per session. Sessions that did not opt in continue to receive executionContextDestroyed as before. The wait-for-disconnect loop itself was already in place; this just plumbs the new per-session flag through the WebSocket dispatcher and splits the existing context-destroyed broadcast. Clears test-inspector-waiting-for-disconnect.js, which connects two sessions to a child running with --inspect-brk, opts one of them in, and verifies the opted-in session sees waitingForDisconnect while the other still sees executionContextDestroyed.
…pector') (denoland#34203) Node's internal inspector binding exposes isEnabled() so user code can ask whether the debugger is currently attached. Deno's process.binding ('inspector') was an empty object, so any code reaching for that helper hit \"inspector.isEnabled is not a function\" — most visibly the test-inspector-enabled.js node_compat test, which uses it to verify that --inspect=0 enables the inspector and that process._debugEnd() later flips the state back off. This adds a minimal inspector binding that delegates to the existing op_inspector_enabled op (the same op backing process._debugEnd's gate), enables the test in config.jsonc, and leaves room to grow the binding with the rest of Node's surface later.
…ands (denoland#34201) The Node-style inspector Network domain shipped in denoland#32707 plumbed event broadcast for Network.dataReceived / dataSent / requestWillBeSent / responseReceived, but the three CDP commands consumers actually use to pull bodies back out — Network.getResponseBody, Network.streamResourceContent, and Network.getRequestPostData — weren't implemented, so any tool that called them got a -32601 method-not-found error. This adds a bounded per-process buffer (32 requests max, 10 MB per request, FIFO eviction) on the inspector that captures bodies as the events flow through op_inspector_emit_protocol_event, plus dispatcher handlers for the three commands on both the local-session and WebSocket paths. Charset handling mirrors Node: utf-8 returns a decoded string, anything else returns base64. Raw Buffer/Uint8Array data passed to inspector.Network.dataReceived/.dataSent is base64-encoded at the polyfill layer to match the wire format consumers expect. Clears four node_compat tests: test-inspector-network-data-received.js, test-inspector-network-data-sent.js, test-inspector-network-arbitrary-data.js, and test-inspector-emit-protocol-event.js
This updates the node TLS polyfill to better match Node client session behavior for TLS resume tests. - honor mutable `tls.DEFAULT_MIN_VERSION` / `tls.DEFAULT_MAX_VERSION` in client and server secure contexts - expose client session values and session events for TLS 1.2 and TLS 1.3 resume flows - enable `parallel/test-tls-client-resume.js` and `parallel/test-tls-client-resume-12.js` in node compat config
Relands `module.registerHooks()` support after the full `module.register()` revert in denoland#34077. This intentionally keeps the scope focused on the stable sync API: - restores sync `resolve`/`load` hook chaining for CommonJS `require()` - restores ESM `import()` interception for sync `registerHooks()` hooks - restores `Module.registerHooks` - keeps the previous named `register()` stub that returns `undefined`, preserving compatibility for code that imported it before the deprecated implementation landed - adds focused Deno specs and re-enables the CJS sync Node compat cases This does not reintroduce the deprecated async `module.register()` worker-loader implementation or the `--experimental-loader` passthrough.
## Summary
Lazifies a large fraction of the JS code currently baked into the CLI
startup snapshot. End result:
| | Snapshot blob | Delta |
| --- | ---: | ---: |
| Before this stack (main) | ~11.4 MB | — |
| After | **7,331,556 bytes** | **−~3.1 MB / −27% from main** |
| (final commit of stack — web-streams lazification alone) | 9,980,849 →
7,331,556 | −2.65 MB |
Verified with `DENO_LOG_LAZY_LOAD=1 deno run hello.js`: **0 lazy loads
at startup**, in both TTY and pipe stdout modes. A
non-`fetch`/`stream`/`fs.promises`/`node:repl` program no longer pays
parse cost for any of those subtrees.
## What's now lazy
### Web platform (final commit)
The 208 KB `06_streams.js` polyfill and every ext module that pulls it:
- `ReadableStream` / `WritableStream` / `TransformStream` and all their
inner controllers/readers (13 stream classes)
- `Request` / `Response` / `fetch` / `EventSource` (chain through
`22_body.js` → `06_streams.js`)
- `caches` / `CacheStorage` / `Cache`
- `CompressionStream` / `DecompressionStream`
- `node:stream/web`
- `Deno.serve` / `Deno.serveHttp` / `Deno.upgradeWebSocket` /
`Deno.Command` / `Deno.run` / `Deno.spawn*` / `Deno.kill` /
`Deno.openKv`
### Node polyfills (earlier in stack)
- HTTP cluster: `node:http` / `node:http2` / `node:https` /
`node:_http_*` / `node:internal/http*`
- Crypto cluster: `node:crypto` /
`node:internal/crypto/{cipher,hash,...}`
- Streams cluster: `node:zlib`, `node:repl`, `node:internal/repl`,
`node:readline`, `node:readline/promises`
- Process cluster: `node:child_process`, `node:internal/child_process`,
`node:dgram`, `node:cluster`
- TLS cluster: `node:tls`, `node:_tls_common`, `node:_tls_wrap`
- Misc: `node:fs/promises`, `node:assert/strict`,
`node:internal/event_target`, `node:internal/fs/utils`
Kept eager (loading them is on the hot path of every program):
`node:stream`, `node:stream/promises`, `node:net`, `node:tty`,
`node:module`, `node:process`.
## Overview of changes
### Infrastructure (`build(snapshot)` + `refactor(core)`)
- **`DENO_SNAPSHOT_IMPORT_GRAPH=<file>`** env var: dump JSONL of every
esm/lazy-script edge during snapshot build. Used to identify exactly
which scripts are dragging which polyfills into the snapshot.
- **`DENO_LOG_LAZY_LOAD=1`** runtime env var: prints a stderr line each
time a lazy_loaded_esm / lazy_loaded_js entry actually parses at
runtime. Cache hits suppressed.
- **Captured `__bootstrap`** in `01_core.js` so deferred `loadExtScript`
calls still find `core`/`primordials`/`internals` after `99_main.js`
deletes `globalThis.__bootstrap`.
- **Residual `.ts` transpile in `build.rs`**: pre-transpile any
`lazy_loaded_js` / `lazy_loaded_esm` file that wasn't consumed at
snapshot time so the runtime loader receives parseable JS rather than
TypeScript.
- **Lazy-ESM resolve fallback**: in `module_map`, if static-import
resolve fails, fall back to the lazy ESM source list before erroring
(lets `node:_http_*` re-export work without eager registration).
### Bug fixes pulled out of the lazification work
- `fix(ext/node)`: defer `lazyLoadProcess()` to `deprecated()` wrapper
to break the `assert.ts ↔ process.ts` cycle exposed by lazification.
- `fix(core)`: drop the module-map borrow before recursively
re-evaluating a lazy ESM module — the prior code held it across
`module.evaluate(scope)` and panicked on `RefCell::borrow_mut` during
recursive lazy_load_esm.
### Final commit — web-streams chain
- `runtime/js/98_global_scope_shared.js`: converts every streams-pulling
global to `propNonEnumerableLazyLoaded` / wrapper-function form.
- `runtime/js/99_main.js`: stops spreading `denoNs` with `{...denoNs}`
(which invokes every getter); uses `ObjectDefineProperties +
getOwnPropertyDescriptors` instead. Same fix for the unstable-feature
merge loop. Wraps the wasm-streaming callback and defers
`registerDeclarativeServer` to the `addMainModuleHandler` callback.
- `ext/web/13_message_port.js`: drops top-level streams import;
`markNotSerializable` registration moved into `06_streams.js` itself
(inverts the dep so message_port no longer drags streams).
- `ext/node/polyfills/01_require.js`: lazifies `internal/child_process`
(which pulled `40_process.js → 22_body.js`) and `stream/web` (which
pulled `14_compression.js`).
- `ext/node/polyfills/internal/streams/fast-utf8-stream.js`: replaces
`import * as fs from "node:fs"` with `createLazyLoader("node:fs")`. The
static import was re-entering `node:fs`'s evaluating body and
TDZ-trapping on `lazyUtf8Stream().default`.
- `ext/node/polyfills/internal/fs/{handle,promises}.ts`: defers every
top-level `promisify(lazyFs().X)` to first-call wrappers. Same TDZ
cycle: `node:fs`'s `export const promises = mod.promises` line
re-triggers `get promises` while `lazyInternalPromises().default` is
still in TDZ.
## Outcome
| Surface | Improvement |
| --- | --- |
| Snapshot size | 11.4 MB → **7.33 MB** (−3.1 MB / −27%) |
| `deno run empty.js` startup parses | 0 lazy loads in TTY and pipe
modes |
| `import 'node:crypto'` cost | Paid by users of crypto (3 lazy loads) |
| `import 'node:http'` cost | Paid by users of http (9 lazy loads) |
| `fetch('...')` first-call cost | Loads `26_fetch.js` + `22_body.js` +
`06_streams.js` on demand |
Programs that don't touch streams/fetch/http/repl/Deno.serve no longer
pay the parse cost.
## Test plan
- [ ] `cargo test` passes
- [ ] `cargo test --test node_compat` passes (down from 43 → ~38 fails,
the remainder are pre-existing on main: `IO Safety violation` in `fork`
and the v8 weak-handle GC flake in `test-repl-tab-complete-buffer`)
- [ ] `DENO_LOG_LAZY_LOAD=1 deno run empty.js` prints 0 lazy loads (TTY
and pipe)
- [ ] Smokes: `Deno.serve`, `fetch`, `new
ReadableStream/Request/Response`, `structuredClone(new
ReadableStream())` rejection, `fs.promises.readdir/readFile`,
`node:child_process.spawn`, `node:stream/web`
---------
Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
… and builtin redirects (denoland#34219) A trio of fixes to `node:module` registerHooks() compat behavior, motivated by failing tests in the bundled `test-module-hooks` suite. In the ESM load hook chain, `nextLoad()` used to return `{ source: null }` when the chain fell through, breaking user hooks that wrap `nextLoad` to transform real source. The terminal step now reads the file from disk for `file://` URLs (and returns the builtin marker for `node:*`), matching the contract followed by the CJS path. `createRequire(url)` dropped a URL's query/hash because the resolve hook chain rebuilt `context.parentURL` from the Module's filename via `pathToFileURL`. The original URL is now stashed on the Module under a private symbol and preferred when populating `parentURL`. When a resolve hook redirected `require('zlib')` to a file path, `Module._load` would still hit `loadNativeModule(filename, request)` and return the native builtin via the original request name. The fallback is now skipped when the filename came from a hook (tracked in `cjsHookResolvedFilenames`). Six previously-failing module-hooks compat tests are enabled in `tests/node_compat/config.jsonc` as a result.
Fixes denoland#33723 - Return no hover when a dependency hover in a `.d.ts` file has neither code nor type resolution. - Add an LSP regression test for hovering an unresolved bare import in `types.d.ts`. Co-authored-by: lunadogbot <lunadogbot@users.noreply.github.com>
…enoland#34223) `Module._load` was checking the prefixed builtin cache before invoking the load hook chain, so a `registerHooks()` load hook only fired on the first `require()` of a given builtin. A subsequent `require('node:zlib')` (after a prior `require('zlib')`) returned the cached module and never re-entered the hook, even though Node invokes the hook on every `require()` of a builtin. Moves the load hook chain so it runs before the cache lookup. The hook result is then handled by the existing override / cache-hit / native-module path, so cached modules are still reused and the override path is unchanged otherwise. Enables the previously-failing module-hooks compat tests `test-module-hooks-load-builtin-override-commonjs`, `-json`, and `-module` in `tests/node_compat/config.jsonc`.
…d#34220) Building on the Network CDP domain landed in denoland#32707 and the body buffering hooks in denoland#34201, this wires the global `fetch()` into the inspector so that DevTools and `node:inspector` clients actually observe outgoing HTTP traffic. Until now Deno exposed the `Network.*` emitters but nothing inside Deno called them, so attached frontends saw no events and `Network.getResponseBody` had no data to return. When the inspector is attached, each `fetch()` now emits `requestWillBeSent`, `responseReceived`, `dataReceived`, and either `loadingFinished` or `loadingFailed`, including the post-data and response-body bookkeeping the buffer needs. The cross-extension bridge lives on `internals.__inspectorNetwork`, installed by ext/node's inspector polyfill, so ext/fetch doesn't depend on ext/node and the non-attached path is one `isEnabled()` check. Enables `parallel/test-inspector-network-fetch.js`; node:http / node:http2 / WebSocket instrumentation will follow.
For denoland/deno_core#1292 --------- Signed-off-by: Leo Kettmeir <crowlkats@toaxl.com>
Diff looks big but it's mostly tests
…enoland#34222) Follow-up to denoland#34220, on top of the same `internals.__inspectorNetwork` bridge. When the inspector is attached, constructing a `WebSocket` now emits `Network.webSocketCreated`, the resolved handshake response emits `Network.webSocketHandshakeResponseReceived` with real status/statusText/headers (`op_ws_create` returns them now), and every code path that dispatches the user-visible `CloseEvent` also emits `Network.webSocketClosed`. DevTools and `node:inspector` clients see WebSocket traffic alongside the fetch events. The compat test `parallel/test-inspector-network-websocket.js` is ignored with a reason: it asserts the initiator stack contains a frame matching `/undici/`, which is a Node-internal detail of how Node's WebSocket is built on undici. Deno's WebSocket is native, so no such frame exists; the rest of the assertions pass (verified with a standalone smoke test).
- dsherret/sys_traits#81 I'm updating dprint to use deno_npmrc.
…ame (denoland#34234) ## Summary - `dns.lookup` and `dnsPromises.lookup` now throw/reject `ERR_INVALID_ARG_VALUE` synchronously when called with a falsy hostname, matching Node 25+ (`node/lib/dns.js`, `node/lib/internal/dns/promises.js`). Previously Deno emitted a one-shot DEP0118 `DeprecationWarning` and resolved with a `null` address — Node removed that legacy fallback. - Drops the now-unused `emitInvalidHostnameWarning` helper from `internal/dns/utils.ts`. - Enables the previously skipped node_compat parallel tests `test-dns-lookup.js` and `test-dns-lookup-promises.js`. Those tests stub `cares.getaddrinfo` and assert on `dns.lookup(false, options, cb)` / `dnsPromises.lookup(false, options)`, expecting a sync throw/reject. Before this fix they bailed out with an unexpected `DeprecationWarning` ("DEP0118"), which the issue described as "spurious punycode DEP0040" — the actual emitted code is DEP0118 (invalid hostname), not DEP0040. ## Test plan - [x] `cargo test --test node_compat -- test-dns-lookup` — passes locally (parallel `test-dns-lookup.js`, `test-dns-lookup-promises.js`, `test-dns-lookup-promises-options-deprecated.js`, `test-dns-lookupService.js`, `test-dns-lookupService-promises.js`, internet `test-dns-lookup.js`). - [x] Verified `dnsPromises.lookup('127.0.0.1', { all: true })` still resolves correctly (untouched code path for IP hostnames). Closes denoland/orchid#139 Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com>
…enoland#34229) Closes denoland/orchid#140 Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com>
Found with `cargo shear`. Reduces build time a bit, doesn't affect binary size because it all got DCE'd anyway.
…land#34250) Closes denoland/orchid#152 --------- Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com>
## Summary
Implements the `diagnostics_channel` (and `tracingChannel`) emissions
that previously-absent Node compat tests rely on, plus fixes a few
preexisting polyfill bugs.
New channel emissions:
- `console.{log,info,debug,warn,error}` publish args to per-method
channels **before** formatting, so subscribers can mutate the args and
see the mutation reflected in output.
- `child_process` channel fires for each new `ChildProcess` (covers
`cluster.fork`). `tracingChannel('child_process.spawn')` publishes start
/ end / error with Node's "end XOR error" semantics. As part of this
`PermissionDenied` now maps to an asynchronous `EACCES` error event on
the child when uid/gid weren't requested (matching Node's `execve`
failure path); the EPERM-sync-throw path is preserved for uid/gid setup
failures.
- `worker_threads` channel fires per `Worker` construction.
- `tracingChannel('module.require')` wraps `Module.prototype.require`
with `{ parentFilename, id }` context.
- `net.client.socket` moved from the `net.connect()` factory to
`Socket.prototype.connect`, so all three client entry points
(`net.connect`, `net.createConnection`, `new net.Socket().connect`)
publish exactly once. TLS sockets (`tls.connect` via `TLSSocket extends
Socket`) now publish too.
- `tracingChannel('net.server.listen')` publishes `asyncStart` at the
top of `Server.prototype.listen` (with the original options object, so
ad-hoc fields survive), `asyncEnd` on `'listening'`, and `error` on
`'error'`.
- `http2.client.stream.bodyChunkSent` fires from
`ClientHttp2Stream._write` / `_writev` with `{ stream, writev, data,
encoding }`, honoring the writable internals' `allBuffers` flag so
single-buffer writers see a flat Buffer array.
`http2.client.stream.bodySent` fires from `_final`, including when no
chunks were sent.
Polyfill fixes shaken out by the new tests:
- `Channel.subscribe`/`unsubscribe` now snapshot the subscriber array
via `slice` + `pushApply`, and `publish` captures the local array
reference, so unsubscribe-during-publish still delivers to the remaining
subscribers (`test-diagnostics-channel-sync-unsubscribe`).
- `WeakRefMap`'s finalizer skips deletion when a fresh `WeakRef` for the
same key already holds a live value, fixing the GC race where a
re-created channel would be evicted out from under a live caller
(`test-diagnostics-channel-gc-race-condition`).
- `tracingChannel(0)` now reports `["string", "object",
"TracingChannel"]` so the formatted message matches Node's
(`test-diagnostics-channel-tracing-channel-args-types`).
Out of scope (left for follow-ups):
- `tracingChannel('module.import')` for dynamic `import()` — needs a
host-side dynamic-import hook in `deno_core`/`cli`, since
`core.compileFunction` doesn't accept a per-script dynamic-import
callback. Tests: `test-diagnostics-channel-module-import[-error]`.
- `test-console-diagnostics-channels` expects `process.stdout.write` to
be hijackable, which requires routing the global `console` through
Node's `Console` instance in Node-compat mode. The channel publishing is
in place; only the global-console wiring is missing.
- `test-diagnostics-channel-http2-client-stream-{created,start}` exposes
a pre-existing http/2 push ordering issue where push `created`/`start`
can outpace the request stream's publish in certain configurations.
Closes denoland/orchid#149
## Test plan
- `cargo test --test node_compat -- diagnostics-channel` — newly-added
entries pass; existing entries unchanged.
- `tools/format.js`
- `tools/lint.js --js`
- CI: verifies the network-bound tests (this worker VM has seccomp
filters that block listen(), so the http2/net body and listen tests
couldn't be verified locally and are added to config.jsonc to run on
CI).
---------
Co-authored-by: divybot <divybot@users.noreply.github.com>
Co-authored-by: Divy Srivastava <me@littledivy.com>
This stabilizes `text` imports, so that:
```ts
import hello from "./hello.txt" with { type: "text" };
```
will not require the `--unstable-raw-imports` flag.
This is as the tc39 proposal is stage 3, and the html spec changes
required are already landed.
---------
Co-authored-by: crowlbot <280062030+crowlbot@users.noreply.github.com>
…nd#34231) ## Summary Wires `node:http`'s `ClientRequest` into the inspector so DevTools and `node:inspector` clients observe outgoing http/https traffic from `http.request` / `http.get` / `https.request` / `https.get`. Builds on the Network CDP domain landed in denoland#32707, the body buffering hooks in denoland#34201, and the fetch() instrumentation in denoland#34220. When the inspector is attached, each client request now emits: - `Network.requestWillBeSent` (from the `ClientRequest` constructor, so the user's call-site stays on the V8 stack for `initiator` capture) - `Network.responseReceived` (from `parserOnIncomingClient`, with `type: "Other"` to distinguish from `type: "Fetch"`) - `Network.dataReceived` (per body chunk, by wrapping `res.push` rather than attaching a `data` listener — keeps the response paused until the consumer attaches its own listener, asserted by the upstream test) - `Network.loadingFinished` (on `res.push(null)`) or `Network.loadingFailed` (from `emitErrorEvent`) When the inspector is detached, the cost is one `internals.__inspectorNetwork.isEnabled()` call. ### Inspector server teardown fix Also fixes a pre-existing panic in `InspectorServer::drop`: `shutdown_server_tx.send(()).expect(...)` panicked when the server thread had already exited via `stop()` -> `reset_tx`, which manifests as a flaky exit-1 from the new node_compat test (`for i in 1..10; do deno run --inspect=0 test-inspector-network-http.js; done` reproduced it ~1/5 times). The Drop now treats a missing receiver as a no-op. Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com>
denoland#34258) Fixes denoland#34246. `readFileFromFd` allocated one shared read buffer and pushed `subarray` views of it for each chunk, then concatenated at the end. The subarrays alias the buffer, so when the read had to loop (i.e. when an `encoding` was provided and capped the buffer at `kReadFileBufferLength = 512 KB` while the file was larger), the next read overwrote the start of the buffer and corrupted all previously pushed chunks. The total length matched the file size, but the first chunk-worth of bytes was replaced with content from a later read. The path-based `fs.promises.readFile(path, encoding)` bypassed this code until denoland#34118, which rerouted it through `open()` → `fh.readFile()` → `fs.readFile(fd, {encoding}, cb)` — exposing the latent bug. It manifested as vite/rollup parse failures on large CJS modules like `react-dom-client.production.js` (536 KB). Rewrote `readFileFromFd` to mirror Node's `readFileHandle` (`lib/internal/fs/promises.js`): - Known size: single `Uint8Array(size)`, read with an advancing offset, return directly (one allocation, no concat). - Unknown size (pipes, sockets, `/dev/stdin`): allocate a fresh buffer per iteration so pushed subarrays don't alias. --------- Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
…tor (denoland#34262) `require("node:events").addAbortListener` was `undefined`, so libraries that hit the CJS path (notably undici 8.x used by `npm:undici`) crashed with `addAbortListenerNative is not a function`. Node attaches every named export as a static property on the `EventEmitter` constructor, and that's also what the ESM default export points at; Deno did this for most properties but had never assigned `EventEmitter.addAbortListener`, so both the CJS export and `import events from "node:events"` were missing it. The same file also captured the local `errorMonitor` binding before `EventEmitter.errorMonitor = kErrorMonitor` ran, so the named export and the namespace import resolved to `undefined` even though the property on the constructor itself was correct. Both bindings now read from `kErrorMonitor` directly. Fixes denoland#34261
…and#34264) `npm install` (and any other consumer of `@npmcli/agent`, `http-proxy-agent`, or `https-proxy-agent`) has been failing under deno with `ERR_INVALID_PROTOCOL: Protocol "https:" not supported. Expected "http:"`, thrown synchronously from `http.ClientRequest`. The polymorphic agent in those packages, which extends `agent-base`, reports `protocol: "http:"` even for `https.request(...)`. The root cause is `agent-base@7`'s `protocol` getter. When its own setter hasn't run yet — the common case for the singleton agents `@npmcli/agent` caches — it falls back to scanning the current stack for `(https.js:` or `node:https:` to decide whether the caller is the https module. Real node satisfies that by virtue of its script URL (`node:https:NNN:CC`), but the deno polyfill's stack frames read `ext:deno_node/https.ts:NNN:CC`, so neither pattern matched, the agent assumed http, and `http.ClientRequest` saw `options.protocol "https:" !== agent.protocol "http:"` and threw. Encode the marker in the `request` function's name so its call frame reads `at node:https:request (...)` and agent-base's substring check matches. The added unit test mirrors agent-base's `protocol` getter exactly and asserts `https.request(httpsUrl, { agent })` no longer throws `ERR_INVALID_PROTOCOL`; with the marker removed the test fails with the original error, so it's a real regression guard.
…enoland#33250) Reland of denoland#33158, which was reverted in denoland#33215 to be relanded for Deno v2.8. The fixes from denoland#33215 (internal timers for TLS handshake, sanitizer test fixes) are already in main. --- Changes the default for `sanitizeOps` and `sanitizeResources` from `true` to `false` in `Deno.test()`. This aligns with what most users expect -- tests no longer fail due to leaked async ops or resources unless explicitly opted in. ## New in this reland ### Config file support Sanitizers can now be configured in `deno.json(c)`: ```jsonc { "test": { "sanitizeOps": true, "sanitizeResources": true } } ``` ### Module-level API `Deno.test.sanitizer()` allows configuring sanitizers per-module: ```ts // Enable sanitizers for all tests in this file Deno.test.sanitizer({ ops: true, resources: true }); Deno.test("my test", () => { // ops and resources sanitizers are enabled }); Deno.test({ name: "override per-test", sanitizeOps: false, fn() { // this test opts out of ops sanitizer }, }); ``` ### Precedence (highest to lowest) 1. Per-test options (`sanitizeOps`/`sanitizeResources` on `Deno.test()`) 2. Module-level (`Deno.test.sanitizer()`) 3. CLI flags (`--sanitize-ops`/`--sanitize-resources`) 4. Environment variables (`DENO_TEST_SANITIZE_OPS`/`DENO_TEST_SANITIZE_RESOURCES`) 5. Config file (`deno.json` `test.sanitizeOps`/`test.sanitizeResources`) --------- Co-authored-by: crowlbot <280062030+crowlbot@users.noreply.github.com>
## Summary
- Implement real `node:wasi` preview1 compatibility for the 20 WASI
tests in the Node compat suite that were previously absent from
`tests/node_compat/config.jsonc`. All 20 now pass on Linux.
- Add 20 new entries to `config.jsonc` for the newly-passing tests.
The implementation work is in `ext/node/ops/wasi.rs` and
`ext/node/polyfills/wasi.ts`; the suite entries are added at the bottom
of `config.jsonc`. No existing entries were flipped — every test added
here is newly green.
## What changed in node:wasi
- **ENOTCAPABLE for sandbox escape.** `cant_dotdot` / `symlink_escape`
expect `errno == ENOTCAPABLE (76)`; we previously returned `ERRNO_PERM
(63)`. The starts_with check now maps to `NOTCAPABLE`.
- **Symlink loop detection.** Probe `raw_os_error()` for `ELOOP` so we
don't depend on the unstable `ErrorKind::FilesystemLoop` variant.
- **path_open / path_filestat_get / path_readlink follow flags.** Honor
`LOOKUPFLAGS_SYMLINK_FOLLOW` for path_open / path_filestat_get, and
force no-follow on the final component for path_readlink so
`std::fs::read_link` doesn't reject the resolved target.
- **futimens.** `fd_filestat_set_times` and `path_filestat_set_times`
use the `filetime` crate, with full `FSTFLAGS_ATIM(_NOW)` /
`FSTFLAGS_MTIM(_NOW)` semantics.
- **path_link.** Added (`std::fs::hard_link`) and wired through the JS
polyfill so `link(2)` works.
- **poll_oneoff.** Implements clock subscriptions (relative and abstime)
plus immediate fd_read/fd_write events. For stdin, the
`EVENT_FD_READWRITE_HANGUP` bit is set so wasi-libc's poll() shim
returns `POLLHUP|POLLIN` as test-wasi-poll.js requires.
- **stdio fd routing.** The WASI constructor's `stdin`/`stdout`/`stderr`
options now do something: fds >= 3 are dup'd into an owned
`std::fs::File` and used in place of the inherited process streams.
Closing the user's original fd afterwards doesn't affect ours.
- **sock_accept.** Returns `ERRNO_BADF` for unknown fds and
`ERRNO_NOTSOCK` for non-socket fds in our table (not `ERRNO_NOSYS`),
matching Node/uvwasi semantics.
- **ExperimentalWarning on construction.** Match Node's "WASI is an
experimental feature and might change at any time" warning so the
suite's `common.expectWarning('ExperimentalWarning', ...)` doesn't fail
the very first test.
- **finalizeBindings.** Node-only API used by `wasi:thread-spawn`
workers to bind a shared `WebAssembly.Memory` without re-running
`_start`. test-wasi-pthread needs it.
## Runner fixup
The vendored Node suite is missing the empty `test/fixtures/wasi/subdir`
directory (git doesn't track empty dirs). test-wasi-readdir asserts that
the preopen has four entries including `subdir`. The runner now creates
it before enumerating tests, with a docstring explaining why.
## Test plan
- [x] All 25 WASI tests in `tests/node_compat/runner/suite/test/wasi/`
pass via `cargo test --test node_compat 'test-wasi-'`
- [x] `tests/unit_node/wasi_test.ts` (added tests for finalizeBindings
and the ExperimentalWarning) passes
- [x] `tools/format.js --check`, `tools/lint.js --rs`, and
`tools/lint.js --js` all clean
## Platform-specific notes
The 20 candidate tests are bounded WASI behavior — no host-OS-specific
syscalls aside from the dup/_dup paths in stdio routing. The Linux run
exercises all 20; macOS should behave identically. Windows path
canonicalization / hard_link / symlink behavior is consistent with what
we already exercise elsewhere in `ext/node/ops/wasi.rs`, so I expect the
same wins on macOS and Windows but only Linux was verified in this
session.
Closes denoland/orchid#150
---------
Co-authored-by: divybot <divybot@users.noreply.github.com>
Co-authored-by: Divy Srivastava <me@littledivy.com>
Picks up denoland/deno_task_shell#175, which adds the POSIX `:` (null command) as a shell builtin alongside `true` and `false`. Without it `deno task` fails with `:: command not found` (exit 127) on any package.json whose script is the bare colon no-op, e.g. the `"test": ":"` script in the aube benchmark fixture (https://aube.en.dev/benchmarks.html) that previously prevented Deno from completing the install-test scenario at all. Verified locally: `deno task test` against a minimal `{ "scripts": { "test": ":" } }` package now exits 0.
## Summary
The `node:trace_events` polyfill was a stub — `createTracing` returned
an
object with no `.enable`/`.disable` methods and `getEnabledCategories`
always returned `""`. This fleshes out the polyfill enough that the
four ignored compat tests now run end-to-end.
- `createTracing` validates input, returns a `Tracing` with `enabled` /
`categories` properties and `enable()`/`disable()` methods, and emits
the `>10` instances memory-leak warning.
- `getEnabledCategories()` returns `undefined` when no instance is
enabled, otherwise the comma-joined union of every enabled instance's
categories.
- `internalBinding('trace_events')` now exposes `trace(phase, cat, name,
id, scope)` and `getCategoryEnabledBuffer(cat)` (with refcount
semantics) so fixtures going through `internal/test/binding` observe
the same state as the public API.
- When `node.async_hooks` is enabled, `setTimeout` / `setImmediate` are
wrapped to emit `b`/`e` events with `cat="node,node.async_hooks"`.
On process exit each isolate writes its recorded events; workers
write per-thread slice files in `cwd` and the main isolate aggregates
them into a single `node_trace.${rotation}.log` so consumers see one
combined log (matching Node's process-wide `TracingController`
behavior).
- `--trace-event-categories` is now plumbed through `node_shim` →
`child_process` as `DENO_NODE_TRACE_EVENT_CATEGORIES`. The node
bootstrap (`01_require.js`) reads it in every isolate (main +
workers), pre-enables the listed categories, and pushes the flag
back onto `process.execArgv` so test fixtures that probe `execArgv`
see the expected shape.
Enables in `tests/node_compat/config.jsonc`:
- `parallel/test-trace-events-api.js`
- `parallel/test-trace-events-async-hooks-dynamic.js`
- `parallel/test-trace-events-async-hooks-worker.js`
- `parallel/test-trace-events-get-category-enabled-buffer.js`
Closes denoland/orchid#136
## Test plan
- [x] `cargo test --test node_compat -- test-trace-events-api.js`
- [x] `cargo test --test node_compat --
test-trace-events-async-hooks-dynamic.js`
- [x] `cargo test --test node_compat --
test-trace-events-async-hooks-worker.js`
- [x] `cargo test --test node_compat --
test-trace-events-get-category-enabled-buffer.js`
- [x] `cargo test --test node_compat --
test-trace-events-dynamic-enable-workers-disabled.js` (already-passing
test still passes)
- [x] `cargo test -p node_shim` (127 tests pass)
- [x] `cargo clippy --bin deno`
---------
Co-authored-by: divybot <divybot@users.noreply.github.com>
Co-authored-by: Divy Srivastava <me@littledivy.com>
…4255) Closes denoland/orchid#147 --------- Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com>
Adds `deno ci`, a thin subcommand for reproducible installs in CI environments. It mirrors `npm ci`: errors if `deno.lock` is missing, removes any existing `node_modules` directory, then runs the install with `--frozen` so the lockfile must match the config file exactly. The intent is to give CI scripts and Dockerfiles an obvious, greppable signal of "reproducible install" without having to remember the right combination of flags on `deno install`. `--prod` and `--skip-types` are accepted with the same semantics as `deno install`.
Picks up two upstream changes in deno_task_shell: support for `set -e` / `set +e` (and the `set -o errexit` long form), which aborts the surrounding sequential list on the first non-zero exit, and the POSIX null command `:` as a builtin. See denoland/deno_task_shell#171 and denoland/deno_task_shell#175.
## Summary
- Replaces `notImplemented("test.run")` in
`ext/node/polyfills/testing.ts` with a real `TestsStream`-compatible
`Readable`.
- Supports `run({ watch, cwd, signal })`: emits `test:watch:drained`
after each run cycle, and `test:watch:restarted` (followed by `drained`)
when a file changes under the watched cwd.
- The returned stream both pushes events as data chunks (for async
iteration / `'data'` listeners) and emits each event under its `type` as
a named event, matching Node's `TestsStream` semantics so callers can do
`stream.on('test:watch:drained', ...)` directly.
- `AbortSignal` ends the stream and closes the watcher; bursts of fs
events are debounced (~50ms) so a single user-visible change triggers
exactly one restart cycle.
## Scope
This PR implements the watch-mode event machinery only. Programmatic
test discovery / execution under `run()` (which would let it actually
emit `test:pass`/`test:fail`) is deliberately left for a follow-up; the
stream emits empty run cycles for now. That is enough to unblock the
watch-event fixtures in the Node compat suite that drive behavior
through the programmatic API rather than spawning a subprocess.
Five previously-absent test-runner watch fixtures now pass and are added
to \`tests/node_compat/config.jsonc\`:
- \`test-runner/test-run-watch-cwd.mjs\`
- \`test-runner/test-run-watch-cwd-isolation-none.mjs\`
- \`test-runner/test-run-watch-cwd-isolation-process.mjs\`
- \`test-runner/test-run-watch-emit-restarted.mjs\` (validates the
drained/restarted/drained sequence via
\`assert.partialDeepStrictEqual\`)
- \`test-runner/test-run-watch-no-emit-restarted-disabled.mjs\`
(validates that \`watch: false\` never emits \`test:watch:restarted\`
via \`common.mustNotCall\`)
The remaining \`test-run-watch-*\` and \`test-watch-*-isolation-*\`
fixtures depend on subprocess spawning of a \`run()\`-fixture piped
through \`node:test/reporters\` \`tap\`, or on CLI \`--test --watch\`
flags - both substantial separate features and out of scope for this PR.
Co-authored-by: divybot <divybot@users.noreply.github.com>
Co-authored-by: Divy Srivastava <me@littledivy.com>
…land#34263) `deno fmt` panics with `finish_indent was called without a corresponding start_indent` when a tagged HTML/SVG template embeds content whose formatted output transitions by more than one indent level between adjacent lines. The minimal repro from denoland#29963 is ```js console.log(html`<a><svg viewBox="0 0 16 16" width="20" height="20" fill="currentColor"> <path d="" /> </svg></a>`); ``` `markup_fmt` produces lines at indent levels 0 → 2 → 1, and `dprint-plugin-typescript`'s embedded-template path emitted only a single `StartIndent`/`FinishIndent` per transition while its end-of-template cleanup emitted one signal per remaining level, leaving the dprint IR unbalanced. The fix is in `dprint-plugin-typescript` (dprint/dprint-plugin-typescript#783) and is consumed here by bumping the pin to `=0.96.1`. A spec regression test in `tests/specs/fmt/embedded_html_multi_indent` covers the case using the exact input from the issue and asserts the expected formatted output. Closes denoland#29963 Closes denoland#33623 Closes denoland#31820 Closes denoland#30276 Closes denoland#30980
…enoland#34270) Network.* CDP events for fetch and WebSocket only fired when user code imported node:inspector — the bridge that ext/fetch and ext/websocket look up on internals.__inspectorNetwork was installed inside that polyfill's module body. Attaching Chrome DevTools to a script that didn't import node:inspector showed nothing in the Network tab. Moves the bridge install into a small always-loaded script that runs at runtime startup via the node:process bootstrap. Also wires the missing WebSocket frame events (frameSent / frameReceived / frameError) and instruments Deno.upgradeWebSocket so server-side sockets accepted via Deno.serve show up alongside their clients in the inspector.
This change denoland/rusty_v8@12d7ef1#diff-b2b1f3ec619c03c5bba2f171beab1301d960e1dcac6a17a99ce02326ce9c49b4R83 made it so icu data is bundled in with v8 unconditionally, so we don't need to include the copy from deno_core anymore. Resolves a roughly 11MB regression in binary size with the v8 upgrade.
## Summary - Consolidates HTTP Brotli quality and window parameters into shared constants and a helper. - Reuses the helper for legacy and response-body byte-path compression while preserving the existing quality, lgwin, and buffer sizes. - Leaves streaming Brotli behavior, Node zlib Brotli behavior, and Web compression behavior unchanged. ## Validation - `cargo fmt --check` - `cargo test --locked --lib -p deno_http response_body` - `cargo build --locked --profile release-lite --bin deno` - Rebuilt a fresh same-target release-lite baseline and candidate on `bigboi`; measured `349161960` bytes baseline vs `349161888` bytes candidate, a 72-byte reduction. - Ran HTTP Brotli raw response and `node:zlib` Brotli round-trip smoke checks. Co-authored-by: Nathan Whitaker <nathan@deno.com>
…enoland#34273) Users naturally reach for `deno audit fix` (mirroring `npm audit fix`) before discovering the `--fix` flag. This accepts `fix` as a positional argument to the `audit` subcommand and folds it into the same `fix` boolean on `AuditFlags`, so both invocations behave identically. The positional is restricted to the literal `fix`, so unrelated arguments still error.
Relax the twox-hash dep to a default (caret) requirement. Update twox-hash from 2.1.0 to 2.1.2. Closes denoland#34274
…enoland#34287) Both `Math.sumPrecise` and `Intl.Locale.prototype.variants` are available at runtime via V8, but our bundled TypeScript libs (currently on 6.0) don't yet declare them, so `deno check` errors with TS2339 whenever user code references either API. This adds the missing typings to `lib.deno.shared_globals.d.ts` so both stable and worker contexts pick them up. `variants` is typed as `string` to match the spec, which returns the variant subtags joined by `-` (e.g. `"1996-fonipa"`).
Adds `--watch` support to `deno check`, so that the type checker re-runs automatically whenever a file in the dependency graph changes. This has been a long-standing user request — the existing workaround is to wrap `deno check` in an external file-watching tool, which is less precise and doesn't reuse anything across runs. The implementation follows the same shape as `run --watch`: when the flag is present, the check pass is run inside `file_watcher::watch_func`, and the factory is built via `from_flags_for_watcher` so the graph loader's `FileWatcherReporter` automatically registers every loaded module with the watcher. No bespoke path collection is needed beyond what the module graph already exposes. Closes denoland#14858
…es (denoland#34290) `deno install -g @earendil-works/pi-coding-agent` previously bailed with a "missing a prefix" error and asked the user to retype it with `npm:`, while `deno add @earendil-works/pi-coding-agent` and local `deno install @earendil-works/pi-coding-agent` have defaulted to the npm registry since denoland#33246. The global install path lives in `cli/tools/installer/global.rs` and was missed in that change.
…4253) ## Summary - Adds a guarded fast path for `ServerResponse.end(string)` that writes the response header and body directly when the response shape is safe. - Preserves existing behavior for HEAD/no-body responses, trailers, chunked responses, strict content length, queued writes, unsupported encodings, and non-server outgoing messages. - Reduces hot-path overhead for common node:http server responses while keeping the change limited to one reviewable runtime optimization. ## Validation - Ran release-lite formatting, linting, and diff checks for the changed runtime file. - Ran focused release-lite node:http and node_compat correctness checks for ServerResponse write/end, HEAD/no-body, empty writes, 304/content-length, standalone responses, and custom ServerResponse cases. - Benchmarked on the same target with release-lite builds: GET improved from 15,352.67 req/s to 17,054.93 req/s (+11.09%, 95% CI +8.44% to +13.74%); POST 16 KiB improved from 13,143.39 req/s to 14,215.37 req/s (+8.16%, 95% CI +5.25% to +11.06%). --------- Co-authored-by: Nathan Whitaker <nathan@deno.com>
## Summary - Switches the Quinn dependency in the networking extension from the broad `rustls` alias to the explicit AWS-LC Rustls provider feature. - Avoids enabling Quinn's Ring provider path while keeping the existing AWS-LC provider authoritative. - Reduces the measured release-lite binary size on the same execution target. ## Validation - `cargo check -p deno_net` - `cargo build -p deno --profile release-lite` - QUIC smoke test with `target/release-lite/deno run --allow-all --unstable-net` - Same-target release-lite size comparison on `bigboi`: as-built `315,245,864` bytes to `312,755,832` bytes; stripped scratch copy `139,131,824` bytes to `136,821,680` bytes. Co-authored-by: Nathan Whitaker <nathan@deno.com>
…34293) ## Summary - Removes the `base32` crate from the resolved dependency graph. - Replaces npm cache package-name encoding and decoding with a local RFC 4648 lowercase no-padding implementation while preserving existing on-disk cache names and lenient partial-input decode behavior. - Shares the cache-dir encoder from resolver code and removes a discarded `base32::encode` call in temporary file naming without changing the generated hex name format. ## Validation - `cargo fmt --check` - `cargo test -p deno_cache_dir npm --locked` - `cargo check -p deno_resolver --lib --locked` - `cargo check -p deno_fs --locked` - `cargo metadata --locked --format-version 1 | jq -e 'all(.packages[]; .name != "base32")'` - Wrapped `cargo tree --locked -i base32` package-not-found check - `git diff --check` Co-authored-by: Nathan Whitaker <nathan@deno.com>
Bumped versions for 2.8.0 --------- Co-authored-by: bartlomieju <bartlomieju@users.noreply.github.com> Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
## Summary Node accepts hostnames in `--inspect=...`, `--inspect-port=...`, and `inspector.open(port, host)`, resolving them via DNS. Deno's inspector flag parser and `op_inspector_open` only accepted IP-literal hosts, so `--inspect=localhost:0` failed at parse time. - Resolve hostnames via `ToSocketAddrs` in `inspect_value_parser` and `op_inspector_open`, preferring IPv4 results to match Node's resolver. - Add `op_inspector_port`. The `process.debugPort` getter consults it so userland sees the ephemeral port chosen by `--inspect=...:0`. An explicit `process.debugPort = N` assignment still wins, matching Node's mutable semantics. - For `--inspect-port=N` used without `--inspect[-brk|-wait]`, Node updates `process.debugPort` but does not start the inspector. Deno doesn't surface `--inspect-port` as a runtime flag, so the node_shim translator injects the equivalent `process.debugPort = N;` assignment into the eval code used by `-p`/`-e`. - Enables `parallel/test-inspector-port-zero.js` in the node_compat config. Closes denoland/orchid#142 ## Test plan - New unit test `inspect_value_parser_resolves_hostnames` in `cli/args/flags.rs` covers `localhost:0`, `localhost:1234`, and `localhost`. - New unit tests in `libs/node_shim/lib.rs` (`test_translate_inspect_port_injects_debug_port_for_print` and `test_translate_inspect_port_does_not_inject_when_inspector_enabled`). - `tests/node_compat/runner/suite/test/parallel/test-inspector-port-zero.js` now passes (verified across 5 consecutive runs). - Existing inspector tests still pass: `test-inspector-debug-end.js`, `test-inspector-has-idle.js`, `test-inspector-promises.js`, `test-inspector-open.js`, `test-inspector-open-coverage.js`, `test-inspector-open-port-integer-overflow.js`. - Existing inspector parser unit tests still pass: `inspect_flag_parsing`, `inspect_wait`, `inspect_default_host`, `inspect_publish_uid_flag_parsing`. - `cargo clippy -p deno_node -p node_shim -p deno --bin deno --lib` clean. Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com>
## Summary - Replaces the Unix install shim's `shell-escape` usage with a small local POSIX single-quote helper. - Removes `shell-escape` from dependency metadata and the lockfile. - Preserves existing shim quoting behavior for safe paths, shell-sensitive values, embedded single quotes, and empty strings. ## Validation - Ran focused install shim tests covering `generate_shim`. - Ran focused tests for the new POSIX shell escaping helper. - Ran `cargo build -p test_server`. - Ran `cargo fmt --check` and `git diff --check`. - Confirmed `shell-escape` is no longer present in the Linux `deno` dependency graph. Co-authored-by: Nathan Whitaker <nathan@deno.com>
## Summary - Replaces the JUnit test reporter's `quick-junit` dependency with a small local XML writer tailored to the existing reporter output. - Preserves generated JUnit report structure and escaping behavior for test cases, failures, errors, skipped tests, timings, and properties. - Removes `quick-junit` and its now-unused transitive packages from dependency metadata. ## Validation - Ran focused Rust tests for the JUnit reporter. - Ran JUnit-related spec tests where practical. - Confirmed the dependency graph and lockfile no longer include the removed packages. Co-authored-by: Nathan Whitaker <nathan@deno.com>
Tools like tsx do `import M from "node:module"; M.register` to decide whether the host supports module loader hooks. The named `register` export (a stub that returns `undefined`) was already present, but the revert and re-land of the module loader hooks stack lost the line that attaches it as a static on `Module`, so `M.register` came back as `undefined`. tsx then falls through to its `registerHooks` feature gate, which requires Node `>= 24.11.1` while Deno reports `24.2.0`, so it throws "This version of Node.js does not support module.register()". Re-attaching `Module.register = register;` next to`Module.registerHooks = registerHooks;` is enough to make the stub visible again and unblocks tsx.
…ural finding JS-side `--prof` attribution on two `Deno.serve` workloads (tiny JSON response, headers-iter + lookup, both at ~27-28k rps with -c 32) re-affirms the H1-H4 findings from `perf-research/fetch` (PR #1) but surfaces no ext/http-specific subsystem redesign that meets the prompt's architectural floor. The Deno.serve JS surface is a thin orchestration layer over Request / Response / Headers; the hot JS costs are inherited from those fetch types. Native attribution (hyper, op dispatch, libc, kernel — ~50 percent of total ticks) is left unranked: `kernel.perf_event_paranoid` cannot be lowered without privilege this host doesn't have. That budget is the next obvious direction for an ext/http investigation once the constraint lifts; it isn't a JS-side architectural question. Includes the workload servers, profile runner, autocannon + V8 prof artifacts for both workloads, and a short README documenting why this investigation does not produce a new H-finding.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
JS-side V8 `--prof` attribution on two `Deno.serve` workloads (tiny JSON
response, headers-iter + lookup) at ~27-28k rps under `autocannon -c 32`.
Headline
No new architectural finding. Every JS-side cost on the Deno.serve hot
path that crosses the prompt's architectural bar is already a finding in
`perf-research/fetch` (PR #1) and either has an open upstream PR or is
explicitly dropped as below-floor:
The `mapped` wrapper at `ext/http/00_serve.ts:553-680` shows ~4-8% of JS
time across both workloads, but this is per-request orchestration the
public `Deno.serve` contract requires (build `InnerRequest`, brand-check
the user's `Response`, dispatch). No subsystem-redesign preserves the
contract; per-call-site tweaks are below the prompt's floor.
50-55% of total ticks land in `/target/release/deno` and are not
attributable from JS-side profiling alone — native costs (hyper, op
dispatch, libc, kernel) need `perf` / `samply` which need
`kernel.perf_event_paranoid <= 1`. That budget is the obvious next
research direction once the host constraint lifts; not a JS-side
architectural question.
Diff
See `README.md` for the full breakdown including 2 attribution tables and
the version pin list.