Fix race condition in plugin resolution during disconnect#6269
Fix race condition in plugin resolution during disconnect#6269
Conversation
PR checklist ✅All required conditions are satisfied:
🎉 Great job! This PR is ready for review. |
SDK Size Comparison 📏
|
WalkthroughThe PR introduces thread synchronization around plugin operations in ChatClient using a new private lock, refactors plugin dependency resolution into a dedicated helper function that enforces initialization state checks, and adds a test case verifying correct error handling when disconnect races occur during dependency resolution. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ncy resolution races with disconnection.
e9b5224 to
3e8b87a
Compare
|
| throw IllegalStateException("ChatClient::connectUser() must be called before resolving any dependency") | ||
| } | ||
| val resolver = plugins.find { plugin -> | ||
| val resolver = currentPlugins.find { plugin -> |
There was a problem hiding this comment.
Just a quick question: Do we have some potential implications if we reach this point in the code - but disconnect() was already called? (fully understand that this is an extreme edge case, but I am just thinking of possible side effects)
Also, is it possible that currentPlugins is resolved as emptyList() before the awaitInitializationState()?



Goal
Fix a race condition where
disconnect()clears the plugins list between theinitializationStatecheck and theplugins.find()call inresolvePluginDependency(), causingIllegalStateException: Plugin was not foundcrashes#6156
Implementation
@Volatileto thepluginsproperty to ensure cross-thread visibility of writes.resolvePluginDependency(): snapshot plugins before checkinginitializationState. SincedisconnectUserSuspend()setsNOT_INITIALIZEDbefore clearing plugins, if the snapshot captures populated plugins and then we seeCOMPLETE, the snapshot is guaranteed valid.🎨 UI Changes
No UI changes.
Testing
DependencyResolverTest— a new test (Should resolve dependency when plugins are cleared during resolution) uses aDisconnectSimulatingStateFlowto deterministically simulatedisconnect()clearing plugins mid-resolution. It verifies the dependency resolves successfully because the plugins snapshot was taken before the state was read.Note
I can't reproduce using the sample app.
Summary by CodeRabbit