[foreign] RFR 8209497: Polish Resource API

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Wed Aug 15 10:18:01 UTC 2018


Hi John,
thanks for the comments. I think I get the point you are trying to make 
- e.g. all managed resources have a scope, the scope could be a native 
scope (e.g. backed by native memory) or an heap scope (backed by our 
good old Java heap). Then callbacks just becomes resources, where they 
can sometimes point to a native vs. heap scope.

While in general I buy this, there are some details about callbacks - 
and the way in which they can be created, that doesn't fit too well into 
this scheme of things. Consider the following case:

interface Foo extends Callback<Foo> {
    void m();
}

And then, the following Java code:

Foo foo = () -> {};

The above 'foo' is, at least in my opinion, just a plain Java object. In 
a way, I have not even used the Panama API, at least not directly.

The changes proposed for this Resource API basically claim that not all 
callback objects are created equal: some of them are just regular POJO; 
other are created by the binder and might have some backing storage. I 
agree with you that the backing storage might be heap allocated, or 
native memory allocated - but the distinction remains: callbacks with 
some backing storage are not just plain Java lambdas; they are something 
more.

Now, I can fully believe that what we are seeing is a result of us 
pushing functional interfaces too far - e.g. in a world where functional 
interfaces and function pointers are separated (the current API unifies 
them), then functional interfaces are... plain old good Java functional 
interfaces - e.g. Java objects, managed by the GC. A function pointer, 
conversely, would be something that (sometimes) ties a functional 
interface object into a native function pointer. Under this revised 
classification, a function pointer would become a resource, whereas a 
callback would cease to be one (at least in its fuller sense).

That said, it's clear that this part of the API needs few more 
iterations and some experience from concrete use cases before being 
called 'good'. This change addresses some small irregularities in the 
API, rather than trying to re-design it more fully (which, I agree with 
you, it's something that will have to happen sooner or later).

Thanks
Maurizio


On 14/08/18 21:51, John Rose wrote:
> On Aug 14, 2018, at 7:22 AM, Maurizio Cimadamore <maurizio.cimadamore at oracle.com> wrote:
>> Callbacks are NOT resources (since you can have a callback backed by a Java lambda), but they can _optionally_ be linked to one.
> Callbacks are not unique in this respect.  Any entity that can be copied
> or mirrored between the Java heap and native storage works this way.
>
> For example, a by-value struct return value (or a vector return value)
> might be allocated in native memory so that it can be addressed by
> native code, or it might be allocated on the Java heap (wrapped in
> a long[] array or heap byte buffer).  If the identity is unimportant because
> it is passed by-value, then the runtime can validly copy it back and
> forth between the Java heap and native memory, as needed, and
> even keep two copies (one for each location).  This usage pattern
> is quite similar to callbacks.
>
> Even the basic Pointer type is designed to allow pointers into
> the Java heap as well as native memory (and other kinds of
> memory too).  A Pointer backed by pure Java heap storage
> will sometimes be useful as a cursor into Java-readable native
> data structures, even though native code cannot directly
> address those structures (unless they are mirrored in native
> memory or the heap block is somehow temporarily pinned).
>
> The underlying Unsafe API is designed to allow dynamically
> mixed access between on-heap and off-heap; also such flexible
> allocation patterns are supportable by Pointer and ByteBuffer.
>
> My point here is that, on the Java/native boundary, Callback
> is not unique, and we will have a variety of structures which
> dynamically choose whether they are backed on Java heap,
> native memory, both, or (with value types) neither.
>
> The corollary is, I think, that the Resource API should somehow
> provide for the heap-only state, rather than try to statically
> exclude types from Resources that might have a heap-only
> state.
>
> The easiest way I can think of to encode such a state is to
> create a distinguished Scope object which encodes this state.
> The heap-scope would nominally own objects which can
> "take care of themselves" (with help from the GC of course).
> It wouldn't be able to enumerate them.  Handing off an object
> to the heap-scope would require that object to mirror itself
> on heap and discard any other scoped resources.  Natural
> members of the heap-scope would include Callbacks wrapping
> user lambdas, bit-images of structures stored only on the heap,
> and cursors into heap-backed native data structures such as
> packet images under construction.
>
> Such a heap-scope corresponds approximately to another
> kind of scope we've already talked about, a scope which holds
> per-library resources, which is emptied only when the library
> is unloaded.
>
> A civilized API might also feature temporary scopes for sessions
> of API use.  These would be shorter-lived than the library as a
> whole, but not tied to any particular stack frame (try/finally block).
>
> Any discussion of special-purpose scopes is incomplete unless
> we also point out the null-scope, which absorbs resources and
> never gives them back.  Once a resource is safely owned by the
> null-scope, its backing storage can be freed at any point, either
> immediately or (if we want a measure of debugging help) after
> dangling pointers no longer need to be detected.
>
> All of the above scopes can be characterized as non-stack
> (non-auto or static) scopes.  Most of our demo examples involve
> stack scopes which must be used with try/finally.  Those scopes
> are thread-confined and therefore are much safer to use, as
> long as dangling pointers are prevented.  As you have pointed
> out, Maurizio, those stack scopes also have a natural inclusion
> relation (older includes younger), allowing a safe handoff
> within the thread from blocks used inside a method to an
> enclosing scope that the method returns to.
>
> A final point:  I mentioned mirroring and copying above as
> important moves for by-value data.  C doesn't natively support
> this (except for struct passing), preferring pointer-based
> protocols which reduce the number of copies.  But I think
> it's important to identify native data (in C or another schema)
> which is inherently by-value, and to more aggressively
> copy and mirror such values when there is an advantage.
> Moving to an enclosing scope is sometimes more efficient if
> we copy the payload bits from inside a larger block in the
> child scope into a larger block in the parent scope.  Even
> better, if the payload bits have an unknown lifetime, we
> can copy them to the heap-scope and let the GC worry
> about them.  These optimizations are only unlocked when
> we can mark data elements as being by-value (and when
> we have an accurate layout).  I think this is a goal to keep
> an eye on.
>
> — John



More information about the panama-dev mailing list