Skip to content

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

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

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