[foreign-memaccess] RFR: Move "owner" field and thread-confinement checks to MemoryScope

Peter Levart peter.levart at gmail.com
Fri May 15 11:34:20 UTC 2020


Hi Maurizio, comments inline...

On 5/15/20 11:44 AM, Maurizio Cimadamore wrote:
> On Fri, 15 May 2020 08:56:14 GMT, Peter Levart <plevart at openjdk.org> wrote:
>
>> Now MemoryScope is simplified, I re-based this change and am opening this PR on top. Currently MemorySegment is
>> encapsulating thread-confinement logic and state (owner field) while MemoryScope is encapsulating temporal-confinement
>> logic and state. But the interplay between the two must be very carefully caried out (for example, close() or dup() on
>> child scopes may only be called in owner thread). By moving the thread-confinement logic and state to MemoryScope, I
>> think we get better encapsulation as all MemoryScope methods become "safe" - some can still be called in owner thread
>> only, but failing to do so throws IllegalSateException instead of exhibiting undefined behavior.
> Overall, I like this and I agree that moving ownership in scope makes the code easier to follow. I've added some
> specific comments.
>
> src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryScope.java line 90:
>
>> 89:      */
>> 90:     static MemoryScope createUnchecked(Object ref, Runnable cleanupAction, Thread owner) {
>> 91:         return new Root(owner, ref, cleanupAction);
> For some (merely stylistic) reason, I'd prefer to see `owner` coming first in the parameter list


Ok, moving it as 1st parameter.


>
> src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryScope.java line 95:
>
>> 94:     private final Thread owner;
>> 95:     boolean closed; // = false
>> 96:     private static final VarHandle CLOSED;
> Is there any real advantage in not having the initialization here? I mean, I get we probably save one putfield - but I
> would say that if the code is hot, C2 is probably able to detect that the store is useless? I kind of prefer the code
> to be self-evident rather then to use comments, where possible, of course. I've seen in the past cases where seemingly
> innocuous redundant initializer subtly changed happens-before order; but this shouldn't be the case here?


If there is a data race (publishing reference to scope), then I think 
JMM may allow this write to end up in final memory location later than 
for example another write that puts true to this field. So while C2 may 
optimize this write into no-op, it may still theoretically cause 
trouble. This is only theory of what is allowed by JMM of course. All 
fields of an object already get their 1st implicit initializing write 
which is different as it is guaranteed to happen before any read of that 
field. An explicit write is not given such guarantee (unless the field 
is final).

While publishing scope is never performed via data race in our case 
(scope is assigned to final field in MemorySegment), it is a good 
property of a class to allow such usage (like String for example) in 
particular if such class is security or stability sensitive and/or it 
may be used elsewhere hypothetically. So initializing plain fields to 
their default values is never a good thing and is, IMHO just a relic 
from C, where they did not get the implicit initialization. I think Java 
is old enough that everybody knows this difference even without hints in 
comments. So maybe just remove the comment?


>
> src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryScope.java line 139:
>
>> 138:      * If this is a root scope, new root scope is returned, this root scope is closed but
>> 139:      * eventual cleanup action is not executed yet - it is inherited by duped scope.
>> 140:      * If this is a child scope, new child scope is returned.
> Suggestion:
>
>       * If this is a root scope, a new root scope is returned; this root scope is closed, but
>       * without executing the cleanup action, which is instead transferred to the duped scope.


Better. Will change.


>
> src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryScope.java line 141:
>
>> 140:      * If this is a child scope, new child scope is returned.
>> 141:      * This method may only be called in "owner" thread of this scope unless the
>> 142:      * scope is a root scope with no owner thread - i.e. is not checked.
> Suggestion:
>
>       * If this is a child scope, a new child scope is returned.
>       * This method may only be called in the "owner" thread of this scope unless the


Will change.


>
> src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryScope.java line 203:
>
>> 202:     @ForceInline
>> 203:     final void checkAliveConfined() {
>> 204:         if (closed) {
> Consider making this private


If I make it private, it is not accessible in subclasses although they 
are nestmates unless I do something like that:


((MemoryScope)this).checkAliveConfined();


Do you prefer such call gymnastics? If this class gets used elsewhere 
then maybe it will be required...


>
> src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryScope.java line 250:
>
>> 249:         MemoryScope dup(Thread owner) {
>> 250:             Objects.requireNonNull(owner, "owner");
>> 251:             return closeOrDup(owner);
> Uhm - not sure about this. Let's say that you have an unchecked segment - you start off unconfined - but then, after
> some processing, you want to kill the segment and obtained a confined view. Seems a legitimate use case? Although I
> agree that, in that case, we'd need at least some sort of atomic handshake on the closed bit, to ensure the update
> either fails or succeeds across multiple racy threads.


So above, the check is made against the parameter "owner" not the field 
"owner". This means that we are preventing another usecase where one 
would want to "transfer" ownership to nobody - when one would start with 
confined segment and then wanted to obtain an unconfined segment from it.

This is already prevented in the MemorySegment too:

     public MemorySegment withOwnerThread(Thread newOwner) {
         Objects.requireNonNull(newOwner);

So maybe just rename the parameter to newOwner ?


>
> src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryScope.java line 259:
>
>> 258:
>> 259:         private MemoryScope closeOrDup(Thread newOwner) {
>> 260:             // pre-allocate duped scope so we don't get OOME later and be left with this scope closed
> I think I don't like this way of sharing the code. Since we're in cleanup mode, I think the code would be more readable
> by having two separate methods `close` and `dup` which both uses the same underlying logic for flipping the liveness
> bit (using the lock).


I think the sharing can be performed in a more readable way. Will show 
in an update...


Regards, Peter


>
> -------------
>
> PR: https://git.openjdk.java.net/panama-foreign/pull/167


More information about the panama-dev mailing list