Environment
- perry 0.5.98
- macOS arm64 (darwin 25.4.0)
Problem
Inside a net.Socket 'data' event callback, calling obj.sock.write(bytes) where obj was retrieved from a Map.get() lookup silently fails to deliver the bytes to the peer, even though:
sock.write(bytes) returns immediately (with undefined — no error)
- The same
bytes buffer, written via the closure-captured sock reference, reaches the server and gets a response
- Small writes (≤5 bytes) via the Map path DO work; the failure appears only for larger writes (the driver hits it at 101 bytes)
This looks like the same class of bug as #87 (Promise resolve via property dispatch) but for Socket.write — the method call returns but the underlying I/O op never happens.
Minimal repro
// repro.ts — requires a TCP service that speaks first (any MySQL server).
import * as net from 'node:net';
import { writeHandshakeResponse41 } from '@perry/mysql/src/protocol/writer';
import { writePacket } from '@perry/mysql/src/protocol/framing';
import { DEFAULT_CLIENT_CAPABILITIES, CLIENT_CONNECT_ATTRS } from '@perry/mysql/src/protocol/capabilities';
import { sha256Scramble } from '@perry/mysql/src/auth/caching-sha2';
const STATES = new Map<number, { sock: net.Socket }>();
const sock = net.createConnection('YOUR.MYSQL.HOST' as never, 3306 as never);
STATES.set(1, { sock: sock });
let count = 0;
sock.on('data', (chunk: Buffer) => {
count += 1;
if (count === 1) {
// Extract 20-byte challenge from HandshakeV10 at known offsets, scramble, build packet...
// (elided for brevity — full repro in @perry/mysql tests/perry-aot-smoke.ts)
const bytes = /* 101-byte HandshakeResponse41 */;
const st = STATES.get(1);
if (st !== undefined) {
st.sock.write(bytes); // ← via Map-retrieved socket
}
console.log('[write] done');
} else {
console.log('[data #2] server responded!');
process.exit(0);
}
});
setTimeout(() => { console.log('timeout'); process.exit(1); }, 6000);
Change the key line to:
sock.write(bytes); // ← closure-captured socket, same bytes
and the program immediately receives the server's response.
Observed (Map path)
[data #1] len=95 — got HandshakeV10
[write] bytes.len=101
[write] done
timeout — server never sends AuthMoreData / OK / ERR
Expected (matches closure path and Node/Bun behaviour)
[data #1] len=95
[write] bytes.len=101
[write] done
[data #2] len=6 — server's AuthMoreData{0x03} FAST_AUTH_SUCCESS
Content verification
I verified the 101 bytes produced via the Map path are byte-identical to the bytes the closure path produces (both computed via writeHandshakeResponse41(...) + writePacket(1, payload)), and the MySQL server accepts the closure-sent version. So the bytes are valid; they're simply not being flushed when dispatched via obj.sock.write(...).
Hit in real code
@perry/mysql stores the net.Socket inside a ConnState struct keyed in CONN_STATES: Map<number, ConnState>. Every sendAuthFrame / sendCommand call does st.sock.write(bytes). Under Bun/Node this works perfectly (all 138 unit + 13 real-server integration tests pass). Under Perry 0.5.98 AOT, after #78–#88 were fixed, the driver successfully:
- Connects to MySQL 8.0.45 on webserver.skelpo.net
- Decodes HandshakeV10, receives 20-byte challenge
- Computes correct caching_sha2_password auth response (hex matches Node's output byte-for-byte)
- Builds valid 97-byte HandshakeResponse41 + 4-byte header = 101 bytes
- Calls
st.sock.write(bytes) — call returns normally
- Server never responds → connect times out after 10 s
Workarounds on the @perry/mysql side are ugly (either flatten the state map out of the write path or pass the socket directly through every helper). I'd rather wait for a proper codegen fix.
Related
Environment
Problem
Inside a
net.Socket'data'event callback, callingobj.sock.write(bytes)whereobjwas retrieved from aMap.get()lookup silently fails to deliver the bytes to the peer, even though:sock.write(bytes)returns immediately (withundefined— no error)bytesbuffer, written via the closure-capturedsockreference, reaches the server and gets a responseThis looks like the same class of bug as #87 (Promise resolve via property dispatch) but for
Socket.write— the method call returns but the underlying I/O op never happens.Minimal repro
Change the key line to:
and the program immediately receives the server's response.
Observed (Map path)
Expected (matches closure path and Node/Bun behaviour)
Content verification
I verified the 101 bytes produced via the Map path are byte-identical to the bytes the closure path produces (both computed via
writeHandshakeResponse41(...)+writePacket(1, payload)), and the MySQL server accepts the closure-sent version. So the bytes are valid; they're simply not being flushed when dispatched viaobj.sock.write(...).Hit in real code
@perry/mysqlstores thenet.Socketinside aConnStatestruct keyed inCONN_STATES: Map<number, ConnState>. EverysendAuthFrame/sendCommandcall doesst.sock.write(bytes). Under Bun/Node this works perfectly (all 138 unit + 13 real-server integration tests pass). Under Perry 0.5.98 AOT, after #78–#88 were fixed, the driver successfully:st.sock.write(bytes)— call returns normallyWorkarounds on the @perry/mysql side are ugly (either flatten the state map out of the write path or pass the socket directly through every helper). I'd rather wait for a proper codegen fix.
Related
'data'callbacks.