[foreign] RFR 8217420: Have UniversalNativeInvoker and UniversalUpcallHandler create (and close) the scopes used by boxing code
Jorn Vernee
jbvernee at xs4all.nl
Mon Jan 21 21:51:59 UTC 2019
>> 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.
Actually, now that you mention it, this is only testing for a
fits-in-register struct, not for one being passed by pointer, which is
the case I was trying to solve. I'll take a look at adding more tests
tomorrow (getting tired).
Jorn
Jorn Vernee schreef op 2019-01-21 22:31:
> 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.
>
> 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