[foreign-memaccess] on confinement

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Wed Jul 1 18:13:26 UTC 2020


Hi Andrew - this is all very timely and this one of the idea I was 
secretely hinting at here:

https://mail.openjdk.java.net/pipermail/panama-dev/2020-May/009004.html

We're currently keeping open both the thread-local GC handshake (a 
variation on an idea you had at some point in the past) and the Loom 
lifetime approach. I think the former would be slighly more general and 
preferrable esp. in the native interop use case - but the latter is a 
good approx if we really can't find a way to wrestle GC safepoints.

Cheers
Maurizio

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.
>
> By hooking TemporalResources into Streams, we can also do things like
>
>      try (MemorySegment segment = MemorySegment.allocateNative(SEGMENT_LENGTH*4)) {
>          initialize(segment);
>          System.out.println(StreamSupport.stream(new MySpliterator(segment), /* parallel */true).reduce(Integer::sum));
>      }
>
> This usage is secure because when we create a ReduceTask we do
> this:
>
>      ReduceTask(ReduceTask<P_IN, P_OUT, R, S> parent,
>                 Spliterator<P_IN> spliterator) {
>          ...
>          this.lifetime = parent.lifetime;
>      }
>
>      protected S doLeaf() {
>          Lifetime old = JLA.unsafeSetLifetime(Thread.currentThread(), this.lifetime);
>          try {
>              return helper.wrapAndCopyInto(op.makeSink(), spliterator);
>          } finally {
>              JLA.unsafeSetLifetime(Thread.currentThread(), old);
>          }
>      }
>
> And when we terminate a Stream we can ensure that we are still within
> the Lifetime of the thread that created us.
>
> I'm very sorry that all of this is rather hand-waving and I'm still
> working on a stable and correct prototype, but I hope that you get the
> idea, and that this is of interest to you.
>


More information about the panama-dev mailing list