You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Creating native AOT using gluon and GraalVM 21 CE.
Works fine when running project on java but does not close peer connection when running native exe.
Seems threading problem. Just stuck on peerConnection.close();
applicationg is not responding and have to kill it in task manager.
I ran into the same issue. After spending a long time investigating, I was finally able to narrow it down to the peerConnection.close() call, and then found this issue.
My setup: Spring Boot application packaged as a native executable using GraalVM Native Image. Works perfectly when running as a JAR, but hangs indefinitely on peerConnection.close() when running as a native exe on Windows.
One more thing worth mentioning: the issue is not 100% reproducible. Unfortunately, I have not been able to find a specific scenario that triggers it consistently.
I captured a process dump while the hang was occurring and analyzed it with WinDbg. The thread stack trace below shows the call stuck inside the native close method:
Plaintext
26 Id: 85c4.af74
00 ntdll!NtWaitForSingleObject+0x14
01 KERNELBASE!WaitForSingleObjectEx+0xaf
02 webrtc_java_windows_x86_64!...+0xfe566
03 webrtc_java_windows_x86_64!...+0x10bf71
04 webrtc_java_windows_x86_64!Java_dev_onvoid_webrtc_RTCPeerConnection_close+0x99
05 smarthub_remote!main+0xbbb030
Additionally, several other threads (69–72) are all stuck at LdrpDrainWorkQueue, waiting on the DLL loader lock, which appears to be part of the deadlock chain.
Hoping this dump helps with the investigation. Happy to provide more details if needed.
Hey, not sure if you've resolved this yet, but I think I have. I'm not sure what version of GraalVM you're using — in my case the issue was happening with GraalVM JDK21, and switching to GraalVM JDK25 seems to have fixed it. I consulted Claude to understand why, and here's the likely explanation:
With GraalVM JDK21, a large number of JDK internal classes — including thread management components — are initialized at build time and their state is baked directly into the native executable. When WebRTC's internal threads (networkThread/signalingThread/workerThread) attempt to exit during pc->Close(), Windows TLS (Thread Local Storage) cleanup callbacks require the DLL loader lock. At the same time, the GraalVM JDK21 runtime also holds this lock during its own thread cleanup phase, causing both sides to wait on each other — a classic deadlock.
GraalVM JDK25 introduced the --future-defaults=run-time-initialize-jdk mechanism, which shifts the initialization of JDK thread-related components from build time to runtime. This results in a more natural Windows thread lifecycle, avoiding the loader lock contention with WebRTC's internal threads during shutdown.
Hope this helps!
Thanks both for the detailed report and the follow-up analysis — that WinDbg trace is really helpful.
I checked the recent thread-safety/JNI work that just landed on main (#283, #269) and neither addresses this. Both fix real bugs — dangling native references, missing null-JNIEnv checks, double DeleteLocalRef, incorrect thread detach on JVM shutdown — but none of them touch what's happening here.
@AlemZzz's analysis matches the stack trace: this looks like a Windows loader-lock deadlock between GraalVM Native Image's own build-time-initialized thread bookkeeping and WebRTC's internal threads (network/signaling/worker) doing their own shutdown work inside pc->Close(). Both sides end up waiting on the same loader lock. That's a GraalVM Native Image / Windows interaction rather than a bug in webrtc-java's JNI bridge I can point to and fix directly — the library doesn't control what GraalVM initializes at build time vs. run time.
The workaround you found — GraalVM JDK25 with --future-defaults=run-time-initialize-jdk — lines up with that root cause, since it moves the JDK's thread-related initialization to run time and avoids the lock contention.
Leaving this open since it's not resolved by anything merged so far. If anyone's stuck on JDK21 and can't upgrade, it'd help to know whether manually marking the relevant JDK thread classes as run-time-initialized (via native-image's --initialize-at-run-time flag) works as a narrower workaround.
Activity
AlemZzz commentedon Apr 1, 2026
I ran into the same issue. After spending a long time investigating, I was finally able to narrow it down to the peerConnection.close() call, and then found this issue.
My setup: Spring Boot application packaged as a native executable using GraalVM Native Image. Works perfectly when running as a JAR, but hangs indefinitely on peerConnection.close() when running as a native exe on Windows.
One more thing worth mentioning: the issue is not 100% reproducible. Unfortunately, I have not been able to find a specific scenario that triggers it consistently.
I captured a process dump while the hang was occurring and analyzed it with WinDbg. The thread stack trace below shows the call stuck inside the native close method:
Plaintext
26 Id: 85c4.af74
00 ntdll!NtWaitForSingleObject+0x14
01 KERNELBASE!WaitForSingleObjectEx+0xaf
02 webrtc_java_windows_x86_64!...+0xfe566
03 webrtc_java_windows_x86_64!...+0x10bf71
04 webrtc_java_windows_x86_64!Java_dev_onvoid_webrtc_RTCPeerConnection_close+0x99
05 smarthub_remote!main+0xbbb030
Additionally, several other threads (69–72) are all stuck at LdrpDrainWorkQueue, waiting on the DLL loader lock, which appears to be part of the deadlock chain.
Hoping this dump helps with the investigation. Happy to provide more details if needed.
AlemZzz commentedon Apr 13, 2026
Hey, not sure if you've resolved this yet, but I think I have. I'm not sure what version of GraalVM you're using — in my case the issue was happening with GraalVM JDK21, and switching to GraalVM JDK25 seems to have fixed it. I consulted Claude to understand why, and here's the likely explanation:
With GraalVM JDK21, a large number of JDK internal classes — including thread management components — are initialized at build time and their state is baked directly into the native executable. When WebRTC's internal threads (networkThread/signalingThread/workerThread) attempt to exit during pc->Close(), Windows TLS (Thread Local Storage) cleanup callbacks require the DLL loader lock. At the same time, the GraalVM JDK21 runtime also holds this lock during its own thread cleanup phase, causing both sides to wait on each other — a classic deadlock.
GraalVM JDK25 introduced the --future-defaults=run-time-initialize-jdk mechanism, which shifts the initialization of JDK thread-related components from build time to runtime. This results in a more natural Windows thread lifecycle, avoiding the loader lock contention with WebRTC's internal threads during shutdown.
Hope this helps!
devopvoid commentedon Sep 13, 2026
Thanks both for the detailed report and the follow-up analysis — that WinDbg trace is really helpful.
I checked the recent thread-safety/JNI work that just landed on
main(#283, #269) and neither addresses this. Both fix real bugs — dangling native references, missing null-JNIEnvchecks, doubleDeleteLocalRef, incorrect thread detach on JVM shutdown — but none of them touch what's happening here.@AlemZzz's analysis matches the stack trace: this looks like a Windows loader-lock deadlock between GraalVM Native Image's own build-time-initialized thread bookkeeping and WebRTC's internal threads (network/signaling/worker) doing their own shutdown work inside
pc->Close(). Both sides end up waiting on the same loader lock. That's a GraalVM Native Image / Windows interaction rather than a bug in webrtc-java's JNI bridge I can point to and fix directly — the library doesn't control what GraalVM initializes at build time vs. run time.The workaround you found — GraalVM JDK25 with
--future-defaults=run-time-initialize-jdk— lines up with that root cause, since it moves the JDK's thread-related initialization to run time and avoids the lock contention.Leaving this open since it's not resolved by anything merged so far. If anyone's stuck on JDK21 and can't upgrade, it'd help to know whether manually marking the relevant JDK thread classes as run-time-initialized (via native-image's
--initialize-at-run-timeflag) works as a narrower workaround.