Environment
- perry 0.5.97
- macOS arm64 (darwin 25.4.0)
Problem
When a Promise's resolve function is stored as a property on an object and later invoked via property access (e.g. obj.resolve(val)) from inside a net.Socket 'data' event handler, the outer await never wakes. Calling resolve(val) directly (with the bare captured identifier) works correctly.
Related to #77 (which fixed the setTimeout path), but this variant involves property-access dispatch rather than a captured closure binding — and it still hangs under 0.5.97.
Minimal repro
// repro.ts
import * as net from 'node:net';
async function readOneChunk(host: string, port: number): Promise<number> {
return new Promise<number>((resolve, reject) => {
const sock = net.createConnection(host as never, port as never);
const box = { resolve: resolve }; // store on an object
sock.on('data', (chunk: Buffer) => {
console.log('[data] len=' + chunk.length + ' before resolve');
box.resolve(chunk.length); // <-- call via property access
console.log('[data] after resolve');
sock.destroy();
});
sock.on('error', (e: Error) => { reject(e); });
});
}
async function main() {
// Any TCP server that sends ≥ 1 chunk works; MySQL's HandshakeV10
// is a convenient test target.
const len = await readOneChunk('84.32.98.163', 3306);
console.log('main: resolved len=' + len);
}
main().catch(e => { console.error('err:', e.message); });
perry compile repro.ts -o /tmp/repro && /tmp/repro
# Observed:
# [data] len=95 before resolve
# [data] after resolve
# (hangs indefinitely — "main: resolved len=..." never prints)
#
# Expected (Node/Bun):
# [data] len=95 before resolve
# [data] after resolve
# main: resolved len=95
The same program with resolve(chunk.length) in place of box.resolve(chunk.length) completes correctly under 0.5.97.
Hit in real code
@perry/mysql's runTextQuery() stores resolve on a PendingQuery struct that hangs off the connection state map; finishResultset() (invoked from the socket 'data' callback) calls pending.resolve(result). Under AOT the driver authenticates correctly and receives the query response, but the await conn.query(...) hangs forever. Under Bun / Node the same code resolves instantly.
I've captured the trace from inside the driver showing resolve() returns normally from the callback, but the awaiting coroutine never resumes:
[onData] packet[0..3] seq=1..4 — all parsed correctly
[finishRS] statusFlags=0x2
[finishRS] before resolve
[finishRS] after resolve
<hang>
Expected
box.resolve(val) from a socket 'data' callback behaves identically to the bare resolve(val) — the awaiting coroutine resumes on the next microtask tick.
Related
Environment
Problem
When a Promise's
resolvefunction is stored as a property on an object and later invoked via property access (e.g.obj.resolve(val)) from inside anet.Socket'data'event handler, the outerawaitnever wakes. Callingresolve(val)directly (with the bare captured identifier) works correctly.Related to #77 (which fixed the setTimeout path), but this variant involves property-access dispatch rather than a captured closure binding — and it still hangs under 0.5.97.
Minimal repro
The same program with
resolve(chunk.length)in place ofbox.resolve(chunk.length)completes correctly under 0.5.97.Hit in real code
@perry/mysql'srunTextQuery()storesresolveon aPendingQuerystruct that hangs off the connection state map;finishResultset()(invoked from the socket 'data' callback) callspending.resolve(result). Under AOT the driver authenticates correctly and receives the query response, but theawait conn.query(...)hangs forever. Under Bun / Node the same code resolves instantly.I've captured the trace from inside the driver showing
resolve()returns normally from the callback, but the awaiting coroutine never resumes:Expected
box.resolve(val)from a socket 'data' callback behaves identically to the bareresolve(val)— the awaiting coroutine resumes on the next microtask tick.Related
setTimeout-based dispatch.setImmediate(() => pending.resolve(result))also not wake the await in my driver (I tried that as a workaround — same hang).