[foreign] RFR 8217420: Have UniversalNativeInvoker and UniversalUpcallHandler create (and close) the scopes used by boxing code

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Mon Jan 21 21:48:12 UTC 2019


On 21/01/2019 21:31, Jorn Vernee wrote:
> Maurizio Cimadamore schreef op 2019-01-21 22:16:
>> Hi Jorn,
>> I think I agree with the spirit of the fix. I like that this makes the
>> scopes stricter - which ended up uncovering some issues.
>>
>> Again, I have to point out that this fix only restrict things for
>> universal invoker mode - I believe that when using the direct
>> invocation scheme, parameters to upcalls will be passed in leaky
>> scopes, which will not reveal the clang-related failure.
>
> Sorry about that, I wasn't sure before if I was supposed to make 
> changes to the direct invoker as well, since I would have no way of 
> testing those changes locally. (I've put it on my checklist for the 
> future).
>
>> I think I'd like to see a specific test for lifecycle of callback
>> args, so that we can easily run it with all possible invoker modes and
>> make sure it passes.
>
> I added a test for that (CallScopeTest), is there something else you'd 
> want to have tested? AFAICT this lifecycle problem currently only 
> exists with structs.

That's good - I missed that earlier. I guess we need to make sure that 
the same semantics is applied on all platforms; I'm a bit uncertain on 
how that's gonna work for the direct invoker (or the linkToNative 
invoker, for what it's worth) - as these invokers are based on the 
assumption that arguments can be converted into their native form on the 
fly - I'm not sure in those invoker it would be easy to do something 
such as creating a call-wide scope (in either direction).

I can try and take a look. I guess I could use the 
MethodHandles.tryFinally combinator to make sure the scope is closed 
after the call (and also inject a Scope prefix parameter on all adapter 
handles), but I'd also have to check what happens to performances.

Maurizio

>
> Jorn
>
>> Maurizio
>>
>> On 21/01/2019 18:13, Jorn Vernee wrote:
>>> Hi, another patch :)
>>>
>>> From the bug description:
>>>
>>> Currently, when (un-)boxing code needs to do an allocation a Scope 
>>> is created for that, and this Scope is then leaked.
>>>
>>> For an up-/down-call there are 2 kinds of scopes; 1) for values 
>>> being passed as arguments by value, which are valid for the duration 
>>> of the call. 2) for values being returned, which are up to the 
>>> caller to manage.
>>>
>>> We can do better than the current scheme of always leaking, by 
>>> having UniversalUpcallHandler and UniversalNativeInvoker create both 
>>> of these types of scopes, pass them to boxing code, and then close 
>>> scopes of type 1. when the call is finished.
>>>
>>> This will reduce the amount of memory being leaked, and could also 
>>> help identify bugs where references to temporary objects are leaked.
>>>
>>> ---
>>>
>>> After implementing this, it turns out that UniversalUpcallHandler 
>>> only needs a Scope for the arguments being passed to the java method 
>>> being called. For returns there should be no allocations; either the 
>>> value is being passed in registers, or written to a pointer passed 
>>> in by the caller.
>>>
>>> I did discover a problem with this patch in the TestJextractFFI 
>>> patch code:
>>>
>>>     public Stream<Cursor> children() {
>>>         final ArrayList<Cursor> ar = new ArrayList<>();
>>>         // FIXME: need a way to pass ar down as user data d
>>>         try (Scope sc = Scope.newNativeScope()) {
>>>             LibClang.lib.clang_visitChildren(cursor, 
>>> sc.allocateCallback((c, p, d) -> {
>>>                 ar.add(new Cursor(c));
>>>                 return LibClang.lib.CXChildVisit_Continue();
>>>             }), Pointer.nullPointer());
>>>             return ar.stream();
>>>         }
>>>     }
>>>
>>> According to C semantics, the argument to the lambda `c`, which is a 
>>> CXCursor struct being passed in by-value, is only valid for the 
>>> duration of the call. The Windows ABI depends on this fact, because 
>>> it allocates the struct on the stack of the caller, and then passes 
>>> a pointer to the callee (the lambda in this case). I think on SysV a 
>>> by-value struct is always passed in register + stack?
>>>
>>> But, the `c` argument is being leaked from the lambda, by wrapping 
>>> it in a Cursor and storing it in a list in the enclosing scope. When 
>>> the call to the lambda ends, the struct is deallocated and the 
>>> pointer that is stored on the Java side is now dangling. I've 
>>> initially solved the crash this was causing by defensively copying 
>>> the struct in the Windows boxing code (instead of just wrapping the 
>>> passed pointer), but I realized this also means that a user would 
>>> have to manually release a struct passed into an upcall to avoid a 
>>> memory leak.
>>>
>>> We have 2 options here:
>>>
>>> 1.) Go with C style. Struct arguments are valid only during an 
>>> upcall, and we have to manually copy them if we want to 'leak' them 
>>> from the scope of the upcall.
>>> 2.) Go with status quo. Struct doesn't need to be copied manually 
>>> when saving it outside the scope of the upcall. The copy is 
>>> defensively done in Windows boxing code instead, and sometimes 
>>> redundant. Only on Windows (for now), struct arguments to an upcall 
>>> have to be manually released to avoid a memory leak.
>>>
>>> I think 1 is the better option here since this adheres to the 
>>> expectations native code has when calling into Java. I've reflected 
>>> that in the patch as well, but of course I'm interested in hearing 
>>> other opinions as well :) Note that the crash I was seeing before no 
>>> longer happens since the Scope used in the boxing code is now closed 
>>> after the call, and we get an exception instead.
>>>
>>> Bug: https://bugs.openjdk.java.net/browse/JDK-8217420
>>> Webrev: 
>>> http://cr.openjdk.java.net/~jvernee/panama/webrevs/8217420/webrev.00/
>>>
>>> Cheers,
>>> Jorn


More information about the panama-dev mailing list