From 9c5ed4d80ed898639ab84e4526e3487b4de8672e Mon Sep 17 00:00:00 2001 From: Jake Gaylor Date: Fri, 1 May 2026 03:42:03 -0400 Subject: [PATCH] Emit 'exit' immediately on EXIT byte (~38x exec speedup) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the non-TTY exec path, the server sends a single EXIT byte over the WebSocket and then holds the connection open ~5s before initiating its own close handshake. The current handler calls `this.close()` on EXIT and only emits the 'exit' event from `handleClose`, which fires when the WS actually closes — so every exec eats the server's ~5s wait before `execFile`'s promise resolves. The Python SDK had the same bug; the Elixir SDK does not — `sprites-ex` sends a CLOSE frame and immediately tears down the TCP, never waiting for the server's reciprocal CLOSE. Fix: emit 'exit' synchronously when the EXIT byte arrives, then issue the WS close as fire-and-forget. `handleClose` is guarded by `if (!this.done)` so it won't double-emit. In a side-by-side bench (5 cat /tmp/x calls against a warm sprite): before: ~5,300ms per exec after: ~140ms per exec (~38x speedup) This puts sprites-js on parity with the Python SDK after its equivalent fix (close_timeout=0, see superfly/sprites-py#26) and with the Elixir SDK. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/websocket.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/websocket.ts b/src/websocket.ts index 699038b..dab0e51 100644 --- a/src/websocket.ts +++ b/src/websocket.ts @@ -237,6 +237,14 @@ export class WSCommand extends EventEmitter { break; case StreamID.Exit: this.exitCode = payload.length > 0 ? payload[0] : 0; + // Emit 'exit' immediately. The server holds the WS open ~5s after + // sending EXIT before initiating its own close handshake, so + // waiting for handleClose to fire 'exit' adds a fixed ~5s tax to + // every exec. Fire-and-forget close — matches the Elixir SDK. + if (!this.done) { + this.done = true; + this.emit('exit', this.exitCode); + } this.close(); break; }