Fix SI-7943 -- make `TrieMap.getOrElseUpdate` atomic. #4319
Conversation
This is what I get with the vanilla scala> Test.main(Array())
|
Updated exception message. |
* Note: This method will invoke op at most once. | ||
* However, `op` may be invoked without the result being added to the map if | ||
* a concurrent process is also trying to add a value corresponding to the | ||
* same key k. |
Ichoran
Feb 18, 2015
Contributor
Very nitpicky, but k should be in backquotes.
Very nitpicky, but k should be in backquotes.
@@ -178,6 +178,10 @@ trait MapLike[A, B, +This <: MapLike[A, B, This] with Map[A, B]] | |||
* | |||
* Otherwise, computes value from given expression `op`, stores with key | |||
* in map and returns that value. | |||
* | |||
* Concurrent map implementations may evaluate the expression `op` | |||
* multiple times. |
Ichoran
Feb 18, 2015
Contributor
Do we want to add, "or may evaluate op
without inserting the result"?
Do we want to add, "or may evaluate op
without inserting the result"?
LGTM but could perhaps be a bit better with tiny doc changes. |
Override `getOrElseUpdate` method in `TrieMap`. The signature and contract of this method corresponds closely to the `computeIfAbsent` from `java.util.concurrent.ConcurrentMap`. Eventually, `computeIfAbsent` should be added to `scala.collection.concurrent.Map`. Add tests. Review by @Ichoran
Docs fixed. |
LGTM without qualifications! |
Thanks, @axel22! Good to see you pop up here so regularly :-) |
Glad to be of help! :) |
Fix SI-7943 -- make `TrieMap.getOrElseUpdate` atomic.
Not sure if this is the same as |
Description says "closely corresponds", not "the same as". Also, a caller of |
True, it can be worked around. This just threw us for a loop at work because we were still getting exceptions. |
Can you describe say a bit more about what the exception was? Was it with this change, or with an earlier Scala release? |
It was with this change. We had code which should only be called once and would throw an exception if it was called twice. Its result was to be stored in the |
Was the |
Yes, indeed it was. You provided this caveat in the documentation, we just failed to heed it because we assumed it would work like |
Well, I'm thinking now whether we should have the execute-exactly-once semantics on |
You don't want to perform the computation once; you want to perform it zero times if you don't end up adding it. But you then have to either literally lock or have a virtual CAS-based lock to reserve time to perform the computation. If it's okay to block for arbitrarily long on a single element waiting for a computation to complete, then it's okay. Overall, I'd say it's better the present way. Maybe the docs need to be even clearer that if you call the method multiple times, you may get one execution of op per call if the map is in the process of having that key added. (It's a natural consequence of what the docs already say, but thinking about properties when simultaneous updates are attempted is not so easy; if they were, concurrent programming wouldn't be so hard.) |
Essentially, a lock on a lazily-evaluated node would be required. We had a long discussion about this: https://issues.scala-lang.org/browse/SI-7943 I tend to fall in your camp about it being better the present way, and having the docs improved. |
I'd be happy to have the docs improved too. I might open a PR and do just that. For what it's worth, I found an old implementation of an |
Sorry to awaken this old thread.
I can imagine one or more race conditions if I take it that only the delegation to |
@matanster - I think you are missing what |
Yes, but I was incorrectly interpreting the code. I guess that's why I don't typically write concurrent libraries ;) Shall I remove my unnecessary comment then from this issue here? |
that's not necessary. if you're now convinced the code is correct, this conversation documents that it passed an additional level of review, with at least the three of us, perhaps more, having taken a second look. |
Problem This function was added because getOrElseUpdate was not atomic until Scala 2.11.6. Since we supported 2.10 we included our own simple atomic implementation. See: scala/scala#4319 We now no longer support Scala 2.10. Solution Delete this function and change callers to use `getOrElseUpdate` directly since this is now atomic for the supported version of Scala. RB_ID=842684
Override
getOrElseUpdate
method inTrieMap
.The signature and contract of this method corresponds closely to the
computeIfAbsent
fromjava.util.concurrent.ConcurrentMap
.Eventually,
computeIfAbsent
should be added toscala.collection.concurrent.Map
.Add tests.
Review by @Ichoran