Skip to content

[Bug]: container logs -n truncates the first line it prints; four additional performance defects #2022

Description

@gnavadev

I have done the following

  • I have searched the existing issues
  • If possible, I've reproduced the issue using the 'main' branch of this project

Steps to reproduce

Starting state: stock installed release (version in Environment below), container
system running, no custom ~/.config/container/config.toml. No prior commands required.

Reproducibility: always, deterministic whenever the first log line exceeds the
tail reader's 1024-byte read chunk.

  1. Start the container system:

    container system start
  2. Create a container whose first log line is 2000 characters, followed by two short lines:

    container run -d --name logtest debian:bookworm sh -c 'head -c 2000 /dev/zero | tr "\0" "a"; echo; echo second; echo third'
  3. Confirm it has exited:

    container ls -a
  4. Print the full log and measure the first line, it is correct:

    container logs logtest | head -1 | wc -c

    Output: 2001 (2000 characters plus newline).

  5. Request the last three lines of that same file:

    container logs -n 3 logtest | head -1 | wc -c

    Output: 1011. The first line is silently truncated to its own tail.
    Expected 2001, identical to step 4.

Steps 4 and 5 read the same file and disagree, so the defect is isolated to the -n
path. container machine logs -n has a byte-for-byte identical implementation
(MachineLogs.swift:70-93 vs ContainerLogs.swift:60-88) and is affected the same way.

Problem description

Current behavior

1. container logs -n truncates the first line it prints. Measured above: the same
log file reports 2001 bytes without -n and 1011 bytes with -n 3.

The truncation point is a direct function of the read chunk size, not the environment.
The log is 2014 bytes (2000 + \n + second\n + third\n). The reader seeks back
1024 bytes to offset 990, so the first line in its window is bytes 990–1999, 1010
characters plus a newline, giving 1011.

Cause, in ContainerLogs.swift:60-88: the loop reads backwards in 1 KiB chunks and
stops as soon as it holds n non-empty lines. Here the very first chunk already yields
three (a fragment, second, third), so it stops mid-line and prints the fragment as
though it were complete. The same loop also prepends each chunk to a Data buffer and
re-decodes and re-splits the entire accumulated buffer every iteration, making the cost
quadratic in bytes read.

The following four were identified by code inspection, not measured. They are
inefficiencies rather than incorrect output:

2. Redundant regex compilation
ContainerBuild/Globber.swift:78-91. Each call
compiles the same pattern twice: once via try Regex(...) purely to validate and then
discard, and again inside range(of:options:.regularExpression). This runs for every
file and directory visited during a build-context walk.

3. Global lock held across a filesystem walk
Services/ContainerAPIService/Server/Containers/ContainersService.swift:231-252 and
.../Volumes/VolumesService.swift:183-199. A single un-keyed AsyncLock guards every
operation (create/delete/exec/list/disk-usage) and is held for the duration of a
synchronous recursive allocatedSize walk, so container system df blocks unrelated
container operations for the length of the scan.

4. Linear scan against a Set
ContainerBuild/BuildFSSync.swift:220.
items.contains { item.relativePath == rel } resolves to Sequence.contains(where:)
rather than the Set's O(1) contains(_:), despite DirEntry hashing and comparing
solely on relativePath. O(k²) per directory during build-context tar creation.

5. Serial per-container stats
ContainerCommands/Container/ContainerStats.swift:166-193.
Two samples per container are fetched one round trip at a time in a for loop, so wall
time scales with container count.

Expected behavior

  1. container logs -n N returns the last N complete lines, byte-identical to the last
    N lines of container logs. Cost linear in bytes read.
  2. Each distinct glob pattern compiled once.
  3. Disk-usage accounting snapshots state under the lock and sizes bundles outside it, so
    container system df does not block unrelated commands.
  4. O(1) set membership.
  5. Concurrent fan-out; wall time set by the slowest container, not the sum.

Relevant logs

No error messages, stack traces, or non-zero exit codes are involved, the commands
succeed and return wrong output. The wc -c counts in the reproduction steps are the
complete evidence.

Environment

- OS: macOS 26.4 (25E246)
- Xcode: Version 26.5 (17F42)
- Container: Container CLI version 1.1.0 (build: release, commit: 5973b9c)

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions