RUM-2875 Introduce the FeatureContextUpdateLitener API#1829
Conversation
4dd25fd to
0632729
Compare
| ) : FeatureScope { | ||
|
|
||
| internal val initialized = AtomicBoolean(false) | ||
| internal val contextUpdateListeners: ConcurrentHashMap<FeatureContextUpdateListener, Any?> = |
There was a problem hiding this comment.
not sure if I should use a ConcurrentHashMap here or just a Set and make a copy when notifying the listeners to make sure it is concurrent
1dc0481 to
b6e49b2
Compare
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## feature/sr-web-view-support #1829 +/- ##
===============================================================
+ Coverage 83.75% 83.78% +0.03%
===============================================================
Files 472 472
Lines 16598 16623 +25
Branches 2501 2504 +3
===============================================================
+ Hits 13900 13926 +26
- Misses 2004 2010 +6
+ Partials 694 687 -7
|
| featureName: String, | ||
| updateCallback: (context: MutableMap<String, Any?>) -> Unit |
There was a problem hiding this comment.
better to revert, not part of the actual code change.
| internal val contextUpdateListeners: ConcurrentHashMap<FeatureContextUpdateListener, String> = | ||
| ConcurrentHashMap() |
There was a problem hiding this comment.
Seems like it is used like a Set, and value is actually never used. In this case it can be just Collections.newSetFromMap(ConcurrentHashMap()) and placeholder for he value is not needed.
There was a problem hiding this comment.
yes, wanted to use the same thing but not available for API 21 :(
There was a problem hiding this comment.
There was a problem hiding this comment.
ah yes...that's not what I tested, I tested the Collections.newKeySet which is a new method, but indeed that one would work also. Tks !!
| } | ||
|
|
||
| internal fun setContextUpdateListener(listener: FeatureContextUpdateListener) { | ||
| synchronized(contextUpdateListeners) { |
There was a problem hiding this comment.
Do we actually need ConcurrentHashMap then if each access that modifies the state of contextUpdateListeners is backed by synchronized?
There was a problem hiding this comment.
yes we need it as I did not want to synchronize when we notify the listeners. When I travers them for notification another thread might remove or add a new one and it will get into ConcurrentModificationException
| internalLogger.log( | ||
| InternalLogger.Level.WARN, | ||
| InternalLogger.Target.USER, | ||
| { CONTEXT_UPDATE_LISTENER_ALREADY_EXISTS.format(Locale.US, wrappedFeature.name) } |
There was a problem hiding this comment.
But we don't actually overwrite anything, do we? Both key and value stay the same.
There was a problem hiding this comment.
yes you are right...unless you are providing a different instance of the same listener. The equals method will say true and we are replacing the previous instance with the new one.
| updateCallback(mutableContext) | ||
| it.setFeatureContext(featureName, mutableContext) | ||
| feature.notifyContextUpdated(mutableContext) | ||
| mutableContext |
| * @param event the updated context | ||
| */ | ||
| @AnyThread | ||
| fun onContextUpdate(event: Map<String, Any?>) |
There was a problem hiding this comment.
In this case, generally speaking, we cannot reuse the same listener object for context updates of the different features.
What if we add a feature name as a first argument? That way we could re-use the same object.
Not an ask or blocker for me, just some brainstorming.
There was a problem hiding this comment.
oh...yeah, it might be better indeed. Let's do this.
48ecd0c to
c54cb2d
Compare
4ba06aa to
0011b35
Compare
| // notify all the other features | ||
| features.filter { it.key != featureName } | ||
| .forEach { (_, feature) -> | ||
| feature.notifyContextUpdated(featureName, mutableContext) |
There was a problem hiding this comment.
even though the type on the receiver side is Map here, maybe it is worth to make a copy as a safe-guard?
| feature.notifyContextUpdated(featureName, mutableContext) | |
| feature.notifyContextUpdated(featureName, mutableContext.toMap()) |
|
|
||
| companion object { | ||
| internal const val CONTEXT_UPDATE_LISTENER_ALREADY_EXISTS = | ||
| "Feature \"%s\" already has this listener registered, overwriting it." |
There was a problem hiding this comment.
Still there is no overwrite, it is already there. Maybe we can drop the part about overwriting?
| } | ||
|
|
||
| @Test | ||
| fun `𝕄 remove context update listener 𝕎 setContextUpdateListener()`( |
There was a problem hiding this comment.
| fun `𝕄 remove context update listener 𝕎 setContextUpdateListener()`( | |
| fun `𝕄 remove context update listener 𝕎 removeContextUpdateReceiver()`( |
| } | ||
|
|
||
| @Test | ||
| fun `M update registered listeners W notifyContextUpdateListener()`(forge: Forge) { |
There was a problem hiding this comment.
| fun `M update registered listeners W notifyContextUpdateListener()`(forge: Forge) { | |
| fun `M update registered listeners W notifyContextUpdated()`(forge: Forge) { |
| val countDownLatch = CountDownLatch(mockListeners.size) | ||
| mockListeners.forEach { | ||
| Thread { | ||
| testedFeature.setContextUpdateListener(it) | ||
| countDownLatch.countDown() | ||
| }.start() | ||
| } | ||
|
|
||
| // Then | ||
| countDownLatch.await(5, TimeUnit.SECONDS) |
There was a problem hiding this comment.
Alternatively this can be written without CountDownLatch:
| val countDownLatch = CountDownLatch(mockListeners.size) | |
| mockListeners.forEach { | |
| Thread { | |
| testedFeature.setContextUpdateListener(it) | |
| countDownLatch.countDown() | |
| }.start() | |
| } | |
| // Then | |
| countDownLatch.await(5, TimeUnit.SECONDS) | |
| mockListeners.map { | |
| Thread { | |
| testedFeature.setContextUpdateListener(it) | |
| countDownLatch.countDown() | |
| }.apply { start() } | |
| }.forEach { it.join(5, TimeUnit.SECONDS) } | |
| // Then |
but not a big deal
0011b35 to
6869dff
Compare
6869dff to
474b8b0
Compare
What does this PR do?
A brief description of the change being made with this pull request.
Motivation
What inspired you to submit this pull request?
Additional Notes
Anything else we should know when reviewing?
Review checklist (to be filled by reviewers)