Skip to content

flake: stale NFS file handle #157

@cgwalters

Description

@cgwalters

I don't understand why we're getting ESTALE sometimes in CI; haven't seen it locally. My understanding of the situations where this can happen require mutation of the source, but that shouldn't be happening here.


Investigation Summary (2026-01-23)

Assisted-by: OpenCode (Opus 4.5)

Scope of Impact

  • Affected repos: Only bootc-dev/bcvk (checked bootc, infra, ocidir-rs, containers-image-proxy-rs)
  • Frequency: ~5% of recent failed runs (5 out of ~100 runs checked)
  • Tests affected: Various libvirt/to-disk integration tests:
    • test_to_disk_for_image_quay_io_fedora_fedora_bootc_42
    • test_libvirt_run_bind_storage_ro
    • test_libvirt_comprehensive_workflow
    • test_libvirt_port_forward_connectivity

Error Pattern

All failures occur in paths like:

/run/virtiofs-mnt-hoststorage/overlay/<layer-hash>/diff/sysroot/ostree/repo/objects/<xx>/<hash>.file

These are ostree content-addressed objects with many hardlinks (up to 970+ links for .file-xattrs-link files).

Architecture

Guest VM
  └── virtiofs mount at /run/virtiofs-mnt-hoststorage
        └── virtiofsd (v1.13.0, running unprivileged in container)
              └── Host container storage (~/.local/share/containers/storage/overlay/...)
                    └── ostree repo objects (read-only, hardlinked)

Root Cause Analysis

  1. bcvk passes --inode-file-handles=fallback to virtiofsd (see qemu.rs:932), which should enable file handles when possible.

  2. However, virtiofsd runs unprivileged (inside a podman container with --cap-add=all, but in a user namespace where CAP_DAC_READ_SEARCH doesn't grant real host filesystem capabilities).

  3. Without CAP_DAC_READ_SEARCH, file handles don't work. From virtiofsd README:

    "virtiofsd can't use file handles (--inode-file-handles requires CAP_DAC_READ_SEARCH)"

  4. Fallback to O_PATH FDs: virtiofsd falls back to using O_PATH file descriptors to track inodes. When the guest wants to open a file, virtiofsd reopens via /proc/self/fd/N.

  5. O_PATH FDs are vulnerable to ESTALE: According to virtiofsd docs, using O_PATH FDs instead of file handles "may also be important in cases where virtiofsd should only have file descriptors open for files that are open in the guest, e.g. to get around bad interactions with NFS's silly renaming."

The Puzzle

The container image layers are read-only - no files should be deleted or modified during the test. Yet we're seeing ESTALE. Possible explanations:

  1. Inode generation mismatch: The kernel FUSE code checks inode->i_generation != handle->generation and returns ESTALE on mismatch. virtiofsd always sets generation: 0, so this shouldn't trigger unless something is evicting/recreating inodes.

  2. Hardlink edge case: ostree objects have extremely high hardlink counts. There might be some edge case with how virtiofsd or the kernel handles lookups on highly-hardlinked files.

  3. Race in inode cache: Something in the kernel's inode cache management could be evicting and revalidating inodes during concurrent access, causing transient failures.

  4. Host filesystem peculiarity: Something specific to GitHub Actions runner filesystem configuration.

Relevant Code Paths

  • virtiofsd inode store: When --inode-file-handles=prefer/fallback but capabilities unavailable, falls back to O_PATH FDs (src/passthrough/inode_store.rs:405-417)
  • virtiofsd file open: Reopens via /proc/self/fd/N (src/passthrough/util.rs:41-53)
  • Kernel FUSE staleness check: fuse_stale_inode() in fs/fuse/fuse_i.h:1080-1085

Example Failing Runs

Potential Mitigations to Explore

  1. Run virtiofsd privileged: Grant CAP_DAC_READ_SEARCH to enable actual file handles
  2. Add debug logging: Capture virtiofsd debug output during failures
  3. Retry on ESTALE: Application-level workaround (not ideal)
  4. File upstream bug: With virtiofsd or kernel FUSE maintainers

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions