[foreign-memaccess] on confinement
Andrew Haley
aph at redhat.com
Wed Aug 5 12:23:01 UTC 2020
On 7/20/20 10:42 AM, Maurizio Cimadamore wrote:
>
> On 01/07/2020 18:40, Andrew Haley wrote:
>> Hi,
>>
>> On 04/06/2019 12:34, Maurizio Cimadamore wrote:
>>
>>> So, it seems that, even with the complexity of (1), we still don't have
>>> a clear cut way to think about the problem outlined above. And I'd
>>> really really like to avoid exposing such complex state transitions into
>>> the final API. I think the cross product of alive/dead state with
>>> neutral/owned state leads to place where it's really hard to think about
>>> who can do what at any given point in time (in addition to make the
>>> implementation more convoluted and direct which might result in more
>>> places for bugs to hide, as well as a potential for performance
>>> degradation).
>>>
>>> On the other hand, it seems like something like (2) would not only lead
>>> to something more desirable API/programming model-wise, but also to a
>>> cleaner path to supporting a multi-ownership scenario such as the one
>>> described above. Whether the fact that (2) requires explicit ownership
>>> transfers is too strict, is something that we don't have enough data
>>> points, at this stage, to work with.
>>>
>>> Of course I would have preferred to side-step all this and leave all
>>> synchronization cost to the user - making only minimal assumptions; but,
>>> as we have shown, this approach (which was my opening position!) has a
>>> flaw in the sense that it leaves concurrent access exposed to the risk
>>> of accessing a segment that's already been closed (which can result in a
>>> VM crash). While this is an acceptable answer for an unsafe API, we
>>> wouldn't want something like this to happen in a safe API.
>> I've been working with Ron Pressler on his "Lifetime" class. The idea
>> is to register a lifetime in a try/finally block and then launch some
>> threads, either virtual or non-virtual. "non-virtual" is a name for
>> the kind of Java Threads we have today, "virtual Threads" are what
>> Project Loom now calls "fibers".
>>
>> The idea of a Lifetime is that you create a Lifetime instance and then
>> launch some Threads:
>>
>> try (MemorySegment segment = MemorySegment.allocateNative(100)) {
>> try (ExecutorService executor = Executors.newThreadExecutor(factory)) {
>> writeSegment(segment, intHandle); // Executed on this thread
>>
>> executor.submit(() -> {
>> writeSegment(segment, intHandle); // Executed on a newly-launched thread
>> });
>> // The newly-launched thread is guaranteed to have terminated so we
>> // can close the MemorySegment.
>> }
>> }
>>
>> In order to make this work I've made some small modifications to
>> AbstractMemorySegmentImpl.java, in particular this:
>>
>> --- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
>> +++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
>> @@ -280,7 +290,7 @@ public abstract class AbstractMemorySegmentImpl implements MemorySegment, Memory
>> }
>>
>> void checkRange(long offset, long length, boolean writeAccess) {
>> - scope.checkValidState();
>> + access.checkAccess();
>> if (writeAccess && !isSet(WRITE)) {
>> throw unsupportedAccessMode(WRITE);
>> } else if (!writeAccess && !isSet(READ)) {
>>
>> There's a new class called TemporalResource which is used to make sure
>> our gurantees are satisfied. access here is an instance of
>> TemporalResource.
>>
>> Every call to TemporalResource::checkAccess() makes sure that the
>> parent of this thread is the thread that created the TemporalResource
>> and that we are still a child of the Lifetime in which the
>> TemporalResource was created. We can do this without any inter-thread
>> communication.
>
> Looking in more details at this, I have few questions:
>
> * I assume we need to create a lifetime for every new memory segment
> (where the lifetime represents the span of a given segment) ? Or does
> the segment just attach to the "current lifetime" that was available at
> creation?
There's more than one way to do it, but the current implementation
needs to create a Lifetime object suitable for a try-with-resources
construct.
> * Any thread created in a 'well-behaved' way (e.g. executor service,
> stream, or other lifetime-aware constructs) will 'nest' accordingly into
> the segment's lifetime
Right.
> * It follows that the segment cannot be closed until any thread
> associated with its lifetime have completed.
Yes, exactly.
> Is my understanding correct? Can you please clarify the first point? In
> other words, in this code:
>
> try (MemorySegment segment = MemorySegment.allocateNative(100)) {
> try (ExecutorService executor = Executors.newThreadExecutor(factory)) {
> writeSegment(segment, intHandle); // Executed on this thread
>
> executor.submit(() -> {
> writeSegment(segment, intHandle); // Executed on a newly-launched thread
> });
> // The newly-launched thread is guaranteed to have terminated so we
> // can close the MemorySegment.
> }
> }
>
> What happens when we create the segment? Do we create a new lifetime
> nested in the outer lifetime (e.g. L1 <= L0) ? Or do we just let the
> segment reuse the outer lifetime L0 ?
The thing we create is a TemporalResource. There's a rough patch
(against the loom-dev repo) at:
http://cr.openjdk.java.net/~aph/streams-with-lifetimes.diff. A
TemporalResource records the current Lifetime and current Thread;
all threads created by a ThreadExecutor inherit the current
lifetime.
--
Andrew Haley (he/him)
Java Platform Lead Engineer
Red Hat UK Ltd. <https://www.redhat.com>
https://keybase.io/andrewhaley
EAC8 43EB D3EF DB98 CC77 2FAD A5CD 6035 332F A671
More information about the panama-dev
mailing list