Fix process hang on /exit due to bubbletea renderer deadlock#2269
Merged
dgageot merged 2 commits intodocker:mainfrom Mar 28, 2026
Merged
Fix process hang on /exit due to bubbletea renderer deadlock#2269dgageot merged 2 commits intodocker:mainfrom
dgageot merged 2 commits intodocker:mainfrom
Conversation
Add tests that demonstrate the bubbletea v2 renderer mutex deadlock that causes the process to hang after /exit when stdout blocks. - TestExitDeadlock_BlockedStdout: proves p.Run() hangs when the renderer's flush goroutine holds the mutex during a blocked stdout write, preventing the final render after tea.Quit. - TestExitSafetyNet_BlockedStdout: verifies a safety-net timer can break the deadlock by calling an exit function. - TestExitSafetyNet_GracefulShutdown: verifies the safety net does not fire during normal (non-blocked) shutdown. - TestCleanupAll_SpawnsSafetyNet: verifies cleanupAll spawns the safety-net goroutine that calls exitFunc. This test currently FAILS because the safety net is not yet implemented. Refs: docker#2268 Assisted-By: docker-agent
When the user types /exit, bubbletea's cursedRenderer can deadlock: its ticker-driven flush goroutine holds a mutex while blocked writing to stdout, and the event loop's final render() call after tea.Quit blocks on the same mutex. p.Run() never returns and the process hangs. Add a safety-net goroutine in cleanupAll() that calls os.Exit(0) after 5 seconds, guaranteeing the process terminates even when bubbletea's renderer is deadlocked. Fixes docker#2268 Assisted-By: docker-agent
dgageot
approved these changes
Mar 27, 2026
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.
Fixes #2268
Problem
After typing
/exit, the docker-agent process hangs indefinitely. The rootcause is a mutex deadlock inside bubbletea v2's
cursedRenderer: therenderer's ticker-driven
flushgoroutine holds a mutex while blocked on awritesyscall to stdout (terminal buffer full), and the event loop's finalrender()call aftertea.Quitblocks trying to acquire the same mutex.See #2268 for full diagnostic including goroutine dumps and sequence diagram.
Solution
Add a safety-net goroutine in
cleanupAll()that callsos.Exit(0)after a5-second timeout. This guarantees the process terminates even when bubbletea's
renderer is deadlocked.
The timeout and exit function are exposed as package-level variables
(
shutdownTimeout,exitFunc) so tests can override them.Tests
TestExitDeadlock_BlockedStdout— proves the bubbletea deadlock exists(p.Run never returns when stdout blocks during the final render)
TestCleanupAll_SpawnsSafetyNet— verifiescleanupAllspawns thesafety-net goroutine that calls
exitFuncafter the timeoutTestExitSafetyNet_BlockedStdout— end-to-end: blocked stdout + safetynet fires
TestExitSafetyNet_GracefulShutdown— verifies the safety net does NOTfire during normal shutdown (no blocked stdout)