Skip to content

Fix WebSocket sends, lost requests and client lifetime - #5

Merged
dezinezync merged 2 commits into
DZNS:mainfrom
Jatin5120:fix/websocket-send-and-lifecycle
Oct 14, 2026
Merged

dezinezync merged 2 commits into
DZNS:mainfrom
Jatin5120:fix/websocket-send-and-lifecycle

Conversation

@Jatin5120

@Jatin5120 Jatin5120 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #4.

Problem

  • A send waiting for the connection returned successfully when disconnect() was called or the retry limit was reached, even though nothing was sent.
  • When the connection dropped, requests waiting for a reply were never resumed.
  • Cancelling a send while it waited for the connection did nothing. Cancelling send(message:responseType:) could reach the actor before the request was registered, so the request still returned successfully. This is why testTaskCancellation was flaky.
  • The receive and ping loops held the client strongly, so a client the app stopped using stayed in memory with its connection open.
  • ReconnectionPolicy rejected valid settings such as (2, 5, 8) and (2, 30, 60).

Changes

All changes are in DZWebSocketClient.swift and ReconnectionPolicy.swift.

  • A send waiting for the connection now throws when the client disconnects or stops retrying.
  • When the connection drops, every request waiting for a reply fails with the connection's error.
  • A cancelled send stops waiting and removes its own request. Only one of reply, error, cancel or disconnect can finish a request.
  • The receive, ping and retry loops hold the client weakly. When the client is released, it stops its work and closes the socket.
  • A new initializer adds pingInterval (1 second to 1 day, or nil for no pings). The existing initializer still pings every 10 seconds.
  • ReconnectionPolicy now checks that delays are positive and finite and that maximumDelay >= baseDelay. The delay formulas are unchanged. It is also Sendable.

Compatibility

  • There are no breaking API changes; swift package diagnose-api-breaking-changes main reports none.
  • Sending still connects automatically when the client is disconnected.
  • A send that used to return without sending now throws. This is the intended fix.

Testing

  • All 47 existing tests pass, with no test changes, on macOS and on the iOS 26.5 simulator. testTaskCancellation passed 40/40 runs.
  • I compared main and this branch using local test servers. These scenarios fail on main and pass with this change:
    • disconnecting or reaching the retry limit while a send is waiting
    • a connection drop while requests are waiting for replies
    • cancelling a send while it waits for the connection
    • releasing a client that is connected, connecting or waiting to retry
  • It also works in our app.

Known issues, for a follow-up PR

  • A very large maximumDelay can overflow Duration.
  • listen() can miss messages that arrive before its handler is registered.
  • There is no pong deadline.

🤖 Generated with Claude Code

Sends could report success without sending anything, requests could
wait forever after a drop, and an abandoned client stayed in memory.

- Throw from waiting sends when the client disconnects or gives up
- Fail requests that are still waiting for a reply when the connection drops
- Let a cancelled send stop waiting and clean up after itself
- Deliver state changes in order
- Release an abandoned client and close its connection
- Add an optional ping interval (nil turns pings off)
- Accept retry policies such as (2, 5, 8) that the old check rejected

The existing initializer, default ping interval and auto-connect on
send are unchanged. No public API breaks. All existing tests pass, and
testTaskCancellation no longer fails intermittently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread Sources/DZNetworking/Core/DZWebSocketClient.swift
Comment thread Sources/DZNetworking/Core/DZWebSocketClient.swift Outdated
Remove the chain that ran onStateChange calls in order, so state
changes are delivered as before. Write the else-break guards in the
receive and ping loops over multiple lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dezinezync
dezinezync merged commit 6d06657 into DZNS:main Oct 14, 2026
3 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