resource scopes and close actions

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Wed Feb 9 02:42:15 UTC 2022


On 09/02/2022 02:19, Michael Zucchi wrote:
>
> Ok cheers that should probably do.  I only just noticed you can use it 
> as an addressable a couple of days ago but using it for non-symbol 
> things isn't obvious especially given it's name and it's documentation.
>
> So it seems a 'typed' handle-like object that wanted to use a scope on 
> close action will have to:
>
> - use NativeSymbol as the native 'this', which contains a copy of the 
> MemoryAddress, a string and the scope handle.
> - keep a private copy of the MemoryAddress so any close function can 
> use it after the scope is closed (whether it be in the object or in a 
> lambda, it may as well be in the object)
yes
> - (based on the proposed api change) also keep a copy of the 
> ResourceScope if it wants to be able to propagate it automatically 
> which would often be desirable (... i think).
stay tuned on this. We're looking into ways to mitigate the impact of 
the proposed changes which will not require you to keep a ResourceScope 
around (and use a segment instead as a "proxy" of the temporal bounds 
you want to use e.g. to create another segment).
>
> Doesn't seem too elegant but that should work?  I'll explore that with 
> opencl as i've the most experience with that api and it has some 
> consistent conventions that i think will mesh well.
>
> e.g. as a first thought, does this look about right?
>
> An example case is CLCommandQueue which is an object which is created 
> by and belongs to a CLContext and can be closed either by an explicit 
> unref or when the context is unreffed.
>
> public CLCommandQueue {
>     NativeSymbol symbol;
>     ResourceScope scope;
>     MemoryAddress addr;
>
>    // package private
>     close() {
>          clReleaseCommandQueue$MH.invokeExact((Addressable)addr);
>     }
> }
This looks mostly ok - not sure whether you need to keep the `addr` 
around. I would have expected `addr` to only really be used in a close 
action, so when you create CLCommandQueue (e.g. CLCommandQueue::create). 
Am I missing something?
>
> public CLContext {
>     NativeSymbol symbol;
>     ResourceScope scope;
>     MemoryAddress addr;
>
>    public static CLContext create(..., ResourceScope scope) { ... }
>
>    public CLCommandQueue createCommandQueue(, ...scope) {
>        MemoryAddress addr = clContextCreateCommandQueue(...);
>        CLCommandQueue q = CLCommandQueue.create(addr, scope);
>
>        if (scope != this.scope)
>            this.scope.addCloseAction(() -> scope.close());
>        scope.addOnCloseAction(()-> q.close());
>
>        return q;
>    }
> }
>
> I have to think about it a bit more though, there are some cases where 
> this might not be a good fit.  Or you end up having create a scope for 
> single objects and now have to keep track of two references (or add 
> some public close() which calls scope.close()).  Also, dynamic method 
> handles add a wrinkle if they are a release() function.

Creating a scope for single objects doesn't seem optimal - scopes are 
mostly intended to aggregate resources together (which share a common 
lifecycle).

Overall though, there's no real right or wrong - the API gives you 
option when it comes to dealing with lifetime of entities - it is mostly 
a choice of the API designer whether to take advantage of that, or 
ignore that (in order to expose a simpler API). I also expect that some 
libraries might be more scope-friendly than others. Given that C is so 
varied when it comes to management of resource lifecycle, that should 
also not come too much as a surprise.

>
> My JNI code uses reference queues so both explicit and gc-release are 
> possible, this is particularly handy for lambda-based processing 
> chains where there's no 'object' to hold references around and 
> ownership is basically passed by argument in a graph of unpredictable 
> ways.  A common shared scope with a cleaner should work i think but 
> that would require a different design.

In general, starting off with single scope, GC-managed, sounds like a 
good default choice - and then sprinkle scopes as you see needs to 
deallocate more promptly and/or add more fine-grained lifecycle 
management to your library (assuming that is required). Btw, note that a 
GC-backed scope can still be released explicitly, if you so wish (so you 
have both options at once).

Cheers
Maurizio

>
>  !Z
>
> On 7/2/22 21:19, Maurizio Cimadamore wrote:
>> If you want a "scoped" memory address, please look at NativeSymbol. 
>> This is used by the lookup functions provided by the JDK, but is also 
>> useful to capture an address and associate a scope with it. This 
>> allows you to model everything with one field (NativeSymbol) and then 
>> no liveness check is needed, just pass the symbol to the method 
>> handle, and standard safety checks will apply.
>>
>> Maurizio
>>
>> On 05/02/2022 09:16, Michael Zucchi wrote:
>>> OpenCL (& Vulkan) does everything via handles (typed anonymous 
>>> pointers), so presumably represented by MemoryAddress.  What 
>>> approach to use here to make them scope-safe?  Have explicit scope 
>>> tests before using them in calls?
>>>
>>> If one wanted a scope-protected MemoryAddress this seems to be the 
>>> only solution in the present api:
>>>
>>> CLCommandQueue {
>>>   ResourceScope scope;
>>>   MemoryAddress handle;
>>>
>>>   void enqueueNDRangeKernel(...) {
>>>     try (ResourceScope scope = ResourceScope.newConfinedScope()) {
>>>        scope.keepAlive(this.scope);
>>> enqueueNDRangeKernel$MH.invokeExact((Addressable)handle, ...);
>>>     } catch (IllegalStateException ex) {
>>>     }
>>>   }
>>> }
>>>
>>> Actually it's a bit worse because there will be handles (possibly in 
>>> different scopes) in the argument list and they would all need to be 
>>> checked in the same way.
>>>
>>> So for practicality they would have to be MemorySegment (ideally 
>>> length=0) since they are always checked? 
>


More information about the panama-dev mailing list