[Bug]: container logs -f exits immediately with no output when the container's log file is empty
I maintainer di solito rispondono entro 1 giorno
Una pull request collegata è già stata integrata.
- #2377 di @i098 — integrata
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 78/100
Direzione di ricerca
Il bug si trova in tail() in Sources/ContainerCommands/Container/ContainerLogs.swift: il ritorno anticipato su un risultato nil di readToEnd() viene eseguito prima del ramo di follow. Inizia riproducendo il problema con la sequenza minima container run -d seguita da container logs -f descritta nell'issue. È completato quando -f su un log di zero byte si blocca e trasmette le scritture successive, mentre la modalità senza follow e -f su un log non vuoto continuano a funzionare come prima.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Problem
container logs -f exits immediately — no output, no follow, exit code 0 — when the container's log file is still empty at call time. A caller that starts tailing right after container run -d (the normal pattern for a supervised dev service) silently gets nothing, and tooling that treats "the log tail exited" as "the task is done" can go on to tear down a perfectly healthy container.
Real-world impact
A Taskfile-based dev stack ran container run -d --rm --name postgres -e ... -v postgres:/var/lib/postgresql -p 5432:5432 postgres:18-alpine, then tailed with container logs -f postgres, with defer: container stop postgres registered for Ctrl+C cleanup. With a freshly auto-created volume, postgres writes nothing to its log while the container VM boots and initdb runs; the tail hit the zero-byte log and exited within the same second (the deferred container stop fired immediately after, with no log lines in between, and no error reported — i.e. exit 0). The result: postgres was killed ~1s after coming up.
Evidence that postgres did come up in the "failed" run — from the next run's recovery log on the same volume (the volume had been deleted before the failed run, so the timestamp must be from that run):
2026-10-09 15:03:08.142 UTC [27] LOG: database system was interrupted; last known up at 2026-10-09 14:27:23 UTC
2026-10-09 15:03:08.223 UTC [27] LOG: database system was not properly shut down; automatic recovery in progress
14:27:23 is the failed run (stack start 14:27:21, container start step completed at +2s). Re-running the same task dev against the now-initialized volume works: the entrypoint's first line ("Skipping initialization") reaches the log long before initdb would on a fresh volume, the tail's initial read finds it, prints, and follows normally. The only difference between the failing and succeeding runs is whether the volume was fresh.
Minimal repro
container run -d --rm --name logempty alpine sh -c 'sleep 10; echo hello'
sleep 0.5
time container logs -f logempty; echo "exit=$?"
container stop logempty
Expected per the code path below: container logs -f returns within a second, prints nothing, exit=0 — even though the container is still running and emits hello 10s later. Expected per the flag's contract ("Follow log output"): block, then stream hello when it appears.
Root cause
container logs reads the whole log file before ever considering follow:
- The full-file read in
tail(): ContainerLogs.swift#L89-L95 —guard let data = try fh.readToEnd() else { return }, with the in-code comment noting the nil case is a zero-byte read. FileHandle.readToEnd()returnsnilfor a zero-byte file — seeread(upToCount:), which mapslength == 0tonilin the open-source mirror of Foundation (FileHandle.swift#L504-L515); the same nil-on-empty behavior on Darwin Foundation is documented by the comment in the linkedtail()code.- The early
returnsits before the follow branch (ContainerLogs.swift#L105-L109), so-fnever reachesfollowFile. followFileis already built for an empty start — its first act isseekToEnd()(ContainerLogs.swift#L112-L113) — so nothing downstream prevents following an empty log.- The server side just hands back read handles to the on-disk log files (ContainersService.swift#L754-L755), so this is a purely client-side sequencing bug.
Not a duplicate: #1967 and #2022 are about -n line truncation, #2009 is about the on-disk log freezing after an attached client dies. This is about -f not following at all when the file starts empty.
Code evidence is against main at 48f902dc (2026-10-08); the real-world impact was observed on macOS with a current container CLI — the code path above is deterministic for any build containing the linked tail() implementation.
Proposed solution
Make the early return apply only to non-follow mode, so -f falls through to followFile even when the log is empty:
} else if !follow {
// Fast path if all they want is the full file.
guard let data = try fh.readToEnd() else {
return
}
guard let str = String(data: data, encoding: .utf8) else {
throw ContainerizationError(
.internalError,
message: "failed to convert container logs to utf8"
)
}
print(str.trimmingCharacters(in: .newlines))
}
fflush(stdout)
if follow {
setbuf(stdout, nil)
try await Self.followFile(fh: fh)
}
Why this over the alternatives:
- CLI-side retry/poll (re-invoke
logs -funtil the file becomes non-empty) fixes the symptom for one caller but adds a busy-wait to the CLI, does not help consumers of theContainerClient.logsAPI who tail the file themselves, and still leaves the flag failing to honor its contract. - Server-side (block the
containerLogsXPC response until the first byte arrives) would change the semantics of every log retrieval — including non-follow, where an empty log legitimately means "nothing yet" — and adds latency where the on-disk file is already the natural stream source. - Document as working-as-intended contradicts the flag's own help text ("Follow log output") and the direction of #1752, which asks for "follow/stream behavior that works reliably for orchestrators reading multiple containers concurrently." Docker's equivalent documents
--followas: "Thedocker logs --followcommand will continue streaming the new output from the container'sSTDOUTandSTDERR" (Docker CLI reference).
Behavioral delta: container logs -f on an empty log will now block and stream instead of exiting 0. Blocking on empty input is the standard -f/tail -f contract, and the follow stream already ends when the log file is truncated or the seek fails (ContainerLogs.swift#L116-L125), so a stopped/removed container still ends the stream.
Acceptance criteria
-
container logs -fon a container whose log file is currently empty blocks, streams lines as they are written, and exits only when the stream ends (repro above:helloappears after 10s) -
container logswithout-fon an empty log is unchanged (no output, exit 0) -
container logs -fon a non-empty log is unchanged (prints existing content, then follows) - Regression test: follow on a zero-byte log file streams subsequent writes (unit or CLITest)
- Lingua principale
- Swift
- Stelle
- 50.9k
- Fork
- 1.8k
- Merge medio
- 1g 3h
- PR unite (30g)
- 30
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di apple/container
-
[Request]: container ls --filter label=key=valueForse già presa @gerardrecinto l’ha presa 11 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
apple/container#2403 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
[Request]: Improve robustness and path resolution in ensure-hawkeye.shForse già presa @denizguney l’ha presa 24 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno
-
docs: k8s CNI with Cilium example in docs/kubernetes.mdForse già presa @zdk l’ha presa 26 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
-
[Bug]: kernel download hangs forever when the server stops sendingForse già presa @devops-thiago l’ha presa 27 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
I maintainer di solito rispondono entro 1 giorno
-
[Bug]: container export -o deletes an existing directoryForse già presa @devops-thiago l’ha presa 27 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di apple/container
Issue simili
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
521xueweihan/HelloGitHub#4149 ·
-
Android: theme prop is ignored in R8-minified release builds (Theme enum is not Enumerable)Forse già presa @1kuko3 l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 71/100
Cocoanetics/Loupe#8 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 66/100
I maintainer di solito rispondono entro 9 giorni