Skip to content

[4.0.x] Fix #12761: do not wait for the terminal on the thread building it - #12854

Merged
gnodet merged 1 commit into
maven-4.0.xfrom
backport/12814-to-4.0.x
Aug 27, 2026
Merged

[4.0.x] Fix #12761: do not wait for the terminal on the thread building it#12854
gnodet merged 1 commit into
maven-4.0.xfrom
backport/12814-to-4.0.x

Conversation

@gnodet

@gnodet gnodet commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Backport of #12814 to maven-4.0.x.

Fixes a startup hang where FastTerminal deadlocks when JLine 4.x logs a JUL warning during native-library probing on the terminal-building thread. The thread parks on its own future when getTerminal() is called re-entrantly through the SLF4J bridge.

The fix returns TYPE_DUMB and a pre-captured writer for the two re-entrant call sites (getType() and writer()), degrading to unstyled text only while the real terminal is still being built.

Cherry-picked cleanly from 02a675d.

…12814)

* Fix #12761: do not wait for the terminal on the thread building it

MessageUtils.systemInstall publishes the FastTerminal before the background
thread has built the real one, and every FastTerminal method delegates through
getTerminal(). Anything that thread logs is therefore rendered through a terminal
that same thread is still producing, and parks on its own future. There is no
lock cycle, so jstack reports no deadlock.

Both the builder callable and the consumer callback are inside that window: the
consumer runs before terminal.complete(term). Two distinct call paths reach it
from a single log statement, the level rendering via toAnsi and the log sink via
terminal.writer(), so guarding one call site is not enough. getTerminal() hands
the building thread a dumb stand-in instead, which unblocks every delegating
method at once. Do not narrow this to a plain-text fallback in the message
builder: the writer path would still park.

The stand-in writes to the System.err captured at construction, not the live one,
which the consumer replaces with a logging-backed stream that would feed the
output back into the logger it came from.

Rendering degrades to unstyled text for the duration of the build, which is what
a dumb terminal would produce anyway. The build thread also becomes a daemon, so
a wedged build cannot keep the JVM alive.

JLine 4.x initialises its native loader during provider probing and logs a
warning when a library candidate fails, which is what made this reachable after
the 3.30.6 to 4.3.1 bump in 4.0.0-rc-6. The failure itself is harmless, JLine
recovers by extracting the bundled library.

* Answer the two terminal calls directly instead of standing in a terminal

Handing the building thread a DumbTerminal meant constructing a terminal inside
terminal construction, on that same thread, in a failure path. It wedged on
ubuntu/jdk-17 when the stand-in was built after the consumer had already run
AnsiConsole.systemInstall, while passing everywhere else.

A log statement asks the terminal for exactly two things, its type while
rendering and its writer while emitting. AttributedCharSequence.toAnsi asks for
the type first and renders plain for a dumb one, so it never reaches any other
method. Answering those two with a constant and a PrintWriter over the captured
stream covers the same paths and cannot block.

The tests now exercise both calls from both halves of the window rather than one
call each.
@gnodet gnodet added this to the 4.0.0-rc-7 milestone Aug 26, 2026
@gnodet gnodet added bug Something isn't working mvn4 labels Aug 26, 2026

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Clean cherry-pick of #12814 deadlock fix to maven-4.0.x. The diff is byte-for-byte identical to the original (only git index hashes differ due to different base). All 21 CI checks pass across the full OS/JDK matrix.

✅ Looks good

  • Re-entrancy detection logic (isBuildThreadWaitingOnItself) is identical to the original
  • Safe fallbacks (TYPE_DUMB for getType(), pre-captured System.err writer for writer()) are correctly preserved
  • Test exercises re-entrant calls from both builder callable and consumer callback with preemptive timeouts
  • maven-3.9.x/maven-3.10.x don't use FastTerminal (JLine 4.x / maven-jline module), so no further backports needed

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

@gnodet
gnodet merged commit 6aa407a into maven-4.0.x Aug 27, 2026
24 checks passed
@gnodet
gnodet deleted the backport/12814-to-4.0.x branch August 27, 2026 00:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working mvn4

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants