Skip to content

runsc: don't create a lock file when loading a missing container - #16255

Open
NahumLitvin wants to merge 1 commit into
google:masterfrom
NahumLitvin:runsc-state-lock-leak
Open

NahumLitvin wants to merge 1 commit into
google:masterfrom
NahumLitvin:runsc-state-lock-leak

Conversation

@NahumLitvin

@NahumLitvin NahumLitvin commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

We found a runsc root on one of our nodes with 176 state files and 32,062 .lock files. All but 183 of the lock files had no state file, and each orphan belonged to a different, already deleted pod. Every runsc state or events --stats on that node took about 60ms, of which roughly 45ms was the glob in findContainerID walking the directory. Copying 32k empty lock files into a test root took runsc state from about 60ms to 300ms.

The cause is that load() creates the lock file. It locks through gofrs/flock, which opens with O_CREATE, so loading a container whose state file is gone leaves a new lock file behind. The common trigger is a race: Load globs for the state file, a concurrent runsc delete destroys the container, and then load() recreates the lock. An Exact load of a missing ID does the same with no race at all. On our nodes the leak went from a handful a day to about 12k a day per node, because each leaked file makes the glob, and so the race window, longer.

This change opens the lock file without O_CREATE in load(). A missing lock file now reads as os.ErrNotExist, the same as a missing state file, so runsc delete --force and restore keep their not-found handling. A load can still lose to a delete. It now returns not-found without leaving anything on disk. LockForNew and the other lock() callers still create the file. SetFlag needs gofrs/flock v0.11.0, so this bumps it from v0.8.0. The releases in between only add Go modules, Illumos support and the options.

TestLoadAfterDestroy runs the racy order and TestLoadExactMissing loads a missing ID. Both fail on master and pass with the change. To run them: bazel test //runsc/container:state_file_test.

I used Claude Code to help investigate and write this, and I reviewed and tested the result.

@NahumLitvin

Copy link
Copy Markdown
Contributor Author

@konstantin-s-bogom long time no reviewed :)

@NahumLitvin

Copy link
Copy Markdown
Contributor Author

@tamird please review

@tamird

tamird commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

@tamird please review

At a glance I don't love papering over the race but I'll have clanker review later today.

load() takes the container lock through gofrs/flock, which opens the
lock file with O_CREATE. When the state file is gone, the read fails
with ENOENT and the newly created lock file stays behind.

This happens in two ways. Load globs for the state file and then locks,
so a concurrent delete in between leaves a lock file. An Exact load of
an ID that has no state file leaves one with no race at all.

These files pile up in the runsc root, and every later Load globs that
directory. We saw a root with 176 containers and 32k orphan lock files,
where each runsc call spent about 45ms in the glob.

Open the lock file without O_CREATE in load(), and report a missing
lock file as os.ErrNotExist, like a missing state file. LockForNew and
the other lock() callers still create it. This needs gofrs/flock
v0.11.0 for SetFlag.

Assisted-by: Claude Code
Signed-off-by: Nahum Litvin <nahuml@wix.com>
@NahumLitvin
NahumLitvin force-pushed the runsc-state-lock-leak branch from 9bddf83 to ecbf248 Compare October 24, 2026 12:28
@NahumLitvin NahumLitvin changed the title runsc: remove the lock file when loading a destroyed container runsc: don't create a lock file when loading a missing container Oct 24, 2026
@NahumLitvin

Copy link
Copy Markdown
Contributor Author

@tamird giving it my best try :) reworked it so load() never creates the lock file. the race can still happen, it just returns not-found now with nothing left on disk. also turned out an Exact load of a missing id leaked with no race at all, added a test for it.

@EtiennePerot

EtiennePerot commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

I'll be sending a PR shortly that refactors state file loading and avoids taking a lock at all for read-only operations (e.g. runsc state).

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.

3 participants