[foreign-memaccess] on confinement

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Mon Jul 20 09:42:27 UTC 2020


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?

* 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

* It follows that the segment cannot be closed until any thread 
associated with its lifetime have completed.

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 ?

Thanks
Maurizio



More information about the panama-dev mailing list