Skip to content

Fix ExecutorService thread leak in TransactionTestDSL#6175

Merged
duncdrum merged 3 commits intoeXist-db:developfrom
joewiz:bugfix/transaction-test-thread-leak
Mar 25, 2026
Merged

Fix ExecutorService thread leak in TransactionTestDSL#6175
duncdrum merged 3 commits intoeXist-db:developfrom
joewiz:bugfix/transaction-test-thread-leak

Conversation

@joewiz
Copy link
Copy Markdown
Member

@joewiz joewiz commented Mar 25, 2026

Summary

TransactionTestDSL.execute() creates two ExecutorService instances but only shuts them down in the catch block (error path). On the happy path, both executors leak — their non-daemon threads accumulate across test classes in the same surefire fork.

The fix moves shutdownNow() to a finally block.

How it was found

Thread dump captured during a full mvn test -pl exist-core run showed 10 leaked db.exist.transaction-test-dsl.transaction-*-schedule threads from 5 ConcurrentTransactionsTest invocations, all in WAITING (parking) state at ThreadPoolExecutor.getTask(). These non-daemon threads prevented clean JVM exit after all tests completed.

What changed

TransactionTestDSL.java — Moved t1ExecutorService.shutdownNow() and t2ExecutorService.shutdownNow() from catch to finally so executor cleanup happens on both success and error paths.

Related

Test plan

  • Full mvn test -pl exist-core — 6533 tests, 0 failures, BUILD SUCCESS
  • CI passes on all platforms

🤖 Generated with Claude Code

TransactionTestDSL.execute() creates two single-thread ExecutorService
instances but only shuts them down in the catch block (error path).
On the happy path, both executors leak — their non-daemon threads
accumulate across test classes in the same surefire fork and prevent
clean JVM exit.

Move shutdownNow() calls to a finally block so executors are always
cleaned up regardless of success or failure.

Found via thread dump during full test suite run: 10 leaked
"transaction-test-dsl" threads from 5 ConcurrentTransactionsTest
invocations were still alive at fork shutdown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@joewiz joewiz requested a review from a team as a code owner March 25, 2026 03:26
Apply Patrick's suggestion to use try-with-resources instead of
explicit shutdownNow() in a finally block. ExecutorService implements
AutoCloseable since Java 19, and close() calls shutdown() followed
by awaitTermination(), which is the correct cleanup for test code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
}
});

//TODO(AR) rather than working with exceptions, it would be better to encapsulate them in a similar way to working on an empty sequence, e.g. could use Either<L,R>???
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment still relevant after the code changes?

The TODO(AR) about using Either<L,R> instead of exceptions was
originally in the catch block. Since the catch block was removed in
the try-with-resources refactor, add "from Future.get()" to clarify
what exceptions the TODO refers to.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@duncdrum duncdrum merged commit 2beeaf8 into eXist-db:develop Mar 25, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants