Skip to content

fix(kafka): use sentinel file to eliminate starter-script race condition (#11682) - #11957

Open
klouds27 wants to merge 1 commit into
testcontainers:mainfrom
klouds27:feat/11682-kafka-starter-script-race
Open

fix(kafka): use sentinel file to eliminate starter-script race condition (#11682)#11957
klouds27 wants to merge 1 commit into
testcontainers:mainfrom
klouds27:feat/11682-kafka-starter-script-race

Conversation

@klouds27

@klouds27 klouds27 commented Aug 3, 2026

Copy link
Copy Markdown

The Kafka containers start the broker by writing a startup script into the container via copyFileToContainer, then executing it with a shell loop that polls for the file. Because the loop checks only for file existence, it can detect the script the instant its inode is created and attempt to execute it before the write completes. On busy hosts this produces Text file busy (ETXTBSY) and a container exit code of 126.

This was confirmed in #11682 by the reporter and a second commenter. The suggestion of checking file size with [ -s file ] was also tested and failed for the same reason: size becomes non-zero before the write finishes.

The fix introduces a sentinel file (/tmp/testcontainers_start.sh.ready) that is created via execInContainer("touch", ...) only after copyFileToContainer returns. The wait loop now polls for the sentinel instead of the script itself. Because copyFileToContainer completes fully before the sentinel is touched, the loop cannot proceed until the script is both fully written and closed.

The change is applied consistently to all affected containers: org.testcontainers.kafka.KafkaHelper (shared COMMAND constant), org.testcontainers.kafka.KafkaContainer, org.testcontainers.kafka.ConfluentKafkaContainer, and the deprecated org.testcontainers.containers.KafkaContainer.

Fixes #11682

…ion (testcontainers#11682)

copyFileToContainer writes the startup script into the container over a Docker
API call. On busy hosts the wait loop can detect the file the moment its inode
is created, before the write completes, and attempt to execute an incomplete
file. This produces ETXTBSY and exit code 126.

The fix adds a sentinel file (STARTER_SCRIPT + ".ready") that is touched via
execInContainer only after copyFileToContainer returns. The wait loop now checks
for the sentinel instead of the script itself, so execution cannot begin until
the script is fully written.

Applied to KafkaHelper, org.testcontainers.kafka.KafkaContainer,
org.testcontainers.kafka.ConfluentKafkaContainer, and the deprecated
org.testcontainers.containers.KafkaContainer.

Signed-off-by: klouds27 <adalwolf@gmail.com>
@klouds27
klouds27 marked this pull request as ready for review August 6, 2026 19:04
@klouds27
klouds27 requested a review from a team as a code owner August 6, 2026 19:04
Copilot AI lite review requested due to automatic review settings August 6, 2026 19:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a Kafka container startup race where the entrypoint loop could execute /tmp/testcontainers_start.sh before it was fully written, intermittently causing Text file busy (exit code 126). It does this by introducing and waiting on a sentinel file that is only created after the startup script copy completes.

Changes:

  • Add a sentinel file path (/tmp/testcontainers_start.sh.ready) and update the shared Kafka startup COMMAND to wait for the sentinel instead of the script.
  • After copying the startup script into the container, create the sentinel file so the entrypoint loop can safely proceed.
  • Apply the same pattern to the deprecated org.testcontainers.containers.KafkaContainer implementation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
modules/kafka/src/main/java/org/testcontainers/kafka/KafkaHelper.java Introduces the sentinel constant and updates the shared startup command to wait on it.
modules/kafka/src/main/java/org/testcontainers/kafka/KafkaContainer.java Touches the sentinel after copying the starter script to prevent early execution.
modules/kafka/src/main/java/org/testcontainers/kafka/ConfluentKafkaContainer.java Touches the sentinel after copying the starter script to prevent early execution.
modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java Adds sentinel + waits on it in the entry command, and touches it after script copy (deprecated container).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +76 to +80
try {
execInContainer("touch", KafkaHelper.STARTER_SCRIPT_SENTINEL);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
Comment on lines +70 to +74
try {
execInContainer("touch", KafkaHelper.STARTER_SCRIPT_SENTINEL);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
Comment on lines +206 to +210
try {
execInContainer("touch", STARTER_SCRIPT_SENTINEL);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
Comment on lines +28 to 34
static final String STARTER_SCRIPT_SENTINEL = STARTER_SCRIPT + ".ready";

static final String[] COMMAND = {
"sh",
"-c",
"while [ ! -f " + STARTER_SCRIPT + " ]; do sleep 0.1; done; " + STARTER_SCRIPT,
"while [ ! -f " + STARTER_SCRIPT_SENTINEL + " ]; do sleep 0.1; done; " + STARTER_SCRIPT,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Kafka container fails with 'Container exited with code 126' due to a race between command wait loop and script installation

2 participants