From 4cbe7cc36045df4e58deb9aa2c0e078acee63998 Mon Sep 17 00:00:00 2001 From: chsami <23292806+chsami@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:48:31 +0200 Subject: [PATCH] fix(microbot): treat interrupted waits as cancellation, not errors Scripts interrupted on stop while waiting on the client thread or the EDT surfaced InterruptedException as ERROR logs and script error telemetry. Log interruption-caused failures at debug in Microbot.log, restore the interrupt flag in showMessage, and skip interrupted root causes in ScriptErrorReporter. Co-Authored-By: Claude Opus 5.5 --- .../client/plugins/microbot/Microbot.java | 19 ++++++- .../diagnostics/ScriptErrorReporter.java | 4 ++ .../microbot/MicrobotLogInterruptTest.java | 54 +++++++++++++++++++ .../diagnostics/ScriptErrorReporterTest.java | 8 +++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 runelite-client/src/test/java/net/runelite/client/plugins/microbot/MicrobotLogInterruptTest.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java index 840cd92abdc..fc951697a85 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java @@ -464,6 +464,9 @@ public static void showMessage(String message) { } else { SwingUtilities.invokeAndWait(messageRunnable); } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + log.debug("Interrupted while displaying message {}", message); } catch (Exception ex) { log.error("Error displaying message {}:", message, ex); } @@ -487,6 +490,9 @@ public static void showMessage(String message, int disposeTime) { } else { SwingUtilities.invokeAndWait(messageRunnable); } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + log.debug("Interrupted while displaying message {}", message); } catch (Exception ex) { log.error("Error displaying message {}:", message, ex); } @@ -714,7 +720,9 @@ public static void log(String message, Level level, Exception ex) { log.warn(message); break; case ERROR: - if (ex != null) { + if (isInterruption(ex)) { + log.debug(message, ex); + } else if (ex != null) { log.error(message, ex); } else { log.error(message); @@ -729,6 +737,15 @@ public static void log(String message, Level level, Exception ex) { } } + static boolean isInterruption(Throwable ex) { + for (Throwable t = ex; t != null; t = t.getCause() == t ? null : t.getCause()) { + if (t instanceof InterruptedException) { + return true; + } + } + return false; + } + /** * Opens a pop‑up interface with the specified title and description. *

diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/diagnostics/ScriptErrorReporter.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/diagnostics/ScriptErrorReporter.java index ad4424a41de..0f1b5f44985 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/diagnostics/ScriptErrorReporter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/diagnostics/ScriptErrorReporter.java @@ -89,6 +89,10 @@ protected void append(ILoggingEvent event) } IThrowableProxy root = rootCause(event.getThrowableProxy()); + if (root != null && InterruptedException.class.getName().equals(root.getClassName())) + { + return; + } List frames = frames(root); String fingerprint = root == null ? event.getLoggerName() + "|" + scrub(event.getMessage()).replaceAll("\\d+", "#") diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/microbot/MicrobotLogInterruptTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/microbot/MicrobotLogInterruptTest.java new file mode 100644 index 00000000000..e68525c4f3e --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/microbot/MicrobotLogInterruptTest.java @@ -0,0 +1,54 @@ +package net.runelite.client.plugins.microbot; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; +import static org.junit.Assert.assertEquals; + +public class MicrobotLogInterruptTest +{ + private Logger logger; + private ListAppender appender; + private Level previousLevel; + + @Before + public void setUp() + { + logger = (Logger) LoggerFactory.getLogger(Microbot.class); + previousLevel = logger.getLevel(); + logger.setLevel(Level.DEBUG); + appender = new ListAppender<>(); + appender.start(); + logger.addAppender(appender); + } + + @After + public void tearDown() + { + logger.detachAppender(appender); + logger.setLevel(previousLevel); + } + + @Test + public void interruptedClientThreadWaitIsNotLoggedAsError() + { + Microbot.logStackTrace("AttackNpcScript", new RuntimeException("Interrupted waiting for client thread", new InterruptedException())); + + assertEquals(1, appender.list.size()); + assertEquals(Level.DEBUG, appender.list.get(0).getLevel()); + } + + @Test + public void otherFailuresStillLogError() + { + Microbot.logStackTrace("AttackNpcScript", new IllegalStateException("boom")); + + assertEquals(1, appender.list.size()); + assertEquals(Level.ERROR, appender.list.get(0).getLevel()); + } +} diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/microbot/diagnostics/ScriptErrorReporterTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/microbot/diagnostics/ScriptErrorReporterTest.java index 4e2b75375a7..a7b044f932a 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/microbot/diagnostics/ScriptErrorReporterTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/microbot/diagnostics/ScriptErrorReporterTest.java @@ -193,6 +193,14 @@ public void requeuesRejectedBatch() assertEquals(2, captor.getValue().getAsJsonArray("errors").get(0).getAsJsonObject().get("count").getAsInt()); } + @Test + public void ignoresInterruptedWaits() + { + log(Level.ERROR, "AttackNpcScript", new RuntimeException("Interrupted waiting for client thread", new InterruptedException())); + log(Level.ERROR, "Error displaying message {}:", new InterruptedException()); + assertEquals(0, reporter.pendingCount()); + } + @Test public void groupsMessagesThatOnlyDifferInNumbers() {