Repository navigation
Description
Background
PR #4202 fixed #3976 (AppSync subscriptions disconnecting after 2-3 minutes / zombie subscriptions after iOS recycles the TCP route on the same network). However, #4202 was found to regress #4220 — GraphQL subscriptions get stuck in connecting after repeated iOS background/foreground transitions.
@akiramur confirmed v2.56.0 (pre-#4202) does not reproduce #4220. The regression was traced directly to the WebSocket recycle path introduced in #4202.
PR #4228 reverts #4202 to unblock the #4220 regression. That revert also removes the #3976 fix, so #3976 will reappear and must be re-addressed here with a non-regressing approach.
What #4202 did (and why it regressed)
Two layered changes:
1. WebSocketClient.onNetworkStateChange — new (.online, .online) recycle case
case (.online, .online):
// A second .satisfied emission while already online ⇒ underlying path
// was swapped; existing URLSessionWebSocketTask is bound to a stale route.
guard connection?.state == .running else { break }
connection?.cancel(with: .invalid, reason: nil)
subject.send(.disconnected(.invalid, nil))
await createConnectionAndRead()2. AppSyncRealTimeSubscription.prepareForResubscribe() + call in resumeExistingSubscriptions()
func prepareForResubscribe() {
state.send(.none) // reset local state so subscribe() re-sends .start after reconnect
}Suspected regression mechanism: On repeated background/foreground transitions, NWPathMonitor emits additional .satisfied events that get mapped to (.online, .online). Each one cancels and re-establishes the connection. Under rapid lifecycle churn these recycles appear to interleave/stack, leaving the client stuck in connecting (never completing the reconnect handshake). See #4220 for the repro repo (feature/multi-subscription-repro branch).
Requirements for the re-fix
- Still resolve AppSync Subscription Disconnects After 2-3 Minutes (NSPOSIXErrorDomain Code 57) #3976: detect a genuinely stale/zombie WebSocket after a same-network TCP route swap and recover it.
- Do not regress GraphQL subscriptions can get stuck in connecting after repeated iOS background/foreground transitions #4220: rapid/repeated background↔foreground transitions must not leave subscriptions stuck in
connecting. - Ensure resubscription after a real reconnect resends
.start(theprepareForResubscribeconcern) without firing on spurious path events.
Ideas to explore
- Debounce / coalesce
(.online, .online)events so lifecycle churn doesn't trigger repeated recycles. - Liveness probe instead of blind recycle — only recycle when the existing task is actually dead (e.g. ping/pong timeout or detected read/write failure) rather than on every duplicate
.satisfied. - Guard against overlapping recycles — ensure a recycle in flight can't be re-triggered, and that the reconnect completes (or is cancelled cleanly) before another starts.
- Reconcile with app-lifecycle handling so scenePhase transitions don't double-count as path changes.
References
- Regression: GraphQL subscriptions can get stuck in connecting after repeated iOS background/foreground transitions #4220 (open)
- Original bug: AppSync Subscription Disconnects After 2-3 Minutes (NSPOSIXErrorDomain Code 57) #3976 (closed by fix(api): recycle WebSocket and resubscribe on same-online network path change #4202)
- Original fix: fix(api): recycle WebSocket and resubscribe on same-online network path change #4202 (commit
be0a21462) - Revert: Revert "fix(api): recycle WebSocket and resubscribe on same-online network path change (#4202)" #4228
Testing
Validate against the #4220 repro repo (multi-subscription background/foreground churn) and the #3976 scenario (idle subscription surviving a same-network route swap past the ~2-3 min mark) before merging.
Activity