Skip to content

Array.push from inside an async function silently caps at 16 elements when the array is a function parameter #233

Description

@proggeramlug

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

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