From 0deaf3cb4cef0456cd1c12d2f859cc64ff493a8e Mon Sep 17 00:00:00 2001 From: Hal Eisen Date: Thu, 23 Apr 2026 20:34:21 -0700 Subject: [PATCH 1/2] Another attempt to fix all the TimeoutException / unknown finalizer crashes --- .../itsaky/androidide/app/IDEApplication.kt | 12 ++++ .../androidide/zipfs2/ZipFileSystem.java | 12 ++-- .../androidide/idetooltips/ToolTipManager.kt | 71 +++++++++---------- .../fragments/resources/ColorFragment.java | 10 ++- .../fragments/resources/StringFragment.java | 10 ++- .../layouteditor/managers/ValuesManager.java | 7 +- .../vectormaster/VectorMasterDrawable.java | 8 +++ .../vectormaster/VectorMasterView.java | 8 +++ 8 files changed, 86 insertions(+), 52 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 1253cdf342..3369b0edc3 100755 --- a/app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt +++ b/app/src/main/java/com/itsaky/androidide/app/IDEApplication.kt @@ -211,6 +211,12 @@ class IDEApplication : return } + if (isFinalizerWatchdogTimeout(thread, exception)) { + logger.warn("Non-fatal: FinalizerWatchdogDaemon timeout (suppressed crash)", exception) + Sentry.captureException(exception) + return + } + if (isUserUnlocked) { CredentialProtectedApplicationLoader.handleUncaughtException(thread, exception) return @@ -226,4 +232,10 @@ class IDEApplication : it.className.contains("PhantomCleanable") } } + + private fun isFinalizerWatchdogTimeout(thread: Thread, exception: Throwable): Boolean { + if (exception !is java.util.concurrent.TimeoutException) return false + return thread.name.contains("FinalizerWatchdogDaemon") || + exception.stackTrace.any { it.className.contains("Daemons\$FinalizerWatchdogDaemon") } + } } diff --git a/composite-builds/build-deps/java-compiler/src/main/java/com/itsaky/androidide/zipfs2/ZipFileSystem.java b/composite-builds/build-deps/java-compiler/src/main/java/com/itsaky/androidide/zipfs2/ZipFileSystem.java index 5fa6cfb925..c5dc18dfae 100644 --- a/composite-builds/build-deps/java-compiler/src/main/java/com/itsaky/androidide/zipfs2/ZipFileSystem.java +++ b/composite-builds/build-deps/java-compiler/src/main/java/com/itsaky/androidide/zipfs2/ZipFileSystem.java @@ -1089,13 +1089,11 @@ private static void closeChannelQuietly(SeekableByteChannel channel) { @SuppressWarnings("deprecation") protected void finalize() throws IOException { - try { - close(); - } catch (Throwable ignored) { - // On devices with flaky storage, close() can throw UncheckedIOException - // wrapping EIO. Swallow all errors during finalization to prevent - // killing the FinalizerDaemon thread. - } + // No-op: do NOT perform I/O during finalization. + // On slow storage, close() can exceed the 10-second FinalizerWatchdogDaemon + // timeout, causing a fatal TimeoutException crash (Sentry APPDEVFORALL-E8). + // All ZipFileSystems should be closed deterministically via close()/doClose(). + // The OS reclaims file descriptors at process exit regardless. } // Reads len bytes of data from the specified offset into buf. diff --git a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/ToolTipManager.kt b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/ToolTipManager.kt index 63ecf19557..b902b131e6 100644 --- a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/ToolTipManager.kt +++ b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/ToolTipManager.kt @@ -97,52 +97,51 @@ object TooltipManager { try { val db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY) - var cursor = db.rawQuery(QUERY_LAST_CHANGE, arrayOf()) - cursor.moveToFirst() - - lastChange = "${cursor.getString(0)} ${cursor.getString(1)}" - - Log.d(TAG, "last change is '${lastChange}'.") + db.use { database -> + database.rawQuery(QUERY_LAST_CHANGE, arrayOf()).use { c -> + c.moveToFirst() + lastChange = "${c.getString(0)} ${c.getString(1)}" + } - cursor = db.rawQuery(QUERY_TOOLTIP, arrayOf(tag, category)) + Log.d(TAG, "last change is '${lastChange}'.") - when (cursor.count) { - 0 -> throw NoTooltipFoundException(category, tag) - 1 -> { /* Expected case, continue processing */ - } + database.rawQuery(QUERY_TOOLTIP, arrayOf(tag, category)).use { c -> + when (c.count) { + 0 -> throw NoTooltipFoundException(category, tag) + 1 -> { /* Expected case, continue processing */ + } - else -> throw DatabaseCorruptionException( - "Multiple tooltips found for category='$category', tag='$tag' (found ${cursor.count} rows). " + - "Each category/tag combination should be unique." - ) - } + else -> throw DatabaseCorruptionException( + "Multiple tooltips found for category='$category', tag='$tag' (found ${c.count} rows). " + + "Each category/tag combination should be unique." + ) + } - cursor.moveToFirst() + c.moveToFirst() - rowId = cursor.getInt(0) - tooltipId = cursor.getInt(1) - summary = cursor.getString(2) - detail = cursor.getString(3) + rowId = c.getInt(0) + tooltipId = c.getInt(1) + summary = c.getString(2) + detail = c.getString(3) + } - cursor = db.rawQuery(QUERY_TOOLTIP_BUTTONS, arrayOf(tooltipId.toString())) + database.rawQuery(QUERY_TOOLTIP_BUTTONS, arrayOf(tooltipId.toString())).use { c -> + while (c.moveToNext()) { + buttons.add( + Pair( + c.getString(0), + "http://localhost:6174/" + c.getString(1) + ) + ) + } + } - while (cursor.moveToNext()) { - buttons.add( - Pair( - cursor.getString(0), - "http://localhost:6174/" + cursor.getString(1) - ) + Log.d( + TAG, + "For tooltip ${tooltipId}, retrieved ${buttons.size} buttons. They are $buttons." ) } - Log.d( - TAG, - "For tooltip ${tooltipId}, retrieved ${buttons.size} buttons. They are $buttons." - ) - - cursor.close() - db.close() - } catch (e: Exception) { Log.e( TAG, diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/ColorFragment.java b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/ColorFragment.java index 2682cabcea..af543add0e 100644 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/ColorFragment.java +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/ColorFragment.java @@ -36,6 +36,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -81,9 +82,12 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat * @param filePath = Current project colors file path; */ public void loadColorsFromXML(String filePath) throws FileNotFoundException { - InputStream stream = new FileInputStream(filePath); - colorParser = new ValuesResourceParser(stream, ValuesResourceParser.TAG_COLOR); - colorList = colorParser.getValuesList(); + try (InputStream stream = new FileInputStream(filePath)) { + colorParser = new ValuesResourceParser(stream, ValuesResourceParser.TAG_COLOR); + colorList = colorParser.getValuesList(); + } catch (IOException e) { + e.printStackTrace(); + } } private void setupDialogViews(LayoutValuesItemDialogBinding bind) { diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/StringFragment.java b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/StringFragment.java index 3502cfc937..652dc13cc2 100644 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/StringFragment.java +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/StringFragment.java @@ -31,6 +31,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -75,9 +76,12 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { * @param filePath = Current project strings file path; */ public void loadStringsFromXML(String filePath) throws FileNotFoundException { - InputStream stream = new FileInputStream(filePath); - stringParser = new ValuesResourceParser(stream, ValuesResourceParser.TAG_STRING); - stringList = stringParser.getValuesList(); + try (InputStream stream = new FileInputStream(filePath)) { + stringParser = new ValuesResourceParser(stream, ValuesResourceParser.TAG_STRING); + stringList = stringParser.getValuesList(); + } catch (IOException e) { + e.printStackTrace(); + } } public void addString() { diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/managers/ValuesManager.java b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/managers/ValuesManager.java index 2f48345187..c57675f397 100644 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/managers/ValuesManager.java +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/managers/ValuesManager.java @@ -5,21 +5,22 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.IOException; public class ValuesManager { public static String getValueFromResources(String tag, String value, String path) { String resValueName = value.substring(value.indexOf("/") + 1); String result = null; - try { - ValuesResourceParser parser = new ValuesResourceParser(new FileInputStream(path), tag); + try (FileInputStream stream = new FileInputStream(path)) { + ValuesResourceParser parser = new ValuesResourceParser(stream, tag); for (ValuesItem item : parser.getValuesList()) { if (item.name.equals(resValueName)) { result = item.value; } } - } catch (FileNotFoundException e) { + } catch (IOException e) { e.printStackTrace(); } return result; diff --git a/vectormaster/src/main/java/org/appdevforall/codeonthego/vectormaster/VectorMasterDrawable.java b/vectormaster/src/main/java/org/appdevforall/codeonthego/vectormaster/VectorMasterDrawable.java index bfdb47795c..510eceef68 100644 --- a/vectormaster/src/main/java/org/appdevforall/codeonthego/vectormaster/VectorMasterDrawable.java +++ b/vectormaster/src/main/java/org/appdevforall/codeonthego/vectormaster/VectorMasterDrawable.java @@ -400,6 +400,14 @@ private void buildVectorModel() { } } catch (XmlPullParserException | IOException e) { e.printStackTrace(); + } finally { + if (vectorStream != null) { + try { + vectorStream.close(); + } catch (IOException ignored) { + } + vectorStream = null; + } } } diff --git a/vectormaster/src/main/java/org/appdevforall/codeonthego/vectormaster/VectorMasterView.java b/vectormaster/src/main/java/org/appdevforall/codeonthego/vectormaster/VectorMasterView.java index 30409ba4ff..6002dc3b95 100644 --- a/vectormaster/src/main/java/org/appdevforall/codeonthego/vectormaster/VectorMasterView.java +++ b/vectormaster/src/main/java/org/appdevforall/codeonthego/vectormaster/VectorMasterView.java @@ -610,6 +610,14 @@ void buildVectorModel() { } } catch (XmlPullParserException | IOException e) { e.printStackTrace(); + } finally { + if (vectorStream != null) { + try { + vectorStream.close(); + } catch (IOException ignored) { + } + vectorStream = null; + } } } From 7ca4d56fd8cb0332b8c6d32363f2b56320a16832 Mon Sep 17 00:00:00 2001 From: Hal Eisen Date: Thu, 23 Apr 2026 23:21:33 -0700 Subject: [PATCH 2/2] Re-throw FileNotFoundException instead of over-eagerly swallowing it to comply with declaration and snackbar usage --- .../layouteditor/fragments/resources/ColorFragment.java | 2 ++ .../layouteditor/fragments/resources/StringFragment.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/ColorFragment.java b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/ColorFragment.java index af543add0e..e3ab300cd0 100644 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/ColorFragment.java +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/ColorFragment.java @@ -85,6 +85,8 @@ public void loadColorsFromXML(String filePath) throws FileNotFoundException { try (InputStream stream = new FileInputStream(filePath)) { colorParser = new ValuesResourceParser(stream, ValuesResourceParser.TAG_COLOR); colorList = colorParser.getValuesList(); + } catch (FileNotFoundException e) { + throw e; } catch (IOException e) { e.printStackTrace(); } diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/StringFragment.java b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/StringFragment.java index 652dc13cc2..88ac30d15f 100644 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/StringFragment.java +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/StringFragment.java @@ -79,6 +79,8 @@ public void loadStringsFromXML(String filePath) throws FileNotFoundException { try (InputStream stream = new FileInputStream(filePath)) { stringParser = new ValuesResourceParser(stream, ValuesResourceParser.TAG_STRING); stringList = stringParser.getValuesList(); + } catch (FileNotFoundException e) { + throw e; } catch (IOException e) { e.printStackTrace(); }