Guard console appender against writing to a closed terminal during shutdown#579
Open
mvanhorn wants to merge 1 commit into
Open
Guard console appender against writing to a closed terminal during shutdown#579mvanhorn wants to merge 1 commit into
mvanhorn wants to merge 1 commit into
Conversation
…utdown When a dedicated server is stopped, MinecraftServer.stopServer() keeps logging after the JLine terminal has been closed, so log events are dispatched to a dead appender and log4j prints 'IllegalStateException: Terminal has been closed' on every clean stop. print() synchronizes on the instance while close()/setReader() synchronize on the class, so the terminal/reader fields can be cleared (or the terminal closed) concurrently. Capture them into locals and catch IllegalStateException, falling back to the saved stdout PrintStream. Also drop the stale reader in close() so a later event cannot reuse a reader bound to the closed terminal. Fixes CleanroomMC#566
7 tasks
Rongmario
reviewed
Jun 27, 2026
| stdout.print(text); | ||
| } | ||
|
|
||
| stdout.print(text); |
Member
There was a problem hiding this comment.
Should put this in the catch block, otherwise it’d print with both the Terminal’s writer and to stdout unconditionally?
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.
Summary
Stops the
ERROR An exception occurred processing Appender Console / java.lang.IllegalStateException: Terminal has been closedstack trace that log4j prints on every clean dedicated-server stop (#566). The shutdown already completed correctly, but the spurious trace was logged each timestopwas typed.Why this matters
As the maintainer confirmed on the issue,
MinecraftServer.stopServer()keeps logging ("Stopping server", "Saving players", ...) after the JLine terminal has already been closed during shutdown, so those log events are dispatched to a dead appender.TerminalConsoleAppender.print()issynchronizedon the instance, whileclose()andsetReader()aresynchronizedon the class. Because they use different monitors, the staticterminal/readerfields can be cleared, or the terminal closed, concurrently with aprint()that has already passed its null check. The call then lands onreader.printAbove(...)/terminal.writer().print(...)against a closed terminal and throwsIllegalStateException: Terminal has been closed.The fix:
print()captures the sharedterminal/readerinto locals (so a concurrent clear cannot turn the guard into an NPE) and wraps the JLine write intry/catch (IllegalStateException), falling back to the savedstdoutPrintStreamexactly as the no-terminal branch already does. Late shutdown log lines still reach stdout instead of failing the appender.close()now also nulls the stalereaderafter closing the terminal, so a later event cannot reuse aLineReaderbound to the closed terminal.Normal runtime logging is unchanged: while the terminal is open and a
LineReaderis attached,reader.printAbove(...)is still used.Testing
TerminalHandleronly clears the reader in itsfinallyafter the read loop exits, so duringstopServer()the reader/terminal can still be set while the terminal is being closed - the exact race the catch now absorbs.TerminalConsoleAppenderand that the open-terminal path (reader.printAbove/terminal.writer()) is preserved; only the closed-terminal case is rerouted to stdout.Fixes #566