Fix crash: guard executor submits against teardown race#324
Closed
patrickrb wants to merge 1 commit into
Closed
Conversation
afterDecode() runs on the FT8 decode thread and submits a QTH-lookup task to getQTHThreadPool after every decode pass. That pool (and sendWaveDataThreadPool) is shut down in MainViewModel.onCleared() when the ViewModel is destroyed on app close or connection reset/reconnect. A decode result landing in the same instant the pool is shut down reaches ExecutorService.execute() on a terminated pool and throws RejectedExecutionException on the decode thread, crashing the app (observed on a FlexRadio network reconnect). Add ExecutorUtils.safeExecute(), which skips submission when the pool is shut down and swallows the residual RejectedExecutionException race, dropping the follow-up task instead of crashing. Route all three submit sites (QTH lookup + two network/CAT TX-audio sends) through it. Covered by ExecutorUtilsTest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #324 +/- ##
=========================================
Coverage 11.51% 11.51%
Complexity 113 113
=========================================
Files 88 88
Lines 12466 12466
Branches 2230 2230
=========================================
Hits 1435 1435
Misses 10900 10900
Partials 131 131 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR prevents a teardown race from crashing the decode thread by guarding ExecutorService.execute() submissions against executors that are concurrently being shut down during MainViewModel.onCleared().
Changes:
- Added
ExecutorUtils.safeExecute(...)to skip submissions on shutdown pools and swallowRejectedExecutionException. - Routed QTH/grid lookup and TX-audio send submissions in
MainViewModelthroughsafeExecute. - Added unit tests validating
safeExecutebehavior for active/shutdown/null inputs.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| ft8af/app/src/main/java/com/k1af/ft8af/ExecutorUtils.java | Introduces safeExecute helper to avoid crashes from executor teardown races. |
| ft8af/app/src/main/java/com/k1af/ft8af/MainViewModel.java | Uses safeExecute for decode follow-up and TX-audio executor submissions. |
| ft8af/app/src/test/java/com/k1af/ft8af/ExecutorUtilsTest.java | Adds unit tests for safeExecute behavior across pool/task states. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+35
to
+46
| @Test | ||
| public void shutdownPool_dropsTaskAndReturnsFalse() { | ||
| ExecutorService pool = Executors.newSingleThreadExecutor(); | ||
| pool.shutdown(); | ||
| // The pre-fix code path: execute() on a terminated pool threw and crashed | ||
| // the decode thread. safeExecute must swallow it and report the drop. | ||
| boolean accepted = ExecutorUtils.safeExecute(pool, () -> { | ||
| throw new AssertionError("task must not run on a shut-down pool"); | ||
| }); | ||
| assertThat(accepted).isFalse(); | ||
| } | ||
|
|
Owner
Author
|
Consolidated into #329 (single combined PR) as requested. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The app crashes with
RejectedExecutionExceptionafter a decode when the connection is reset/reconnected or the app is closing. Observed live on a FlexRadio network reconnect:afterDecode()runs on the FT8 decode thread and submits a QTH/grid-lookup task togetQTHThreadPoolafter every pass. That pool — andsendWaveDataThreadPool— is shut down inMainViewModel.onCleared()when the ViewModel is destroyed (app close, connection reset/reconnect). A decode result that lands in the same instant the pool is shut down reachesExecutorService.execute()on a terminated pool and throws on the decode thread, crashing the app.Fix
Add
ExecutorUtils.safeExecute(pool, task), which:pool.isShutdown(), andRejectedExecutionExceptionfor the narrow check-then-execute race,dropping the follow-up task instead of crashing. The dropped work is non-essential (a grid lookup, or a TX-audio send during teardown).
Routed all three submit sites through it: the QTH lookup (line 594) and the two network/CAT TX-audio sends (lines 689, 717).
Test
ExecutorUtilsTest(pure JUnit4 + Truth, no runner needed): active pool runs the task and returns true; shut-down pool drops the task and returns false without throwing; null pool/task return false.🤖 Generated with Claude Code