Perry: 0.5.346 native (macOS aarch64). Discovered while building a benchmark harness for @perryts/mongodb against mongodb (official) + mongoose — every cell measured exactly 16 latency samples per op when the iteration count was higher.
When an async helper takes an array as a parameter and .push()es to it, only the first 16 push calls take effect. Subsequent pushes (after await-resolution boundaries) are silent no-ops — samples.length reads back 16 forever, even though the loop continues to call the helper.
The .push() itself is not the issue: a direct inline arr.push(v) in the same loop works to N=25 (and beyond). The problem appears only when the array is passed by reference into an async function and the push happens there.
Minimal repro
async function push(arr: number[], v: number): Promise<void> {
arr.push(v);
}
async function main() {
const samples: number[] = [];
for (let i = 0; i < 25; i++) {
await push(samples, i);
if (i >= 14 && i <= 17) {
console.log('after iter', i, 'samples.length=', samples.length);
}
}
console.log('FINAL length:', samples.length, '(expected 25)');
}
main();
Actual (Perry 0.5.346)
after iter 14 samples.length= 15
after iter 15 samples.length= 16
after iter 16 samples.length= 16
after iter 17 samples.length= 16
FINAL length: 16 (expected 25)
Expected (Bun 1.3.12 and Node 25.8.0 — both produce this)
after iter 14 samples.length= 15
after iter 15 samples.length= 16
after iter 16 samples.length= 17
after iter 17 samples.length= 18
FINAL length: 25 (expected 25)
Workaround that works
Inline the push in the caller — don't pass the array through an async function:
for (let i = 0; i < 25; i++) {
samples.push(i); // works correctly to 25
}
I had to do this in the bench harness — the original factored timed(samples, fn) helper was capping all latency samples at 16, which silently truncated percentile data without any error.
Notes
Perry: 0.5.346 native (macOS aarch64). Discovered while building a benchmark harness for
@perryts/mongodbagainstmongodb(official) +mongoose— every cell measured exactly 16 latency samples per op when the iteration count was higher.When an
asynchelper takes an array as a parameter and.push()es to it, only the first 16 push calls take effect. Subsequent pushes (afterawait-resolution boundaries) are silent no-ops —samples.lengthreads back 16 forever, even though the loop continues to call the helper.The
.push()itself is not the issue: a direct inlinearr.push(v)in the same loop works to N=25 (and beyond). The problem appears only when the array is passed by reference into an async function and the push happens there.Minimal repro
Actual (Perry 0.5.346)
Expected (Bun 1.3.12 and Node 25.8.0 — both produce this)
Workaround that works
Inline the push in the caller — don't pass the array through an async function:
I had to do this in the bench harness — the original factored
timed(samples, fn)helper was capping all latency samples at 16, which silently truncated percentile data without any error.Notes
arr[i]=writes silently dropped on module-level const arrays declared empty) and the closed JSON.parse silently caps output array at ~1666 entries for large inputs #46 (JSON.parse silently caps output array at ~1666 entries) — same shape of "writes vanish quietly." Likely a different mechanism: this one specifically involves the async-function-parameter handoff, not module-level state.number[],string[],object[]).awaitboundaries — each helper call's push counts toward the same 16-element ceiling.