Skip to content

Hangs on close RTCPeerConnection #234

Open

Description

@sayat2002

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.

Activity

  1. AlemZzz commented on Apr 1, 2026

    @AlemZzz

    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.

  2. AlemZzz commented on Apr 13, 2026

    @AlemZzz

    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!

  3. devopvoid commented on Sep 13, 2026

    @devopvoid
    Owner

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions