Skip to content

Add per-player logging and fix connection loss detection#7

Merged
chrisuthe merged 3 commits intochrisuthe:masterfrom
scyto:fix/per-player-logging-and-ack-cleanup
Jan 29, 2026
Merged

Add per-player logging and fix connection loss detection#7
chrisuthe merged 3 commits intochrisuthe:masterfrom
scyto:fix/per-player-logging-and-ack-cleanup

Conversation

@scyto
Copy link
Copy Markdown
Contributor

@scyto scyto commented Jan 26, 2026

Summary

Per-player logging & ACK cleanup

  • Add [PlayerName] prefix to all server message log lines (group/update, server/state, server/command) for multi-player diagnostics
  • Log group ID and name changes in HandleGroupUpdate to help diagnose grouping issues
  • Simplify SendPlayerStateAckAsync: remove redundant try/catch (already handled by SafeFireAndForget)

Connection loss detection & reconnection improvements

  • Trigger HandleConnectionLostAsync from failed sends — When SendMessageAsync/SendBinaryAsync detects WebSocket.State != Open while the connection state is still Connected or Handshaking, fire-and-forget HandleConnectionLostAsync(). Fixes players stuck in "Connected" state after server disappears (the receive loop blocks forever on ReceiveAsync while TCP hasn't timed out).
  • Extract handshake into reusable methodsSendHandshakeAsync() and CreateClientHelloMessage() extracted from ConnectAsync() for reuse in reconnection handshakes.
  • Add reconnection re-handshakePerformReconnectHandshakeAsync() sends a fresh ClientHello when the WebSocket reconnects (entering Handshaking state while _isReconnecting is true). Resets clock synchronizer for the new connection.
  • Stop time sync on Reconnecting state — Prevents "WebSocket is not connected" spam from the time sync loop between disconnection and full teardown.
  • Fix ConnectInternalAsync retry state — Don't transition through Disconnected during AutoReconnect retries. Let the caller (ConnectAsync / TryReconnectAsync) handle failure, avoiding spurious state change events.

Dependency

Required together with chrisuthe/Multi-SendSpin-Player-Container#123 — The app disables SDK AutoReconnect and manages its own reconnection with mDNS discovery, but relies on the SendMessageAsync fix here to detect connection loss promptly. Both PRs must be merged together.

Test plan

  • Tested with 4 SDK player instances connected to Music Assistant
  • Verified log output includes [PlayerName] on all server message handlers
  • Verified group ID change detection logs correctly
  • Start app with players connected to a SendSpin server
  • Kill the server process — verify players transition from Connected → Stopped within ~10 seconds
  • Restart the server — verify players reconnect via mDNS-driven reconnection
  • Verify no "Failed to send time sync burst" warning spam after disconnection

🤖 Generated with Claude Code

- Add [PlayerName] prefix to all server message log lines
  (group/update, server/state, server/command) for multi-player
  diagnostics
- Log group ID and name changes in HandleGroupUpdate to help
  diagnose grouping issues
- Simplify SendPlayerStateAckAsync: remove redundant try/catch
  wrapper (exceptions already handled by SafeFireAndForget)
- Clean up comments to match actual protocol behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@scyto
Copy link
Copy Markdown
Contributor Author

scyto commented Jan 26, 2026

these seemed like useful things to keep and have in the SDK?

Trigger HandleConnectionLostAsync from failed sends when WebSocket is
closed but receive loop hasn't detected it yet. This fixes players
getting stuck in Connected state after server disappears.

Also: extract handshake into reusable SendHandshakeAsync, add
re-handshake on reconnection, stop time sync on Reconnecting state,
and fix ConnectInternalAsync to not transition through Disconnected
during AutoReconnect retries.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@scyto scyto changed the title Add per-player logging and simplify ACK handling Add per-player logging and fix connection loss detection Jan 27, 2026
This commit addresses critical thread-safety issues identified during code review
that could cause duplicate reconnection attempts and unpredictable behavior,
especially in multi-player scenarios like multiSendSpin.

Race condition fixes:

- Remove _isReconnecting field and use e.OldState instead
  The _isReconnecting field was accessed without synchronization from multiple
  threads (receive loop, main thread, reconnect loop). Now we check
  e.OldState == ConnectionState.Reconnecting directly in the event args,
  which is immutable and thread-safe.

- Add Interlocked guard to HandleConnectionLostAsync
  Both send failure detection and the receive loop could trigger
  HandleConnectionLostAsync simultaneously when a connection dies.
  The atomic guard ensures only the first caller proceeds, preventing
  duplicate reconnection attempts and state machine corruption.

Additional improvements:

- Add CancellationToken support to PerformReconnectHandshakeAsync
  Allows graceful cancellation of reconnection handshakes during shutdown.

- Remove duplicate _clockSynchronizer.Reset() call
  HandleServerHello already resets the clock synchronizer when handshake
  completes, making the reset in PerformReconnectHandshakeAsync redundant.

- Revert routine group/update log level to Debug
  Keep routine state logs at Debug level per CLAUDE.md guidelines.
  Only actual state changes (group ID/name changes) remain at Information.

- Use SafeFireAndForget for TimeSyncLoopAsync
  Ensures any exceptions from the time sync loop are properly logged
  instead of being silently discarded with a bare discard assignment.
@chrisuthe chrisuthe merged commit b1afae5 into chrisuthe:master Jan 29, 2026
2 checks passed
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