RFR 8243491: Implementation of Foreign-Memory Access API (Second Incubator)
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Fri May 1 14:36:57 UTC 2020
Hi Peter,
this does look better yes.
I suspect this doesn't affect performance negatively right? (in most
cases, when acquiring, state will be OPEN).
Now there's dup(). I think implementing dup() on a root scope is not too
hard - for the child you probably need some trick, but probably not too
bad - in the sense that in a Child scope, the cleanup action is really
the increment of the root exit scope. So you could have a:
close(boolean doCleanup)
like we do now, and avoid the cleanup on dup (which in the case of the
Child will avoid the adder increment). I *believe* this should be
functionally equivalent to what we have now.
One question: the more I look at the code, the less I'm sure that a
close vs. access race cannot occur. I'm considering this situation:
* thread 1 does acquire, and find state == OPEN
* thread 2 does close, set state to CLOSING, then checks if the adders match
But, how can we be sure that the value we get back from the adder (e.g.
acquires.sum()) is accurate and reflects the fact that thread (1) has
entered already? The API doesn't seem to provide any such guarantee:
" The returned value is /NOT/ an atomic snapshot; invocation in the
absence of concurrent updates returns an accurate result, but concurrent
updates that occur while the sum is being calculated might not be
incorporated."
I guess perhaps the trick is in that "while" ? E.g. there's no guarantee
only if the concurrent update occurs _while_ sum() is called.
Now I think this is ok - because when acquire races with close we can
have two cases:
1) "state" has been set to CLOSING _before_ it is read inside acquire()
2) "state" has been set to CLOSING _after_ it is read inside acquire()
In the case (1), acquire will start spinning, so nothing can harmful can
really happen here. Either the read of "state" from acquire() happened
when "state" is about to transition to CLOSED (in which case it will
fail soon after) - or the read happened before close() had a chance to
look at the counter - in which case there might be a chance that the
counter will be updated concurrently (e.g. acquire() thread calls
increment() while close() thread calls sum()). But there will be two
outcomes here: either the adder has missed the update, in which case the
segment will be close, and acquire() will fail; or the adder got the
update, in which case close() will fail and acquire() will fail.
In the case (2) we have an happen before edge between the "state" read
performed by acquire() and the "state" write performed by close(). Which
means that, by the time we get to calling acquires.sum() we are
guaranteed that the thread doing the close() will have seen the adder
update from the thread doing the acquire (since the update comes
_before_ the volatile read in the acquire() method). So, for this case
we have that:
* [acquire] acquires.increment() happens before
* [acquire] state > OPEN happens before
* [close] state = CLOSING happens before
* [close] acquires.sum()
Which, I think, proves that the thread performing the acquire cannot
possibly have assumed that the scope is OPEN and also updating the adder
concurrently with the call to sum() in the close thread.
Maurizio
On 01/05/2020 14:00, Peter Levart wrote:
>
> On 4/30/20 8:10 PM, Maurizio Cimadamore wrote:
>>
>> On 30/04/2020 01:06, Peter Levart wrote:
>>> Think differently: what if the client succeeded in closing the
>>> segment, just because it did it in a time window when no thread in
>>> the thread pool held an open scope (this is entirely possible with
>>> parallel stream for example since threads periodically acquire and
>>> close scopes). This would have the same effect on threads in the
>>> thread pool - they would not be able to continue their work... What
>>> I'm trying to say is that this is just a mechanism to make things
>>> safe, not to coordinate work. If program wants to avoid trouble, it
>>> must carefully coordinate work of threads.
>>
>> This appear to me to be a bit of a slippery slope? Sure, if somebody
>> prematurely calls close() on a segment while other threads are
>> accessing it, it could be seen as undefined behavior (a la C
>> specifications ;-) ), but then, if you pull on the same string, why
>> even bother with confinement in the first place? If you call close()
>> prematurely and you get a VM crash that's on you?
>
>
> Luckily, I think I have fixed this shortcoming in the alternative
> MemoryScope:
>
>
> http://cr.openjdk.java.net/~plevart/jdk-dev/8243491_MemoryScope/v2/MemoryScope.java
>
>
>
> The trick is in using a 'state' with 3 values: OPEN, CLOSING, CLOSED ...
>
>
> The acquiring thread does the following in order:
>
> - increments the 'acquires' scalable counter (volatile write)
>
> - reads the 'state' (volatile read) and then enters a spin-loop:
>
> - if state == STATE_OPEN the acquire succeeded (this is fast
> path); else
>
> - if state == STATE_CLOSING it spin-loops re-reading 'state' in
> each iteration; else
>
> - if state == STATE_CLOSED it increments 'releases' scalable
> counter and throws exception
>
>
> The closing thread does the following in order:
>
> - writes STATE_CLOSING to 'state' (volatile write)
>
> - sums the 'releases' scalable counter (volatile reads)
>
> - sums the 'acquires' scalable counter (volatile reads)
>
> - compares both sums and:
>
> - if they don't match then it writes back STATE_OPEN to 'state'
> (volatile write) and throws exception; else
>
> - it writes STATE_CLOSED to 'state' (volatile write) and executes
> cleanup action
>
>
> This, I think, is better, isn't it?
>
>
> Regards, Peter
>
>
>
>
>
>> Maurizio
>>
>>
More information about the core-libs-dev
mailing list