Skip to content

Give a container the swap area it asks for - #878

Closed
MayCXC wants to merge 6 commits into
apple:mainfrom
MayCXC:guest-swap
Closed

Give a container the swap area it asks for#878
MayCXC wants to merge 6 commits into
apple:mainfrom
MayCXC:guest-swap

Conversation

@MayCXC

@MayCXC MayCXC commented Aug 27, 2026

Copy link
Copy Markdown

Summary

A container can be given a swap area, so a workload whose memory exceeds its limit is reclaimed rather than killed. The area is a raw file on the host attached as a block device, which is how a guest gets swap it can write to when its own root is read only, and vminitd formats and enables it while mounting the container's filesystems.

The area is enabled with discard, and the device backing it is marked non rotational first. virtio block devices are rotational by default, and the kernel only tracks a swap area in clusters when its device is non rotational, so without that the discard flags are accepted and no discard is ever issued. Together they let the sparse file that backs the area release the blocks the guest stops using, rather than holding the area's high water mark for as long as the container runs.

The host file is sparse. The guest reaches the area as a block device, which the kernel takes as a single extent without consulting the host's layout, so the hole-free requirement that binds a swap file inside the guest does not bind the host file backing the device: https://github.com/torvalds/linux/blob/master/mm/swapfile.c

Dependency and Merge Order

This targets stock apple/containerization:main and depends on #872, which in turn depends on #863. Merge order: #863, then #872, then this. The head carries both prerequisites' commits because all three target stock main; after they merge, this branch takes current main and its remaining diff is the swap support alone.

Motivation and Context

Discussion: #877, which also carries the three design questions I would like answered before this is taken as final (whether the size should count swap alone or the memory-plus-swap total the OCI spec spells, whether the block-device form is the one wanted, and what the default should be). #653 proposed a custom OOM killer for the same underlying situation and was closed.

Testing

  • swift build and make check clean.
  • swift test: passing, including new SwapTests covering header writing and the size arithmetic.
  • make integration: new cases assert that a container over its memory limit is reclaimed to its area rather than killed, and that the host file gives blocks back as the guest stops using them.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

MayCXC added 6 commits August 27, 2026 20:43
The reaper that runs at a concurrency of one deletes everything in the
test directory except the unpacked rootfs it means to preserve. It held
that rootfs path as FileManager reports it, under /var, and compared it
against the entries of contentsOfDirectory, which reports them under
/private/var, so the preserved path never matched any entry and the
rootfs was deleted along with the per-test files. The unpack coordinator
still held it as unpacked, so the next test opened a rootfs that was no
longer there and failed with a missing file error.

Resolve both sides with resolvingSymlinksInPathWithPrivate, which exists
for this difference between the two views of the same directory.
Tests guarded for Linux are compiled out on macOS, so `make test` reports
success without having run them, and nothing says so. The target that
does run them is not mentioned anywhere outside the makefile.
The default kernel the tests fetch had drifted from the one users run.
container installs kata 3.28.0 and its 6.18.15 kernel, while these tests
fetched 3.17.0, so the suite exercised a guest with a different feature
set to the one it is meant to represent. Nested runtimes are the visible
case: 3.17.0 was built without nf_tables, so a docker daemon inside a
container fails there and works on what ships.

Kata moved from xz to zstd between those releases, so the archive is no
longer named for its compression and tar is left to recognise it rather
than being told, which also holds if the format changes again.
A test's rootfs clones, init block, and writable layers land in a
directory the runner creates for it and deletes when it finishes, so a
test holds disk only while it runs at any concurrency. The run-shared
directory holds the unpacked images every bootstrap clones from, and is
removed when the run ends.
A container's storage is its rootfs, an optional writable layer, and its
mounts; a machine's is its containers' plus the volumes they share. One
generic shape describes both the Mount values a machine is configured
with and the AttachedFilesystem values it reports once attached, so
converting between the two is a map over the structure and the roles
cannot drift between configuration and attachment.

Device addresses are allocated walking the same sorted order the devices
are created in, on both backends. Consumers read roles instead of list
positions: the spec builders take a container's mounts without prefix
arithmetic, pod volumes mount by name, and a cloud-hypervisor volume's
virtiofsd is held by a machine-lifetime reference alongside its
containers' reference counts.
A container can be given a swap area, so a workload whose memory exceeds
its limit is reclaimed rather than killed. The area is a raw file on the
host attached as a block device, which is how a guest gets swap it can
write to when its own root is read only, and vminitd formats and enables
it while mounting the container's filesystems.

The area is enabled with discard, and the device backing it is marked non
rotational first. virtio block devices are rotational by default, and the
kernel only tracks a swap area in clusters when its device is non
rotational, so without that the discard flags are accepted and no discard
is ever issued. Together they let the sparse file that backs the area
release the blocks the guest stops using, rather than holding the area's
high water mark for as long as the container runs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants