Prevent crash from ZipFile GC cleanup I/O errors#957
Conversation
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.
📝 WalkthroughRelease Notes
Risks & Best Practices Violations
WalkthroughAdded non-fatal GC cleanup failure detection to IDEApplication that intercepts UncheckedIOException stack traces containing CleanableResource or PhantomCleanable references, short-circuiting crash handling for these known I/O errors. Also annotated CachedJarFileSystem.doClose() with Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt`:
- Around line 187-190: The non-fatal GC cleanup branch in
isNonFatalGcCleanupFailure currently only logs a warning via logger.warn but
does not report to Sentry as promised; modify the branch in IDEApplication (the
block that checks isNonFatalGcCleanupFailure(exception)) to call the Sentry
reporting API (e.g., Sentry.captureException or your app's Sentry wrapper
method) with the exception before returning so the error is both logged and sent
to Sentry. Ensure the capture call happens immediately before the existing
return and uses the same exception object passed into
isNonFatalGcCleanupFailure.
🧹 Nitpick comments (2)
subprojects/javac-services/src/main/java/com/itsaky/androidide/javac/services/fs/CachedJarFileSystem.kt (1)
50-57:@Throws(IOException::class)is contradicted by the internalcatch.
doClose()catches and swallows everyIOExceptionon line 54, so noIOExceptionwill ever propagate to callers. The@Throwsannotation generates athrows IOExceptionclause in bytecode, which misleads Java callers into thinking they need to handle it.Either remove the annotation (if the intent is to always swallow), or re-throw the exception after logging (if the intent is to let callers handle it).
Option A – remove the annotation (current behavior, swallow):
- `@Throws`(IOException::class) fun doClose() {Option B – propagate after logging (match the annotation):
`@Throws`(IOException::class) fun doClose() { try { super.close() } catch (e: IOException) { log.warn("IOException during CachedJarFileSystem close", e) + throw e } }app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt (1)
200-206: Consider also inspecting the cause's stack trace for robustness.Depending on the Android/JDK version, the
CleanableResource/PhantomCleanableframes may appear in the wrappedIOException's stack trace rather than theUncheckedIOException's own. Additionally, checking that the cause is anIOExceptionwould tighten the match.Suggested hardening
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") - } + val framesToCheck = exception.stackTrace.toList() + + (exception.cause?.stackTrace?.toList() ?: emptyList()) + return framesToCheck.any { + it.className.contains("CleanableResource") || + it.className.contains("PhantomCleanable") + } }
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 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.