Skip to content

tee() + branch iteration SIGSEGVs once stream ids pass band start + ~512 (third #6599-class misclassification site) #6606

Closed

Description

@proggeramlug

Summary

readable.tee() + for await over a branch SIGSEGVs once stream-family ids pass STREAM_ID_BAND_START + ~512 — i.e. after ~256 prior stream+reader allocations. This is the same failure class as the two sites fixed in #6599 (numeric handle id with k >= 512 → low-48-of-double taken as a pointer → deref below the macOS ~2TB heap floor), at a third, still-unfixed site reached through the tee path.

Found while validating #6602 (id recycling) — it reproduces identically on a pristine main build, and is masked by #6602's recycling when the quarantine is small (ids stay below the fuse), which is how it surfaced: the same binary crashes with PERRY_STREAM_ID_QUARANTINE=64 or default but passes with =8.

Repro (deterministic)

async function churnReaders(n: number): Promise<number> {
  let total = 0;
  for (let i = 0; i < n; i++) {
    const rs = new ReadableStream({
      start(c) { c.enqueue("x"); c.close(); },
    });
    const reader = rs.getReader();
    const first = await reader.read();
    if (!first.done) total++;
    await reader.read();
    reader.releaseLock();
  }
  return total;
}

async function main() {
  console.log("readers:", await churnReaders(260)); // 250 → OK, 260 → SIGSEGV
  const src = new ReadableStream({
    start(c) { c.enqueue(1); c.enqueue(2); c.close(); },
  });
  const [a, b] = src.tee();
  let sa = 0, sb = 0;
  for await (const v of a) sa += v;
  for await (const v of b) sb += v;
  console.log("tee:", sa, sb);
}

main().then(() => console.log("OK"));
  • churnReaders(250) (max id offset k≈501): prints tee: 3 3 / OK.
  • churnReaders(260) (k≈521): crashes before printing tee:. Node prints tee: 3 3 / OK at any n.
  • Threshold sits exactly at the k≥512 fuse: each churn iteration allocates 2 ids (stream + reader).

Crash

EXC_BAD_ACCESS (code=1, address=0x1969fffffff8)
->  ldurb  w9, [x8, #-0x8]     ; header sniff at ptr-8

0x1969fffffff8 + 8 = 0x196A00000000 — the low 48 bits of the f64 bit pattern of an in-band numeric stream handle (≈1.15M, inside [0x100000, 0x200000)), i.e. somewhere in the tee/branch-iteration path a raw numeric handle is treated as a NaN-boxed pointer and its double-bits' low-48 get dereferenced, exactly like the two #6599 sites (ungated URLSearchParams probe, #597 fold's 48-bit codegen mask).

Filing separately per the granular-issue convention; #6602's fix reduces exposure under steady-state serving (recycled ids stay low) but does not remove the underlying misclassification.

Activity

  1. proggeramlug commented on Jul 18, 2026

    @proggeramlug
    ContributorAuthor

    This is fixed by #6599 — the attribution here was run on a pristine main build, which predates that PR's gates.

    Verified on the fix/stream-band-double-shape-probe toolchain (b389a58 + the #6599 commits, no #6608 recycling — so ids grow monotonically and there is no quarantine masking):

    • the exact repro above: readers: 260 / tee: 3 3 / OK, exit 0, byte-identical to node;
    • escalated to churnReaders(2000) (k ≈ 4000, ~8× past the fuse): still byte-identical to node.

    The tee-branch for await resolves @@asyncIterator through the same two dispatch funnels #6599 gates: the bound-values re-dispatch into js_native_call_method (site 1) and the #597 any-typed iterator fold (site 2) — there is no third ungated site on this path.

    Suggest closing this once #6599 merges (CI is green-so-far and the PR is mergeable). #6602's recycling remains valuable independently, but it is not needed as a mitigation for this crash.

  2. proggeramlug commented on Jul 19, 2026

    @proggeramlug
    ContributorAuthor

    Fixed by #6599 (merged as f9cf1f8) — verified on that branch without id recycling: the exact repro and a 2000-churn (k≈4000) run are byte-identical to node. See the verification comment above for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions