[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