Two problems in the same few lines of StacktalePanel. Both are the kind of thing JetBrains plugin review flags, and the first one is silent.
1. One exception kills auto-refresh for the rest of the session
// StacktalePanel.java:106-111
alarm.addRequest(() -> {
refresh();
schedulePoll(); // never reached if refresh() throws
}, POLL_MILLIS);
refresh() calls findLog() at :114 outside any try, and findLog() hits FilenameIndex.getVirtualFilesByName at :157-158 with no dumb-mode guard.
Open a project whose errors-ai.log is not at the project root (so the early return at :154-155 misses) while the IDE is still indexing, and you get an IndexNotReadyException — the poll loop then dies for the rest of the session, silently. The plugin becomes manual-refresh-only with no indication anything went wrong. On the constructor path (:76) the same throw breaks createToolWindowContent outright.
StReportParser.parse at :131 is outside the try as well, so any parser bug has the same effect. ReportNavigator.java:21-22 has the identical unguarded index query, invoked from the EDT double-click handler.
Fix:
alarm.addRequest(() -> {
try { refresh(); } catch (Throwable t) { LOG.warn(t); }
finally { schedulePoll(); }
}, POLL_MILLIS);
and wrap both index queries in DumbService.getInstance(project).runReadActionInSmartMode(...), or bail out early when DumbService.isDumb() and retry on the next poll.
2. Blocking file I/O and index queries on the EDT, every 3 seconds
The alarm is created with Alarm.ThreadToUse.SWING_THREAD (:56), and the work it schedules is: findLog() (Files.isRegularFile + FilenameIndex) at :114, Files.readString at :124, a full-content String.equals at :128, and a full parse at :131. All on the UI thread.
The library's default file cap is 5 MB (ReportPipeline.DEFAULT_MAX_FILE_SIZE_MB), so at the cap that is a 5 MB read plus a 5 MB string comparison plus a full parse on the EDT every 3 seconds — typing stutter, and modern IntelliJ raises "Slow operations are prohibited on EDT" for the FileBasedIndex access.
Fix: switch to Alarm.ThreadToUse.POOLED_THREAD, do the read and parse there, then invokeLater to touch the Swing model. While you are in there, two cheap wins: skip the read entirely when Files.getLastModifiedTime is unchanged, and skip the poll when the tool window is not visible — right now it runs forever on an idle IDE.
Also in this area
refresh() swallows read failures with a bare return (:125-127). Transient failures are genuinely fine to ignore — the file is mid-write — but a persistent one (a Windows AV lock, EACCES) leaves a frozen list forever with no signal, and the toolbar Refresh button calls the same swallowing path, so it looks like nothing happens. After N consecutive failures, put the reason in the detail pane.
Related: Files.readString(..., UTF_8) throws MalformedInputException when a read lands mid-write of a ━ (3 UTF-8 bytes). Decoding with a CharsetDecoder set to REPLACE makes that case degrade instead of failing.
Verify
./gradlew runIde: open a project mid-indexing with the log outside the root and confirm the tool window still populates once indexing completes; confirm no "slow operations" warnings in the log during normal use.
Two problems in the same few lines of
StacktalePanel. Both are the kind of thing JetBrains plugin review flags, and the first one is silent.1. One exception kills auto-refresh for the rest of the session
refresh()callsfindLog()at:114outside any try, andfindLog()hitsFilenameIndex.getVirtualFilesByNameat:157-158with no dumb-mode guard.Open a project whose
errors-ai.logis not at the project root (so the early return at:154-155misses) while the IDE is still indexing, and you get anIndexNotReadyException— the poll loop then dies for the rest of the session, silently. The plugin becomes manual-refresh-only with no indication anything went wrong. On the constructor path (:76) the same throw breakscreateToolWindowContentoutright.StReportParser.parseat:131is outside the try as well, so any parser bug has the same effect.ReportNavigator.java:21-22has the identical unguarded index query, invoked from the EDT double-click handler.Fix:
and wrap both index queries in
DumbService.getInstance(project).runReadActionInSmartMode(...), or bail out early whenDumbService.isDumb()and retry on the next poll.2. Blocking file I/O and index queries on the EDT, every 3 seconds
The alarm is created with
Alarm.ThreadToUse.SWING_THREAD(:56), and the work it schedules is:findLog()(Files.isRegularFile+FilenameIndex) at:114,Files.readStringat:124, a full-contentString.equalsat:128, and a full parse at:131. All on the UI thread.The library's default file cap is 5 MB (
ReportPipeline.DEFAULT_MAX_FILE_SIZE_MB), so at the cap that is a 5 MB read plus a 5 MB string comparison plus a full parse on the EDT every 3 seconds — typing stutter, and modern IntelliJ raises "Slow operations are prohibited on EDT" for theFileBasedIndexaccess.Fix: switch to
Alarm.ThreadToUse.POOLED_THREAD, do the read and parse there, theninvokeLaterto touch the Swing model. While you are in there, two cheap wins: skip the read entirely whenFiles.getLastModifiedTimeis unchanged, and skip the poll when the tool window is not visible — right now it runs forever on an idle IDE.Also in this area
refresh()swallows read failures with a barereturn(:125-127). Transient failures are genuinely fine to ignore — the file is mid-write — but a persistent one (a Windows AV lock,EACCES) leaves a frozen list forever with no signal, and the toolbar Refresh button calls the same swallowing path, so it looks like nothing happens. After N consecutive failures, put the reason in the detail pane.Related:
Files.readString(..., UTF_8)throwsMalformedInputExceptionwhen a read lands mid-write of a━(3 UTF-8 bytes). Decoding with aCharsetDecoderset toREPLACEmakes that case degrade instead of failing.Verify
./gradlew runIde: open a project mid-indexing with the log outside the root and confirm the tool window still populates once indexing completes; confirm no "slow operations" warnings in the log during normal use.