You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix a race condition that causes IllegalStateException: Plugin 'StatePlugin' was not found crash when globalStateFlow is being collected during user disconnect.
The Problem:
The globalStateFlow extension filters for InitializationState.COMPLETE before accessing globalState:
Set initializationState to NOT_INITIALIZEDbefore clearing plugins:
notifications.onLogout()
// Set initializationState to NOT_INITIALIZED BEFORE clearing plugins to prevent race condition.// This ensures globalStateFlow's filter { it == COMPLETE } won't pass while plugins is being cleared.
mutableClientState.setInitializationState(InitializationState.NOT_INITIALIZED)
plugins.forEach { it.onUserDisconnected() }
plugins = emptyList()
This ensures the flow's .filter { it == COMPLETE } stops emitting before plugins becomes empty.
🎨 UI Changes
No UI changes.
🧪 Testing
This issue was reported by a customer and is not reproducible with the sample apps under normal conditions due to timing dependencies.
To simulate the race condition:
Add a delay in resolvePluginDependency() after the initialization state check:
internalinlinefun <reifiedP : DependencyResolver, reifiedT : Any> resolvePluginDependency(): T {
val initState = awaitInitializationState(RESOLVE_DEPENDENCY_TIMEOUT)
if (initState !=InitializationState.COMPLETE) {
throwIllegalStateException("...")
}
Thread.sleep(500) // Add this to widen the race windowval resolver = plugins.find { it isP } ?:throwIllegalStateException("...")
// ...
}
Trigger a disconnect shortly after connect completes while globalStateFlow is being collected:
One minor question: are there any onUserDisconnected() plugin callbacks that depend on initializationState == COMPLETE? If so, they now see NOT_INITIALIZED. Probably fine since they're shutting down anyway, but wanted to flag it.
One minor question: are there any onUserDisconnected() plugin callbacks that depend on initializationState == COMPLETE? If so, they now see NOT_INITIALIZED. Probably fine since they're shutting down anyway, but wanted to flag it.
No, the onUserDisconnected() implementations don't depend on the initializationState at all. They are all empty actually, only the internal ThrottlingPlugin has logic in it, but not one that depends on `initializationState
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
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.
🎯 Goal
Fix a race condition that causes
IllegalStateException: Plugin 'StatePlugin' was not foundcrash whenglobalStateFlowis being collected during user disconnect.The Problem:
The
globalStateFlowextension filters forInitializationState.COMPLETEbefore accessingglobalState:In
disconnectUserSuspend(), the ordering is:This created a race window where:
initializationState == COMPLETE(not yet cleared)plugins == emptyList()(already cleared)Any
globalStateFlowcollection could potentially pass theinitializationState == COMPLETEcheck, but try to access an emptypluginsfield.Resolves: https://linear.app/stream/issue/AND-1038
🛠 Implementation details
Set
initializationStatetoNOT_INITIALIZEDbefore clearing plugins:This ensures the flow's
.filter { it == COMPLETE }stops emitting beforepluginsbecomes empty.🎨 UI Changes
No UI changes.
🧪 Testing
This issue was reported by a customer and is not reproducible with the sample apps under normal conditions due to timing dependencies.
To simulate the race condition:
Add a delay in
resolvePluginDependency()after the initialization state check:Trigger a disconnect shortly after connect completes while
globalStateFlowis being collected:Without the fix: crash with
IllegalStateException: Plugin 'StatePlugin' was not foundWith the fix: no crash, flow stops emitting gracefully
Summary by CodeRabbit