[foreign-memaccess] on confinement
Jorn Vernee
jbvernee at xs4all.nl
Wed Jun 5 16:48:47 UTC 2019
> Thoughts?
I don't think we can safely do our confinement check `scope.owner !=
Thread.currentThread()` if scope.owner is mutable, without some form of
synchronization. I really think the confinement thread should be
determined at (sub)segment creation time, and then be immutable
afterwards.
Jorn
Maurizio Cimadamore schreef op 2019-06-05 17:40:
> Thanks Jorn,
> I went pretty much through the same reasoning and realized that:
>
> a) confinement must be the default
>
> b) handing off ownership must be an opt-in
>
> b2) similarly, racy shared segments which synchronize on the liveness
> bit can be an equally appealing opt-in
>
> c) tracking region overlapping is uber-expensive; it's much better to
> define primitives which allow 'splitting a region' in non overlapping
> segments by construction (e.g. resize is not the way to get what we
> want here); let's call this 'split'
>
> c2) the bits returned by 'split' are _pinned_
>
> d) we need a way to 'merge' the bits back into the parent.
>
> What I came up with this this [1], which I think kind of implements
> your principle for ShareableSegments (note this is an example, not a
> full blown Panama patch).
>
> Thoughts?
>
> Maurizio
>
> [1] -
> http://cr.openjdk.java.net/~mcimadamore/panama/TestScopedSegmentMerge.java
>
> On 05/06/2019 14:59, Jorn Vernee wrote:
>> One other thing I realized; closing the root segment through a view
>> segment (like proposed before) is only possible when the root segment
>> and _all_ view segments are confined to the same thread. At least if
>> we want to avoid synchronization on access when checking liveliness. I
>> think this gets us the following set of rules for non-shared segments:
>>
>> 1. Terminal operations are always thread confined (safety feature to
>> prevent VM crashes when resource is freed by another thread).
>> 2. Always confined to the same thread (avoid mutable fields,
>> complexity in implementation).
>> 3. We can close the root segment through a view segment.
>> 4. We can not share a view segment with a different thread (would
>> break rule 1. when combined with 3.).
>> 5. No need for the user to keep a reference to the root segment, since
>> we can close it through a view segment.
>> 6. No need for subsegment tracking.
>>
>> Also, shareability should be an opt-in, but it seems that supporting
>> lazy transition into a shared state (with asConfined()) creates too
>> much complexity for the simple single-threaded case, so I think it
>> should be an opt-in at segment creation time. That way we can keep the
>> 'default' single threaded implementation fast and simple.
>>
>> ---
>>
>> We could still go with a separate ShareableSegment type, which does
>> allow sharing of view segments with other threads, but does not allow
>> closing the root segment through a view segment. To avoid mutable
>> confinement thread fields we can require the confinement thread to be
>> specified when creating the view segment. A strawman:
>>
>> interface ShareableSegment extends MemorySegment {
>> MemorySegment resize(Thread confinementThread, long offset,
>> long length); // support 'divide et impera'.
>> default MemorySegment resize(long offset, long length) {
>> return resize(Thread.currentThread(), offset, length);
>> }
>>
>> void merge(MemorySegment subsegment); // could do
>> automatically with GC + Cleaner as well
>> // need some synchronization if resize and merge can be called
>> by other threads then the root's confinement thread
>>
>> // ... factory methods
>> }
>>
>> Which gets us the following rules for shareable segments:
>>
>> 1. Terminal operations are always thread confined (safety feature to
>> prevent VM crashes when resource is freed by another thread).
>> 2. Always confined to the same thread (avoid mutable fields,
>> complexity in implementation).
>> 3. View segments can be confined to different threads than the root
>> segment.
>> 4. We can not close the root segment through a view segment (would
>> break rule 1 when combined with 3).
>> 5. The user must keep a reference to the root segment at all times to
>> be able to close it and avoid resource leaks.
>> 6. Need to track subsegments in order to know whether the root segment
>> can be closed safely.
>>
>> ---
>>
>> Also, overlap of subsegments will break confinement in the sense that
>> multiple threads can write/read to/from the same region, but since
>> subsegments owned by multiple threads can not free/release the
>> underlying resource, I don't think overlapping subsegments could crash
>> the VM. So, maybe it's good enough to tell the user to make sure that
>> subsegments owned by different thread's don't interfere which each
>> other, but we don't enforce that in the implementation?
>>
>> If we go that route I believe we can make the subsegment tracking for
>> ShareableSegment a simple AtomicLong reference count. Where the
>> liveliness flag in a subsegment is a reference to the root segment,
>> that is nulled out when merging, and also used to make sure that merge
>> is called with an actual subsegment.
>>
>> Jorn
>>
>> Maurizio Cimadamore schreef op 2019-06-05 02:16:
>>> On 04/06/2019 17:03, Maurizio Cimadamore wrote:
>>>> Note: I'm not saying this will be trivial to implement correctly -
>>>> but what I like about this is that the programming model will look
>>>> relatively clean in comparison to something like (1). Essentially
>>>> you can slice and dice all you want, and, as long as you are asking
>>>> reasonable questions, things will work with decent performances.
>>>
>>> Quick update; I've been doing some experiment on this - it doesn't
>>> look pretty for now.
>>>
>>> Some of the issues we have to take into account:
>>>
>>> * as discussed, we want the master region to somehow keep track (via
>>> its mutable 'scope-like' object) of the sub-regions
>>>
>>> * if we share the same scope for all subregions (which we probably
>>> want to avoid too much allocation on resize) then we need to have a
>>> way for the sub-region to perform an efficient confinement check -
>>> one
>>> trick I used was to give each sub region an unique index, and then
>>> use
>>> the index to access a subregion 'ownership' array
>>>
>>> * we need to take into account regions being GCed - otherwise the
>>> lists kept into the master region will (potentially) grow w/o bounds
>>>
>>> * we need to take into account synchronization when adding/removing
>>> sub-regions - this is probably not a big concern given that these
>>> operations occur during a 'resize' or when a region is being GC, so
>>> the memory access itself can still be fast
>>>
>>> * since we can transfer ownership, the owner thread is not a final
>>> constant anymore... this will probably affect performances
>>> considerably
>>>
>>> * I haven't even started to look at rejecting overlapping sub regions
>>> with different owners...
>>>
>>> Needless to say, the resulting implementation is very finicky, and
>>> I'm
>>> worried about the overall performance model of this approach.
>>>
>>> Also, I don't think that what I'm seeing is an artifact of lumping
>>> MemoryScope and MemorySegment together - yes, in principle having a
>>> separate scope (with a notion of confinement in it) helps in the
>>> sense
>>> that resizing a segment becomes an orthogonal concern. But then you
>>> are back in a world where you can't give a different thread owner to
>>> different sub-region, and the only way around that restriction is to
>>> use memory copy (e.g. create a new segment and copy contents of the
>>> old one to the new).
>>>
>>> If that cross-subregion policy is what we realistically want to
>>> enforce, then I don't think it's worth doing a lot of heroics here -
>>> we can simply say that a segment is confined to a thread, there's no
>>> ownership transfer operation, but the same effects can be achieved
>>> through memory copy. This doesn't seem quite a rich a story as the
>>> one
>>> we were looking at - but if we were ok with Scope being in charge of
>>> thread confinement, this would have been the only story possible.
>>>
>>> So, the question becomes: do we really need a way to transfer
>>> ownership of a segment from thread A to thread B ? And if so, what
>>> granularity should be used? I think these are the possible answers:
>>>
>>> a) ownership transfer not supported - region copy should be used as a
>>> workaround
>>> b) ownership transfer supported; all subregion are constrained to
>>> have
>>> same owner as the root; when ownership changes, all subregions change
>>> ownership too
>>> c) ownership transfer supported; subregion ownership can set
>>> independently of the root
>>>
>>> I realized that, in the email I've sent this morning I picked the
>>> most
>>> difficult point in the design space (c) - that is, support ownership
>>> transfers at the subregion granularity. This seems useful to
>>> implement
>>> divide and conquer algorithms, but at the same time, I realized, this
>>> was simply not possible with the scope-based solution we had before
>>> (since all subregions had same scope there - hence same confinement).
>>>
>>> In other words, all the implementation strategies we've seen so far
>>> are capable of handling either (a) or (b) [as for (b) I'm not sure
>>> about the potential JIT cost in making thread owner non-final]. The
>>> implementation story for (c) is far more convoluted (**), and I'm
>>> very
>>> skeptical that, even if we can pull that off, it will perform in a
>>> way
>>> that will be deemed acceptable.
>>>
>>> Is (c) simply asking for too much? And, if so, is (b) something that
>>> could be useful still?
>>>
>>> Maurizio
>>>
>>> (**) Honestly, the overlapping region check seems the straw that
>>> breaks the camel's back - to implement the check it's sadly
>>> unavoidable to keep all subregions which share the same root in the
>>> same place - which then poses aforementioned problems with respect to
>>> such subregions being GCed, and need for synchronization when
>>> maintaining all the ancillary lists. And, this overlapping region
>>> check is needed in both the approached (1) and (2) that I have
>>> outlined earlier in [1], I believe.
>>>
>>> [1] -
>>> https://mail.openjdk.java.net/pipermail/panama-dev/2019-June/005674.html
More information about the panama-dev
mailing list