Skip to content

connect is abortable (A24): a guest-discarded dial reclaims its socket - #63

Merged
lannbot merged 1 commit into
mainfrom
abortable-connect
Aug 23, 2026
Merged

connect is abortable (A24): a guest-discarded dial reclaims its socket#63
lannbot merged 1 commit into
mainfrom
abortable-connect

Conversation

@lannbot

@lannbot lannbot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

A guest that cancels an in-flight connect previously left the platform WebSocket connecting until this module's own 30s connect bound fired: polyengine A23's discard is delivery-only ("the host operation itself is not interrupted"). The motivating consumer is polymorph-iroh's new relay dial deadline (polymorph-iroh#93 / polymorph-iroh#95): its guest drops the losing connect future at 10s, and polyengine A24 (polyengine#243) added abortable() for exactly this — naming this dial as the case to fix.

The change

Websocket.connect carries the A24 mark (stage-3 decorator spelling on the existing static): every call receives an AbortSignal appended after the WIT-declared parameters, aborted by the runtime when — and only when — a guest cancellation discards the call. Three windows:

  • Pre-aborted: refused connect-failed before a platform socket exists.
  • Mid-handshake: the abort rides the existing settle discipline (same helper as the connect timer — listeners removed, timer cleared), rejects connect-failed, closes the socket.
  • Post-handshake: an abort after the handshake means the resource can never reach the caller (the discard-only guarantee), so the connection is closed as undeliverable. A signal.aborted pre-check covers the sliver between the handshake settle and this registration.

Direct callers omit the signal; arity under the mark is stable (A24). The module still couples only to @polyengine/protocol (A22).

Floors

  • @polymorph/websocket: @polyengine/protocol@^0.2.3 — the module now imports the mark, so 0.2.3 is a floor, not a refresh.
  • conformance/driver-ct/polyengine: exact pins move to the 0.5.1 runtime family + protocol 0.2.3 (the runtime that actually delivers A23/A24). The A22 pin gate passes: one resolved runtime-family version, one resolved protocol version across both locks.
  • AGENTS.md's version-string mentions updated to match.

Consumers pick this up through their normal @polymorph/websocket release/lock refresh; no surface change (the WIT contract is untouched — the signal is embedder-side vocabulary).

Tests

  • brand: isAbortable(Websocket.connect).
  • mid-handshake abort against a raw-TCP stall stub (accepts, drains, never answers; bare Deno.listen so the test observes accept and socket teardown): rejects connect-failed well under the 30s bound, and the stub sees the socket reclaimed.
  • pre-aborted signal: rejects with no socket ever opened.

Falsified: with the handshake abort listener disabled, the mid-handshake test falls through to the 30s connect bound and fails (0 passed | 1 failed (30s)).

Gates

  • just polyengine-module-check: pass (17/17)
  • just exam-polyengine: pass (runtime-family pin: 0.5.1, protocol pin: 0.2.3, no @polyengine/runtime specifier)
  • just validate-wit: pass
  • just check-js: fails identically on unmodified main (node --check js/componentize/websocket.js: ESM-vs-CJS SyntaxError under node 24) — pre-existing, untouched by this PR.

A guest that cancels an in-flight connect — polymorph-iroh's relay dial
deadline (its #93/#95) drops the losing future at 10s — previously left
the platform WebSocket connecting until this module's own 30s bound
fired: polyengine's A23 discard is delivery-only. With the A24
`abortable()` mark, every call receives an AbortSignal the runtime
aborts exactly when a guest cancellation discards the call:

- mid-handshake, the abort settles the dial connect-failed through the
  same settle discipline as the connect timer, and closes the socket;
- pre-aborted, the dial is refused before a socket exists;
- after the handshake, an abort means the resource can never reach the
  caller, so the connection is closed as undeliverable.

Direct callers omit the signal; arity is stable under the mark.

Floors move with the import: @polyengine/protocol ^0.2.3 (the mark's
first release) in the published module, and the conformance driver's
exact pins to the 0.5.1 runtime family + protocol 0.2.3 (the runtime
that delivers A23/A24). The A22 pin gate passes on one resolved
version per line.

Falsified: with the handshake abort listener disabled, the
mid-handshake test falls through to the 30s connect bound and fails.

Gates: just polyengine-module-check (17/17), just exam-polyengine,
just validate-wit. (just check-js fails identically on unmodified
main: node --check on js/componentize/websocket.js, pre-existing.)
@lannbot
lannbot enabled auto-merge August 23, 2026 19:25
@lannbot
lannbot merged commit 1d5992e into main Aug 23, 2026
2 checks passed
@lannbot
lannbot deleted the abortable-connect branch August 23, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants