Fix #12912: use a dumb fallback terminal for all build-thread reentrant calls - #12921
Conversation
…nt calls The previous fix for #12761 guarded only writer() and getType() against reentrant calls from the build thread, because those were the two methods a log statement exercises. JLine 4.4.0's FFM provider initialization (CLibrary.<clinit>) can reach back through other terminal methods (getSize, getName, encoding, etc.) on the build thread, causing the same deadlock on JDK 25 in container environments. Construct a DumbTerminal before the build thread starts and return it from getTerminal() when the build thread would otherwise wait on its own future. This covers every delegate method at once, regardless of which call path JLine's provider probing takes. The DumbTerminal is constructed directly rather than via TerminalBuilder, which probes for grapheme-cluster support and can block on a null input stream in JLine 4.4.0. The existing writer() and getType() guards remain for explicitness: they take priority (checked before getTerminal()) and guarantee the fallback writer uses the pre-captured System.err. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gnodet
left a comment
There was a problem hiding this comment.
Well-targeted fix for a JDK 25 startup deadlock caused by JLine 4.4.0's FFM provider initialization reaching back through arbitrary terminal methods on the build thread. The approach of using a pre-built DumbTerminal as a catch-all fallback in getTerminal() is sound and minimal.
Key observations:
- The previous fix (#12761) only guarded
writer()andgetType(), but JLine 4.4.0's FFM provider init (CLibrary.<clinit>) reaches back through other terminal methods (getSize,getName,encoding, etc.), causing the same deadlock on JDK 25 in containers. - The existing
writer()andgetType()special-case guards are retained for explicitness — they take priority over the newgetTerminal()fallback and guarantee the fallback writer uses the pre-capturedSystem.err. This layered approach is clean. - The
IOExceptionfrom theDumbTerminalconstructor is caught correctly; extremely unlikely in practice withInputStream.nullInputStream(). - Tests follow the existing pattern with
assertTimeoutPreemptivelyfor deadlock detection.
🔀 Forward-port Status
master branch has a simpler FastTerminal without the #12761 buildThread tracking infrastructure. The same deadlock likely affects master too. A combined fix (including the #12761 build-thread tracking + this #12912 DumbTerminal fallback) should be forward-ported to master. Direct cherry-pick will not apply.
The maven-3.9.x and maven-3.10.x branches do not contain the maven-jline module and are not affected.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
…nt calls (#12921) (#12934) The previous fix for #12761 guarded only writer() and getType() against reentrant calls from the build thread, because those were the two methods a log statement exercises. JLine 4.4.0's FFM provider initialization (CLibrary.<clinit>) can reach back through other terminal methods (getSize, getName, encoding, etc.) on the build thread, causing the same deadlock on JDK 25 in container environments. Construct a DumbTerminal before the build thread starts and return it from getTerminal() when the build thread would otherwise wait on its own future. This covers every delegate method at once, regardless of which call path JLine's provider probing takes. The DumbTerminal is constructed directly rather than via TerminalBuilder, which probes for grapheme-cluster support and can block on a null input stream in JLine 4.4.0. The existing writer() and getType() guards remain for explicitness: they take priority (checked before getTerminal()) and guarantee the fallback writer uses the pre-captured System.err. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
maven-3.10.x branch use JLine 3.x after Migrate from JAnsi to JLine in: There is a https://github.com/apache/maven/tree/maven-3.10.x/maven-jline module |
Summary
writer()andgetType()against reentrant calls from the build thread, but JLine 4.4.0's FFM provider initialization (CLibrary.<clinit>) reaches back through other terminal methods (getSize,getName,encoding, etc.), causing the same deadlockDumbTerminaldirectly (bypassingTerminalBuilder's grapheme-cluster probing) before the build thread starts, and returns it fromgetTerminal()whenever the build thread would otherwise wait on its own future — covering all delegate methods at onceFixes #12912
Test plan
FastTerminalReentrancyTesttests forwriter()andgetType()still passarbitraryTerminalMethodsFromTheBuilderDoNotDeadlockverifiesgetSize()from the builder callback does not deadlockarbitraryTerminalMethodsFromTheConsumerDoNotDeadlockverifiesgetName()from the consumer callback does not deadlock🤖 Generated with Claude Code