Skip to content

feat: expose IsAlive on WebSocketBehavior - #8

Closed
andreion1ca wants to merge 3 commits into
masterfrom
fix/181-expose-isalive-on-websocketbehavior
Closed

feat: expose IsAlive on WebSocketBehavior#8
andreion1ca wants to merge 3 commits into
masterfrom
fix/181-expose-isalive-on-websocketbehavior

Conversation

@andreion1ca

Copy link
Copy Markdown

No description provided.

AltTester-Server's handler base re-exposes the session's liveness
(AltWebSocketHandler.IsAlive). The vendored server build it currently
ships (altserverwebsocket-sharp.dll) has this property, this library does
not, so the server cannot compile against it.

That was the last API gap keeping AltTester-Server on the vendored build,
whose WebSocket.open() still dispatches the queued message via
Delegate.BeginInvoke. BeginInvoke throws PlatformNotSupportedException on
.NET Core, which kills any connection whose peer speaks before OnOpen
returns - AltTester-Server issue sta#181. This library already dispatches via
Task.Factory.StartNew under NET_CORE, so moving the server onto it fixes
the issue.
receiveRequest parked in TcpListener.AcceptTcpClient. On Unix a blocking
accept holds a reference on the listener's socket handle, and
TcpListener.Stop() -> Socket.Dispose() spin-waits for that reference
without interrupting the accept, so any server that had accepted at least
one connection hung forever on shutdown.

Poll the listening socket with a 250ms timeout instead and re-check the
server state each round, which keeps the accept unblocked and lets the
loop retire on its own.
Both server close paths released the connection and only then ran the
closing handshake, so the close frame was written to a stream that was
already gone. The peer never saw the code and reason and reported an
abnormal 1006 close instead - which loses every application close code the
server relies on to explain itself (unsupported SDK, duplicate app name,
connection limits).

Run the handshake first, then release the transport and mark the socket
closed.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves server-side WebSocket shutdown behavior and exposes connection-liveness information to service implementations via WebSocketBehavior.

Changes:

  • Reorders parts of the server close sequence so the close frame/handshake is attempted before tearing down server transport resources.
  • Adds a polling-based accept loop in WebSocketServer to avoid shutdown deadlocks on Unix and to bound shutdown wait time.
  • Exposes IsAlive as a protected property on WebSocketBehavior for session implementations.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
websocket-sharp/websocket-sharp/ServerWebSocket.cs Adjusts server close sequencing to perform handshake/close-frame send before resource teardown.
websocket-sharp/websocket-sharp/Server/WebSocketServer.cs Adds a timed poll before AcceptTcpClient() to make shutdown responsive and avoid Unix accept/stop deadlock.
websocket-sharp/websocket-sharp/Server/WebSocketBehavior.cs Adds protected bool IsAlive to allow derived behaviors to check session connection liveness.
Suppressed comments (1)

websocket-sharp/websocket-sharp/ServerWebSocket.cs:581

  • SendClosingBytes() disposes receivingExitedForLater before ReleaseCommonResources(false) clears receivingExitedEvent. That leaves a race where the receive loop can still attempt receivingExitedEvent?.Set() on a disposed event, potentially throwing ObjectDisposedException. Deferring disposal until after the field is cleared (or guarding Set()) avoids this.
            // Called outside the lock, and before the transport is torn down: the closing frame
            // has to reach a live stream, otherwise the peer sees an abnormal disconnect (1006)
            // instead of the code and reason we are closing with.
            var wasClean = SendClosingBytes();


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +456 to 460
// Called outside the lock, and before the transport is torn down: the closing
// handshake has to write the close frame to a live stream, otherwise the peer sees an
// abnormal disconnect (1006) instead of the code and reason we are closing with.
var wasClean = DoClosingHandshake();

Comment on lines +925 to +933
// Poll with a timeout rather than parking in AcceptTcpClient: on Unix a
// blocking accept holds a reference on the listener's socket handle, and
// TcpListener.Stop() -> Socket.Dispose() spin-waits for that reference to be
// released without interrupting the accept. A server that has accepted at
// least one connection therefore deadlocks on shutdown. Polling keeps the
// accept unblocked and lets this loop observe the shutdown itself.
if (!_listener.Server.Poll(_acceptPollTimeout, SelectMode.SelectRead))
continue;

@andreion1ca
andreion1ca deleted the fix/181-expose-isalive-on-websocketbehavior branch August 13, 2026 06:03
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