closeServer in vscode-ext/src/peer-link.ts documents "Unlink only when the path still names our socket", but unlink only gates the explicit rm. closing.close() unlinks the path on its own: libuv's uv__pipe_close unlinks pipe_fname unconditionally. Reproduced on Node 26.9: A binds, B unlinks and rebinds, A.close() removes B's socket.
So the reclaim stand-down (closeServer(false)) removes the winner's socket too, not only disposal. Trace (from the #753 review):
- A and B both clear the corpse; B displaces A. Both are inside
stillOurs, and A's 250 ms expires first.
- A mismatches and stands down:
A.close() unlinks the path, which now names B's socket.
- If B's stat landed before that unlink, B confirms and serves an unreachable socket. A's next round
tryBinds the free path uncontested and confirms immediately. That leaves two confirmed brokers, which brokerConfirmed exists to prevent (docs/specs/vscode.md → "A bind is not a role until it is believed").
A likely fix direction is an atomic create-only publish: bind a unique path, then link() it onto the fixed one, which fails EEXIST instead of displacing. The unique path is then the one libuv unlinks on close.
Surfaced in #753, which narrows a different race and doesn't address this.
closeServerinvscode-ext/src/peer-link.tsdocuments "Unlink only when the path still names our socket", butunlinkonly gates the explicitrm.closing.close()unlinks the path on its own: libuv'suv__pipe_closeunlinkspipe_fnameunconditionally. Reproduced on Node 26.9: A binds, B unlinks and rebinds,A.close()removes B's socket.So the reclaim stand-down (
closeServer(false)) removes the winner's socket too, not only disposal. Trace (from the #753 review):stillOurs, and A's 250 ms expires first.A.close()unlinks the path, which now names B's socket.tryBinds the free path uncontested and confirms immediately. That leaves two confirmed brokers, whichbrokerConfirmedexists to prevent (docs/specs/vscode.md→ "A bind is not a role until it is believed").A likely fix direction is an atomic create-only publish: bind a unique path, then
link()it onto the fixed one, which failsEEXISTinstead of displacing. The unique path is then the one libuv unlinks on close.Surfaced in #753, which narrows a different race and doesn't address this.