Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions js/net/src/stream.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,32 @@ test("closed is stable, so racing it per frame does not allocate", async () => {
expect(reader.closed).toBe(reader.closed);
});

test("reading to FIN builds no error", async () => {
// Fixtures built outside the window, and kept alive so an error they retain stays counted.
const readers = Array.from(
{ length: 32 },
() =>
new Reader(
new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new Uint8Array([7]));
controller.close();
},
}),
undefined,
QUIC,
),
);
const before = heapStats().objectTypeCounts;
for (const reader of readers) {
expect(await reader.u8()).toBe(7);
expect(await reader.done()).toBe(true);
}
const after = heapStats().objectTypeCounts;
expect(after.TypeError ?? 0).toBeLessThanOrEqual(before.TypeError ?? 0);
expect(after.Error ?? 0).toBeLessThanOrEqual(before.Error ?? 0);
});

test("stop after FIN allocates nothing and sends nothing", async () => {
const cancelled: unknown[] = [];
const reader = new Reader(
Expand Down
13 changes: 5 additions & 8 deletions js/net/src/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export class Reader {
#buffer: Uint8Array;
#chunks: Uint8Array[] = [];
#chunked = 0; // bytes across #chunks
#stream?: ReadableStream<Uint8Array>; // if undefined, the buffer is consumed then EOF
// Undefined once the stream finishes, or without a stream: the buffer is consumed then EOF.
#reader?: ReadableStreamDefaultReader<Uint8Array>;
#closed?: Promise<void>;
// The decode that last ran short and how far, so a retry can wait for those bytes.
Expand All @@ -294,8 +294,7 @@ export class Reader {
version: StreamVersion,
) {
this.#buffer = buffer ?? new Uint8Array();
this.#stream = stream;
this.#reader = this.#stream?.getReader();
this.#reader = stream?.getReader();
this.version = version;
}

Expand All @@ -314,11 +313,9 @@ export class Reader {

if (result.done) {
// The transport already finished the stream. Drop the reader so a later stop
// neither builds a cancel error nor sends a reset the peer will ignore.
if (this.#reader === reader) {
this.#reader = undefined;
reader.releaseLock();
}
// neither builds a cancel error nor sends a reset the peer will ignore. Dropped,
// not released: releasing a closed stream's lock builds a TypeError per stream.
if (this.#reader === reader) this.#reader = undefined;
Comment thread
kixelated marked this conversation as resolved.
return false;
}

Expand Down
15 changes: 11 additions & 4 deletions quest/m1/release-perf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ is fixed. Cost that pays for a feature `main` deliberately added is accepted,
and the reason is written here.

This epic's own work: once every child has landed, run the full main against
release comparison again with `just bench origin/release`, and confirm nothing
new regressed. Then add a release-baseline step to the recurring
release comparison again with `just bench origin/release`, plus the JS frame
path in Chromium with `bench/browser/micro.ts`, and confirm nothing new
regressed. Then add a release-baseline step to the recurring
[performance audit](/quest/m2/audit-perf.md), so every later cut
repeats this comparison.

Expand Down Expand Up @@ -102,7 +103,14 @@ and 12% less on fanout, with the same delivery.
- kio `contended_notify` and `contended_log` at 16 threads: 4 to 5x. These
are contention benches on a busy host, so they are likely noise.
- JS `net/bench/frames.ts` on bun: lite rows 1.2 to 1.3x, IETF rows 1.3 to
2.2x ([JS frames](/quest/m1/release-perf/js-frames.md)).
2.2x. Fixed in Chromium:
[#6296](https://github.com/moq-dev/moq/pull/6296) removed a retained-history
scan, and [#6479](https://github.com/moq-dev/moq/pull/6479) stopped
releasing a finished stream's reader lock, which built a `TypeError` per
group stream. Every row then sits within the A/A spread of release.
The group write/read microbench stays 3 to 12% above release against an
A/A of 3 to 8% on the same host. Decided 2026-10-23 (maintainer): browser
parity accepted on this evidence; the final re-run above checks it again.

The raw report lived in a scratch directory that has since been removed.
`just bench <base>` now pairs full Criterion IDs; point `MOQ_BENCH_ARTIFACTS`
Expand All @@ -112,7 +120,6 @@ outside the worktree to keep the next one.

- [Sparse gaps](/quest/m1/release-perf/sparse-gaps.md) - an arrival and a gap flush cost O(log gaps), not O(gaps)
- [Re-check](/quest/m1/release-perf/recheck.md) - the screen-only moves are re-measured with instruction counts, and the ones that hold are fixed or accepted
- [JS frames](/quest/m1/release-perf/js-frames.md) - the js/net frame path is no slower than release in a browser, or the gap is accepted

## Related

Expand Down
38 changes: 0 additions & 38 deletions quest/m1/release-perf/js-frames.md

This file was deleted.

Loading