From 4ecd44f05fc82d3a62d4d50f30d06b14db9c5550 Mon Sep 17 00:00:00 2001 From: rf Date: Mon, 4 May 2026 17:04:07 +0200 Subject: [PATCH] Fix process-wide signal handler override in JNI_OnLoad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JNI_OnLoad called sigaction() to set SIGUSR1 and SIGUSR2 to SIG_IGN, overwriting the JVM's signal chain process-wide without saving the previous handler (third argument NULL instead of &oldAction). On Linux, JavaScriptCore (WebKit) uses SIGUSR1 to suspend threads during garbage collection: the GC thread sends SIGUSR1 via pthread_kill to all threads, and each thread's handler calls sem_post to acknowledge. With SIG_IGN, the acknowledgement never arrives and the GC thread waits indefinitely, causing a permanent hang in any JavaFX WebView application that uses jSerialComm. Remove all sigaction() calls from JNI_OnLoad. Replace with per-thread pthread_sigmask() calls in the two event reader threads, which blocks only the signals that could interrupt serial port I/O in those specific threads without affecting the rest of the process. SIGUSR1 and SIGUSR2 are intentionally NOT blocked. These threads must remain suspendable by the JVM GC — blocking them would prevent garbage collection from working on those threads. The other signals (SIGHUP, SIGTTOU, SIGTTIN) are also unnecessary process-wide because serial ports are opened with O_NOCTTY, which prevents them from becoming the controlling terminal. Fixes #631 --- src/main/c/Posix/SerialPort_Posix.c | 34 +++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/main/c/Posix/SerialPort_Posix.c b/src/main/c/Posix/SerialPort_Posix.c index 7df4073c..3d13f44c 100644 --- a/src/main/c/Posix/SerialPort_Posix.c +++ b/src/main/c/Posix/SerialPort_Posix.c @@ -135,6 +135,16 @@ static void enumeratePorts(void) // Event listening threads void* eventReadingThread1(void *serialPortPointer) { + // Block signals that could be triggered by serial port operations in this thread only, + // avoiding process-wide signal handler changes that would interfere with the JVM + sigset_t signalMask; + sigemptyset(&signalMask); + sigaddset(&signalMask, SIGHUP); + sigaddset(&signalMask, SIGIO); + sigaddset(&signalMask, SIGTTOU); + sigaddset(&signalMask, SIGTTIN); + pthread_sigmask(SIG_BLOCK, &signalMask, NULL); + // Make this thread immediately and asynchronously cancellable int oldValue; serialPort *port = (serialPort*)(intptr_t)serialPortPointer; @@ -185,6 +195,16 @@ void* eventReadingThread1(void *serialPortPointer) void* eventReadingThread2(void *serialPortPointer) { + // Block signals that could be triggered by serial port operations in this thread only, + // avoiding process-wide signal handler changes that would interfere with the JVM + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGHUP); + sigaddset(&mask, SIGIO); + sigaddset(&mask, SIGTTOU); + sigaddset(&mask, SIGTTIN); + pthread_sigmask(SIG_BLOCK, &mask, NULL); + // Make this thread immediately and asynchronously cancellable int oldValue; serialPort *port = (serialPort*)(intptr_t)serialPortPointer; @@ -333,20 +353,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) eventFlagsField = (*env)->GetFieldID(env, serialCommClass, "eventFlags", "I"); if (checkJniError(env, __LINE__ - 1)) return JNI_ERR; - // Disable handling of various POSIX signals - sigset_t blockMask; - memset(&blockMask, 0, sizeof(blockMask)); - struct sigaction ignoreAction = { 0 }; - ignoreAction.sa_handler = SIG_IGN; - ignoreAction.sa_mask = blockMask; - sigaction(SIGIO, &ignoreAction, NULL); - sigaction(SIGHUP, &ignoreAction, NULL); - sigaction(SIGCONT, &ignoreAction, NULL); - sigaction(SIGUSR1, &ignoreAction, NULL); - sigaction(SIGUSR2, &ignoreAction, NULL); - sigaction(SIGTTOU, &ignoreAction, NULL); - sigaction(SIGTTIN, &ignoreAction, NULL); - // Initialize the critical section lock pthread_mutex_init(&criticalSection, NULL); classInitialized = 1;