SIGSEGV in upcall_stub_load_target when async callbacks are generated with Arena.ofConfined() #366
Labels
No labels
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
java
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
java-gi/java-gi#366
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
I am building a Kotlin desktop application using java-gi for GIO bindings, and I hit a JVM crash (SIGSEGV) when using File.moveAsync with a progress callback.
Here is the Kotlin code that triggers the crash:
When this runs, the JVM instantly crashes with a segmentation fault. Here is the raw crash log showing the FFM upcall stub failing when GIO tries to invoke the callback from a background thread:
Looking at the generated
File.javabindings, the root cause is how theArenais managed for the callback:Because
moveAsyncis an asynchronous function, it returns immediately, closing the_arena. However, the native GIO operation is still running in the background. When it tries to report progress a few milliseconds later viag_main_context_invoke_full, the upcall stub has already been deallocated, resulting in the SIGSEGV.I understand why the generator does this: the upstream
Gio-2.0.girincorrectly annotates theprogress_callbackforg_file_move_asyncwithscope="call"(unlikeg_file_copy_asyncwhich correctly usesscope="notified"and gets a safeArena.global()).However, generating a try-with-resources confined arena for any parameter in an
_asyncfunction seems inherently unsafe, as the function is guaranteed to return before the native operation completes.Would it be possible for the generator to detect
_asyncfunctions and force a shared/global arena for their callbacks, regardless of the upstream scope annotation? Or is there another recommended way to handle upstream GIR scope bugs for async functions?Thanks for the issue report!
A shared or global arena wouldn't work: the shared arena is closed immediately, and the global arena leaks the allocated memory.
My recommendation is to log an issue upstream with GIO. In the meantime I'll try to think of a way to deal with the issue in Java-GI a bit more gracefully than with a segfault.
Relevant: https://gitlab.gnome.org/GNOME/glib/-/work_items/2085
On second thought, no need to log this upstream. It’s a known issue.
I can work around it in java-gi by using the
callbackarena for theprogressCallbacktoo. Just not sure yet how to best implement it.I ended up implementing this — sharing the primary callback's arena for the progress callback. Works on GNOME 46 with
File.moveAsyncandFile.copyAsync.The idea: if a callback is in an
_asyncfunction and it's not the primary (not ASYNC scope, no destroy notify), reuse the primary's_scopearena. If the primary is null at runtime, register the CLEANER on the progress callback so the arena still gets closed.Here's what I changed:
Parameter.java — detect progress callbacks in async functions:
TypedValueGenerator.java — use primary's arena instead of allocating new:
PreprocessingGenerator.java — skip allocation, add CLEANER fallback:
PostprocessingGenerator.java — skip arena close:
Generated code ends up looking like:
I made two additional refinements
IllegalStateExceptioninstead of usingArena.global()to prevent memory leaks in the edge case of malformed GIR dataisAsync()to usecallableAttrs().finishFunc() != nullinstead of string heuristicYour proposed fix would solve the issue for
copyAsyncandmoveAsync, but I want to also fix the memory leak (the global arena is used in places where the scope = notified).Basically, this line is a workaround that results in a global arena (scope = forever), and it needs to be removed:
return (scope == Scope.NOTIFIED && destroy() == null) ? Scope.FOREVER : scope;I've now implemented this properly with !367.