Skip to content

fix: stop closing the ping/receive wait handles while they are still … - #12

Open
andreion1ca wants to merge 1 commit into
AltServer-Websocket-Sharpfrom
fix/206-ping-vs-close-wait-handle-race
Open

andreion1ca wants to merge 1 commit into
AltServer-Websocket-Sharpfrom
fix/206-ping-vs-close-wait-handle-race

Conversation

@andreion1ca

@andreion1ca andreion1ca commented Aug 27, 2026 •

Copy link
Copy Markdown

Fixes #13.

Fixes the crash reported in alttester/AltTester-Server#206.

releaseCommonResources() closed _pongReceived and _receivingExited out from under threads that were already inside Reset/Set/WaitOne on them — a use-after-dispose of an OS wait handle.

On the server, that window is opened by the session sweep: Sweep() → InactiveIDs → broadping() pings live connections, and a connection being torn down at that moment releases the handles underneath the ping. Both AccessViolationException reports in alttester/AltTester-Server#206 land on the first sweep tick, 60s after startup, each one shortly after a rejected connection ("Too many apps connected.") was closed.

Changes:

  • releaseCommonResources drops the references without closing the handles. Every reader snapshots the field into a local first, so a handle still in use stays valid until that last reference goes away and SafeWaitHandle's finalizer releases it. A lock was the alternative, but it can't cover both fields: _pongReceived is only touched under _forPing, while _receivingExited is used by the receive loop and by closeHandshake, neither of which holds that lock.
  • Snapshot the field at the three readers that didn't: client closeHandshake, processPongFrame (which had a catch (NullReferenceException) papering over exactly this), and server closeHandshake (which read _receivingExited twice — null check, then wait).
  • Sweep() clears _sweeping in a finally, so a throw can't leave the flag stuck at true and silently disable the sweep for the rest of the process's life.

Verified by building the netstandard2.0 DLL and running AltTester-Server's test suite against it: 133/133 passing. That exercises the normal connect/close paths, not the narrow ping-vs-teardown window itself.

Paired with alttester/AltTester-Server's own fix, which turns the sweep off (KeepClean = false) — a pong that doesn't arrive within WaitTime (1s) is treated as a dead session, which is what a backgrounded mobile app or a paused editor looks like. This PR is the hardening for every other consumer of the library.

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.

Closing a connection can dispose the ping/receive wait handles while another thread is using them

1 participant