Skip to content

Docker Compose ProcessRunner can hang or leave orphan processes#50963

Open
SebTardif wants to merge 1 commit into
spring-projects:mainfrom
SebTardif:fix/processrunner-reader-latch-destroy
Open

Docker Compose ProcessRunner can hang or leave orphan processes#50963
SebTardif wants to merge 1 commit into
spring-projects:mainfrom
SebTardif:fix/processrunner-reader-latch-destroy

Conversation

@SebTardif

Copy link
Copy Markdown
Contributor

Summary

ProcessRunner can hang forever if a stream reader fails, and can leave orphan Docker Compose child processes when waitFor is interrupted.

Problem

ReaderThread only calls CountDownLatch.countDown() on the success path. If reading fails with IOException, or if the optional outputConsumer throws, the latch is never released. After the child process exits, ProcessRunner.run blocks forever on latch.await() in ReaderThread.toString().

Separately, when process.waitFor() is interrupted, the interrupt flag is restored and an exception is thrown (fixed in #50451), but the child process is not destroyed. Docker Compose commands can remain as orphans.

Change

  • Move latch.countDown() into a finally block in ReaderThread.run().
  • Call process.destroy() after restoring the interrupt flag when waitFor is interrupted.

Failure scenario

  1. Docker Compose support runs a process via ProcessRunner.run(consumer, command...).
  2. The consumer throws (or stream read fails) after partial output.
  3. The process exits, but the reader never counts down the latch.
  4. The calling thread blocks indefinitely on toString() / await().

For interrupt: a long-running compose command is interrupted during waitFor; without destroy(), the child keeps running after the Spring Boot thread has aborted.

Origin

Introduced with Docker Compose support in 842e17ecedaf (2023-04-07, Moritz Halbritter). Present for ~3 years. Related prior fix: #50451 (interrupt flag restore only).

Validation

  • Red/green:
    • runWhenOutputConsumerThrowsDoesNotHang: timed out without the latch fix; passes with it.
    • runWhenInterruptedDestroysChildProcess: child sleep stayed alive without destroy; process is dead with the fix.
  • ./gradlew :core:spring-boot-docker-compose:test --tests ProcessRunnerTests
  • checkFormatMain / checkFormatTest / checkstyleMain / checkstyleTest

Related

Release the output reader latch in a finally block so a failing stream
read or output consumer cannot leave ProcessRunner.run blocked on
CountDownLatch.await after the child process has exited.

Destroy the child process when waitFor is interrupted so Docker Compose
commands are not left running as orphans.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants