From c53f41e7014b3aa0f1ed0507756528a8cd3ef56b Mon Sep 17 00:00:00 2001 From: Vishal Kumar Singh Date: Thu, 7 May 2026 23:17:05 +0530 Subject: [PATCH] Avoid 'Text file busy' race when starting KafkaContainer Fixes #11682. The container's command waits for /tmp/testcontainers_start.sh to appear, then exec's it. containerIsStarting writes the script via a single Container Archive PUT. On busy hosts the directory entry becomes visible before the docker engine has released the writer, and the immediate exec fails with ETXTBSY (exit 126). Switch to a two-file handshake: write the starter script first, then a separate /tmp/testcontainers_start.ready marker. The wait loop polls the marker instead of the script. By the time the second copyFileToContainer returns, the script's writer is fully closed, so the subsequent exec is safe. The marker file is never executed, so its own copy can race with the directory listing harmlessly. --- .../main/java/org/testcontainers/kafka/KafkaContainer.java | 6 ++++++ .../src/main/java/org/testcontainers/kafka/KafkaHelper.java | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaContainer.java b/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaContainer.java index 375fd132f6c..36118d0092c 100644 --- a/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaContainer.java +++ b/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaContainer.java @@ -72,6 +72,12 @@ protected void containerIsStarting(InspectContainerResponse containerInfo) { command += "/etc/kafka/docker/run \n"; copyFileToContainer(Transferable.of(command, 0777), STARTER_SCRIPT); + // Marker file: created after the starter script copy has fully released its + // writer, so the wait loop in KafkaHelper.COMMAND only proceeds when the + // script is safe to execve. Without this, on busy hosts the loop can see the + // starter script entry before docker-cp has closed it and fail with + // "Text file busy" (exit 126). + copyFileToContainer(Transferable.of(""), KafkaHelper.STARTER_SCRIPT_READY_FLAG); } /** diff --git a/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaHelper.java b/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaHelper.java index 61e790d474f..766554dd1cf 100644 --- a/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaHelper.java +++ b/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaHelper.java @@ -25,10 +25,12 @@ class KafkaHelper { static final String STARTER_SCRIPT = "/tmp/testcontainers_start.sh"; + static final String STARTER_SCRIPT_READY_FLAG = "/tmp/testcontainers_start.ready"; + static final String[] COMMAND = { "sh", "-c", - "while [ ! -f " + STARTER_SCRIPT + " ]; do sleep 0.1; done; " + STARTER_SCRIPT, + "while [ ! -f " + STARTER_SCRIPT_READY_FLAG + " ]; do sleep 0.1; done; " + STARTER_SCRIPT, }; static final WaitStrategy WAIT_STRATEGY = Wait.forLogMessage(".*Transitioning from RECOVERY to RUNNING.*", 1);