Repository navigation
fix(chat): stop a reconnect reopening the event stream in the background - #1510
Merged
Merged
Conversation
Bugsnag 6ab0133d58142669ea781ea5 has the bidirectional event stream reopening 6.5 seconds after `MainActivity#onStop()` and going on to sync the feed, with the app in the background. `TokenCoordinator` cancelled its reserve state stream a second after the same `onStop`. `ChatCoordinator.onStop` already closes the stream and stops the heartbeat, so the reopen comes from a second path. The network-reconnect collector is launched on `scope`, which spans the logged-in session rather than the foreground, and a reconnect landing after `onStop` re-synced the feed and reopened the stream behind the close. It now reads a `foregrounded` flag maintained by `onStart`/`onStop`, checked after the debounce so the answer is the lifecycle state the reconnect would actually act in. A reconnect that lands while backgrounded is dropped rather than deferred, since `onStart` already syncs and reopens. The same breadcrumbs show "Network connected, re-syncing chat feed" firing every few seconds in the foreground, each one skipping an already-connected stream. `distinctUntilChanged` was applied to the whole `NetworkState`, which carries signal strength and connection type alongside `connected`, and a cellular radio reports those every few seconds. Every signal wobble passed through as a fresh reconnect on a connection that never dropped. Mapping to `connected` before de-duplicating collapses them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bugsnag
6ab0133d58142669ea781ea5(2026.9.1, versionCode 4503) has the bidirectional event stream reopening 6.5 seconds afterMainActivity#onStop()and going on to sync the feed, with the app in the background.TokenCoordinatorcancelled its reserve state stream a second after the sameonStop:ChatCoordinator.onStopalready does whatTokenCoordinatordoes: it stops the heartbeat and callseventStreamDelegate.close(). Nothing here is a deliberate background channel kept alive for notification delivery, so the reopen is a second path that gets around the teardown rather than an intended exception to it.That path is the network-reconnect collector. It is launched on
scope, which spans the logged-in session rather than the foreground, so a reconnect landing afteronStopre-synced the feed and reopened the stream behind the close. It now reads aforegroundedflag maintained byonStart/onStop, checked after the debounce so the answer is the lifecycle state the reconnect would actually act in rather than the one it arrived in. A reconnect that lands while backgrounded is dropped rather than deferred:onStartalready syncs and reopens, so nothing is lost by ignoring it.The foreground churn was a second bug
The same breadcrumbs show
Network connected, re-syncing chat feedfiring at 17:08:54, :56, 17:09:00, :03 and :11, each one followed byEvent stream already connected, skipping open.distinctUntilChangedwas applied to the wholeNetworkState, which carries signal strength and connection type alongsideconnected, and a cellular radio reports those every few seconds. Every signal wobble therefore passed through as a fresh reconnect and re-synced the feed on a connection that never dropped. Mapping toconnectedbefore de-duplicating collapses them.Tests
ChatCoordinatorLifecycleStreamTestcovers the reconnect in the background, the reconnect in the foreground as a control, and the signal wobbles. Two things about this class are worth knowing before editing it, both of which cost real time here:advanceUntilIdle()never returns, becausestartHeartbeatkeeps an unbounded timed loop scheduled. UserunCurrent()andadvanceTimeBy(), as the other chat coordinator tests do.coordinator.teardown(). An assertion that throws first skips the teardown, andrunTest's cleanup drain then spins on virtual time instead of reporting the failure.The wobbles have to be spaced with
advanceTimeByrather than applied back to back.NetworkConnectivityListener.stateis a conflatedStateFlow, so wobbles set in the same instant collapse before the collector sees them and the churn under test never happens.No rendering or UI code is touched.