From bd7ea64b021992716f1a6e775ae79cfa51cbaf79 Mon Sep 17 00:00:00 2001 From: Constantin Piber <59023762+cpiber@users.noreply.github.com> Date: Sat, 21 Jun 2025 18:02:05 +0200 Subject: [PATCH] Add an abort handler and preload it on Linux (#1456) Some native code (CEF) may cause SIGTRAP to be sent on fatal errors, which brings down the entire server. Instead only kill the thread and attempt to continue. --- .gitignore | 1 + scripts/bundler.sh | 6 +++ scripts/resources/catch_abort.c | 62 ++++++++++++++++++++++++ scripts/resources/deb/install | 1 + scripts/resources/pkg/suwayomi-server.sh | 1 + scripts/resources/suwayomi-server.sh | 1 + 6 files changed, 72 insertions(+) create mode 100644 scripts/resources/catch_abort.c diff --git a/.gitignore b/.gitignore index 0b843148..fbe91129 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ scripts/OpenJDK* scripts/zulu* scripts/electron-* scripts/rcedit-* +scripts/resources/*.so diff --git a/scripts/bundler.sh b/scripts/bundler.sh index e403ffee..aef0a587 100755 --- a/scripts/bundler.sh +++ b/scripts/bundler.sh @@ -38,6 +38,10 @@ main() { download_launcher + if [ ! -f scripts/resources/catch_abort.so ]; then + gcc -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -shared scripts/resources/catch_abort.c -lpthread -o scripts/resources/catch_abort.so + fi + case "$OS" in debian-all) RELEASE="$RELEASE_NAME.deb" @@ -184,6 +188,7 @@ make_linux_bundle() { cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar" cp "scripts/resources/suwayomi-launcher.sh" "$RELEASE_NAME/" cp "scripts/resources/suwayomi-server.sh" "$RELEASE_NAME/" + cp "scripts/resources/catch_abort.so" "$RELEASE_NAME/bin/" tar -I "gzip -9" -cvf "$RELEASE" "$RELEASE_NAME/" } @@ -208,6 +213,7 @@ make_deb_package() { mv "$RELEASE_NAME/Suwayomi-Launcher.jar" "$RELEASE_NAME/$source_dir/Suwayomi-Launcher.jar" cp "$JAR" "$RELEASE_NAME/$source_dir/Suwayomi-Server.jar" copy_linux_package_assets_to "$RELEASE_NAME/$source_dir/" + cp "scripts/resources/catch_abort.so" "$RELEASE_NAME/$source_dir/" tar -I "gzip" -C "$RELEASE_NAME/" -cvf "$upstream_source" "$source_dir" cp -r "scripts/resources/deb/" "$RELEASE_NAME/$source_dir/debian/" diff --git a/scripts/resources/catch_abort.c b/scripts/resources/catch_abort.c new file mode 100644 index 00000000..efe54923 --- /dev/null +++ b/scripts/resources/catch_abort.c @@ -0,0 +1,62 @@ +// Linux only: +// Attempts to catch SIGTRAP, inform Java, then exit the thread instead of bringing down the whole process + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include + +JavaVM *g_vm; + +void load_vm() { + if (g_vm) return; + JavaVM *vms[1]; + jsize n = 0; + // JNI_OnLoad won't be called when loaded via LD_PRELOAD, so attempt to find the VM now + if (JNI_GetCreatedJavaVMs(vms, 1, &n) == JNI_OK && n > 0) { + g_vm = vms[0]; + } +} + +jint throwThreadDeath(JNIEnv *env, char *message) { + char *className = "java/lang/UnknownError"; + jclass exClass = (*env)->FindClass(env, className); + if (exClass == NULL) return JNI_ERR; + return (*env)->ThrowNew(env, exClass, message); +} + +void signalHandler(int signum, siginfo_t* si, void* uc) { + void *retaddrs[64]; + int n = backtrace(retaddrs, sizeof(retaddrs) / sizeof(retaddrs[0])); + printf("\n### ABORT :: Backtrace: ###\n"); + backtrace_symbols_fd(retaddrs, n, STDERR_FILENO); + printf("### ABORT :: Exiting this thread. If this causes problems, please report the above backtrace to Suwayomi. ###\n\n"); + + load_vm(); + if (g_vm) { + JNIEnv *env; + jint getEnvStat = (*g_vm)->GetEnv(g_vm, (void**) &env, JNI_VERSION_1_2); + if (getEnvStat == JNI_EDETACHED) (*g_vm)->AttachCurrentThread(g_vm, (void**) &env, NULL); + jint exStat = throwThreadDeath(env, "SIGTRAP caught"); + if (exStat != 0) printf("Exception throwing failed: %d\n", exStat); + (*g_vm)->DetachCurrentThread(g_vm); + } + pthread_exit(NULL); +} + +__attribute__((constructor)) +void dlmain() { + struct sigaction sa = {0}; + sa.sa_flags = SA_SIGINFO | SA_RESTART; + sa.sa_sigaction = &signalHandler; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGTRAP, &sa, NULL) != 0) { + printf("[FATAL] sigaction failed\n"); + } +} diff --git a/scripts/resources/deb/install b/scripts/resources/deb/install index dd8bd73b..0af50245 100755 --- a/scripts/resources/deb/install +++ b/scripts/resources/deb/install @@ -11,3 +11,4 @@ suwayomi-server.tmpfiles => usr/lib/tmpfiles.d/suwayomi-server.conf suwayomi-server.conf => etc/suwayomi/server.conf suwayomi-server.sh => usr/bin/suwayomi-server suwayomi-launcher.sh => usr/bin/suwayomi-launcher +catch_abort.so usr/share/java/suwayomi-server/bin/ diff --git a/scripts/resources/pkg/suwayomi-server.sh b/scripts/resources/pkg/suwayomi-server.sh index c4e093f5..30792184 100644 --- a/scripts/resources/pkg/suwayomi-server.sh +++ b/scripts/resources/pkg/suwayomi-server.sh @@ -1,3 +1,4 @@ #!/bin/sh +export LD_PRELOAD="/usr/share/java/suwayomi-server/bin/catch_abort.so" exec /usr/bin/java -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar diff --git a/scripts/resources/suwayomi-server.sh b/scripts/resources/suwayomi-server.sh index fb1b1933..8b02f95c 100644 --- a/scripts/resources/suwayomi-server.sh +++ b/scripts/resources/suwayomi-server.sh @@ -1,3 +1,4 @@ #!/bin/sh +export LD_PRELOAD="`realpath ./bin/catch_abort.so`" exec ./jre/bin/java -jar ./bin/Suwayomi-Server.jar