apple / apple/container

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

Open
#2,022 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Swift
Stars
49.9k
Forks
1.8k
Avg merge
1d 20h
Merged PRs (30d)
22

Description

### I have done the following

- [x] I have searched the existing issues
- [x] 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:
```bash
container system start
```

2. Create a container whose first log line is 2000 characters, followed by two short lines:
```bash
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:
```bash
container ls -a
```

4. Print the full log and measure the first line, it is correct:
```bash
container logs logtest | head -1 | wc -c
```
Output: `2001` (2000 characters plus newline).

5. Request the last three lines of that same file:
```bash
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

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

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

Contributor guide

Open the contributing guide

Research direction

Start with the reproduction commands and ContainerLogs.swift:60-88, then compare the matching MachineLogs.swift:70-93 implementation. Review the listed locations in Globber.swift, ContainersService.swift, VolumesService.swift, BuildFSSync.swift, and ContainerStats.swift, using the stated expected behavior as completion criteria. No tests are named, so validate the log output with the provided wc -c commands and add focused coverage where the project conventions indicate.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
backend, build-system, cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.