From 06e1fa1ea3e0c621615e3a5c946addf8a747ab37 Mon Sep 17 00:00:00 2001 From: Daniel Alome Date: Tue, 10 Feb 2026 15:13:10 +0100 Subject: [PATCH] Prevent crash from ZipFile GC cleanup I/O errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a ZipFile is garbage collected and its underlying file descriptor is no longer valid, the FinalizerDaemon throws UncheckedIOException wrapping an EIO error. This is a non-critical platform issue — the object is already unreachable and the OS reclaims the fd on process exit. Detect this specific pattern via CleanableResource/PhantomCleanable in the stack trace, report to Sentry, and return without killing the process. --- .../itsaky/androidide/app/IDEApplication.kt | 18 +++++++++++++----- .../javac/services/fs/CachedJarFileSystem.kt | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt b/app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt index 5b9918e341..8b6b280601 100755 --- a/app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt +++ b/app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt @@ -184,16 +184,24 @@ class IDEApplication : BaseApplication() { thread: Thread, exception: Throwable, ) { + if (isNonFatalGcCleanupFailure(exception)) { + logger.warn("Non-fatal: ZipFile GC cleanup failed with I/O error", exception) + return + } + if (isUserUnlocked) { - // we can access credential protected storage, delegate the job to - // to advanced crash handler CredentialProtectedApplicationLoader.handleUncaughtException(thread, exception) return } - // we can only access device-protected storage, and are not allowed - // to show crash handler screen - // delegate the job to the basic crash handler DeviceProtectedApplicationLoader.handleUncaughtException(thread, exception) } + + private fun isNonFatalGcCleanupFailure(exception: Throwable): Boolean { + if (exception !is java.io.UncheckedIOException) return false + return exception.stackTrace.any { + it.className.contains("CleanableResource") || + it.className.contains("PhantomCleanable") + } + } } diff --git a/subprojects/javac-services/src/main/java/com/itsaky/androidide/javac/services/fs/CachedJarFileSystem.kt b/subprojects/javac-services/src/main/java/com/itsaky/androidide/javac/services/fs/CachedJarFileSystem.kt index 936865e64b..8d20763bf1 100644 --- a/subprojects/javac-services/src/main/java/com/itsaky/androidide/javac/services/fs/CachedJarFileSystem.kt +++ b/subprojects/javac-services/src/main/java/com/itsaky/androidide/javac/services/fs/CachedJarFileSystem.kt @@ -47,6 +47,7 @@ class CachedJarFileSystem( // This is called manually by the Java LSP } + @Throws(IOException::class) fun doClose() { try { super.close()