-
Notifications
You must be signed in to change notification settings - Fork 75
FFL-1621 Fix: set currentState in a blocking call #3068
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
|
🎯 Code Coverage 🔗 Commit SHA: 869276e | Docs | Datadog PR Page | Was this helpful? Give us feedback! |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #3068 +/- ##
===========================================
- Coverage 71.32% 71.30% -0.02%
===========================================
Files 880 880
Lines 32360 32365 +5
Branches 5457 5457
===========================================
- Hits 23079 23076 -3
- Misses 7733 7746 +13
+ Partials 1548 1543 -5
🚀 New features to boost your workflow:
|
| currentState = newState | ||
| subscription.notifyListeners { | ||
| onStateChanged(newState) | ||
| executorService.executeSafe( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enqueue safe executions of the listeners instead of safe execution of the queue. Now listeners cannot not crash the rest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we move synchronized protection inside DDCoreSubscriptionImpl?, it seems to me the job of DDCoreSubscriptionImpl to guarantee the thread-safety no matter where it is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DDCoreSubscription ensures atomicity wrt modifying the subscriber list during a notification loop. Our synchronized here is layering atomicity linking the notification mechanics of DDCoreSubscription with mutation of the currentState. This coupling is a specific use case of the DDCoreSubscription or another layer on top.
DDCoreSubscription is still susceptible to listeners crashing the notification loop. We could put a try-catch there, but it might be better to keep the onus on the caller (which easy to do via the block argument passed to notifyListeners.
wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need do executorService.executeSafe instead of simply calling onStateChanged(newState)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
originally, it was to ensure fairness of state changes - which we've actually lost here with the synchronized block (switched to a ReentrantLock now)
The executor lets us run the subscription consumer off-thread and not block the caller to setEvaluationContext on potential long running consumers. Would it be better to leave the thread mgmt to the caller here? Would the caller need to run their consumer specifically in the UI thread (or some other specific thread?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to leave the thread mgmt to the caller here?
I would say yes, it is up to the caller to do it unless we are picky about listener performance and for us it is critical for it to be fast. And then we can wrap simply onStateChanged calls in try-catch if we want to continue work after consumer threw exception. But then this exception will be handled, so maybe it is actually better to let it propagate and crash the app (since it is exception from the customer code).
...dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/FlagsStateManager.kt
Outdated
Show resolved
Hide resolved
typotter
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ambushwork ptal.
...dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/FlagsStateManager.kt
Outdated
Show resolved
Hide resolved
| currentState = newState | ||
| subscription.notifyListeners { | ||
| onStateChanged(newState) | ||
| executorService.executeSafe( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DDCoreSubscription ensures atomicity wrt modifying the subscriber list during a notification loop. Our synchronized here is layering atomicity linking the notification mechanics of DDCoreSubscription with mutation of the currentState. This coupling is a specific use case of the DDCoreSubscription or another layer on top.
DDCoreSubscription is still susceptible to listeners crashing the notification loop. We could put a try-catch there, but it might be better to keep the onus on the caller (which easy to do via the block argument passed to notifyListeners.
wdyt?
| * The fair parameter ensures that threads acquire the lock in the order they requested it. | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
threads acquire the lock in the order they requested it
Does it really matter actually? I'm wondering if it is better to use ReadWriteLock since probably the number of read operations will dominate over the number of write operations, or is it a wrong assumption?
| currentState = newState | ||
| subscription.notifyListeners { | ||
| onStateChanged(newState) | ||
| executorService.executeSafe( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to leave the thread mgmt to the caller here?
I would say yes, it is up to the caller to do it unless we are picky about listener performance and for us it is critical for it to be fast. And then we can wrap simply onStateChanged calls in try-catch if we want to continue work after consumer threw exception. But then this exception will be handled, so maybe it is actually better to let it propagate and crash the app (since it is exception from the customer code).
What does this PR do?
This PR changes the way that the current state's access and mutation is handled.
executeSafeto avoid crashing the pool before the rest of the listeners are notified.updatetStateand calls toexecuteSafeare made in a sync blockaddListenernow does work inside asynchronized(currentState)block as well to ensure atomic registration+currentState notifcationaddListeneralso runs in anexecuteSafeblockMotivation
As noted in #2998, the
currentStatewas updated synchronously erroneously and prevented other components from realizing that state directly after updatingAdditional Notes
Anything else we should know when reviewing?
Review checklist (to be filled by reviewers)