[foreign-memaccess] RFC: to scope or not to scope?
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Mon Jun 3 23:39:34 UTC 2019
On 03/06/2019 23:13, Jorn Vernee wrote:
> Maurizio Cimadamore schreef op 2019-06-03 22:54:
>> On 01/06/2019 16:50, Jorn Vernee wrote:
>>> For asConfined() creating a view segment would not work, since the
>>> root segment, which holds the liveliness flag, would not be confined
>>> to a single thread. For asConfined to work as expected I believe we
>>> would have to kill the root segment, effectively transferring the
>>> underlying resource to the newly created confined segment. This also
>>> brings up an idea I had been thinking about; make thread
>>> confined-ness a more local, temporary state. e.g. you call
>>> asConfined which creates a segment that is confined to the current
>>> thread, but then add a release() method that invalidates the
>>> confined copy and makes the root segment valid again. This could be
>>> used around loops, e.g.:
>>
>> I don't think I agree with this line of thought. The use I have in
>> mind for this API is something like:
>>
>> try (MemorySegment segment =
>> MemorySegment.ofNative(bytes).asConfined()) { ... }
>>
>> If you do this, then inside the try with resources you have the
>> guarantee that everything will be confined.
>
> This seems to be excluding the case where the MemorySegment is stored
> in a field, and then used in a loop. I'd deem that case quite likely,
> and I think there's a solution possible that works for both use-cases
> without adding too much complexity.
Can't you do same for field?
MemorySegment segment = MemorySegment.ofNative(bytes).asConfined();
If you do this, who exactly will be able to access the non-confined
temporary root we have created?
>
> Also, when we have a snippet like this, the liveliness check should
> already be hoisted, assuming the loop body is also inlined, since the
> executing thread is the only one that has access to the liveliness flag.
>
>> We could of course enforce a no-access state by default, or
>> confined-by-default, but IMHO that's just adding additional hops for
>> the (likely) case where these things don't make a difference. And, if
>> you really want to make sure that what you have is confined, and
>> there's no racy access possible, just call the view method as soon as
>> you allocate the segment (as above), then nobody will be able to
>> access to the non-confined version.
>>
>> This is not different from how ByteBuffer::asReadOnly works - and I
>> think that's a pragmatic compromise. That API too will have the same
>> issues discussed here (if somebody keeps hold of the original BB, you
>> can still write on a supposedly read-only buffer), but I wonder how
>> much that is really a problem in practice, given that it can be
>> avoided with some discipline.
>
> Yes, but the JIT can not really assume that the programmer has this
> discipline. I'm not sure if we can prove that there's only a single
> thread accessing a segment (or rather, the liveliness flag) when this
> is not guaranteed by the API. Which would be the case if a confined
> segment is simply a view over a non-confined segment I think.
Not sure I follow and this seems to conflate API aspects with
optimization aspects. From liveness perspective, only one thread can
change the liveness bit. This is true even in the patch I've sent. Each
segment has an owner thread, and only the owner can close the region.
Confinement only add extra enforcements when accessing the region with
read/write operatons.
Also, from an optimization level, I'm not aware of the JIT having any
issue of 'not trusting' that every access comes from same thread. The
JIT doesn't worry about that - and if there are issues, these are
typically multi-threading bug in user-land (e.g. users forgot to add
synchronization where they needed to). Vlad can correct me if I'm wrong,
but that is my understanding.
>
>> As much as I agree with the theory that returning a no-access segment
>> is the only way to make the API really tight, at the same time, I
>> having factory methods that return what would effectively be unusable
>> segments seems a weird API choice to me. At this point I think I would
>> prefer having some 'flags' to be passed to the segment factory (pretty
>> much like we had scope charateristics before).
>
> This seems good to me for the current goal we're trying to achieve;
> making the liveliness check hoist-able. I think of the local acquire +
> release strategy as a different way to get there.
I think the problems with making the liveliness check hoistable have
nothing to do with confinement guarantees, really - again I'll defer to
Vlad for more on this.
To me the #1 problem is a user model one - as a creator and user of a
segment, do I want to worry about other threads closing the segment
behind my back? I don't think we want that. And that is what leads to
having some critical operations being confined. I think the JIT mostly
doesn't care about all this.
>
> For fully fledged memory resource sharing between threads I agree with
> John that we should disallow access by multiple threads at the same
> time. So the state between allocating a segment and it being acquired
> by a single thread would be 'useless', but we could use a separate
> type that doesn't derive from MemorySegment as well for this right?
> That shouldn't be too weird...
I have problems with this view. On the one hand we have a very rich
VarHandle API which allows very fine-grained control over concurrent
access. On the other hand this is saying: sorry, only one thread at a time.
There will be cases, for sure, where people will be happy with 'one
thread at a time'. But there will be other cases where people won't
care, or where people would like to go racy, and handle things
themselves (this is, after all, a low level API). I honestly don't see
the need for such hand-holding decisions here. I think a racy default is
perfectly fine (after all, that's what users of Unsafe are familiar
with) - if you want more protection, ask for it (but also know that it
might come at extra cost).
I'm very worried about second guessing how users might use this very
general API, and trying to enforce general model of usage - which might
work well in certain cases, but utterly fail in others. If we start
simple, sticking with what's in Unsafe, we can build an API that works,
and the we can add stability guarantees as optional add-ons - this way
the performance model is clear - you pay what you get. So you can stick
with raw and racy (and fast), or go with confined and safe (and possibly
less fast).
Maurizio
>
> Jorn
>
>> Maurizio
More information about the panama-dev
mailing list