Skip to content

fix(serverless): VolumeCache copies files on volumes that reject utime/chmod - #620

Merged
deanq merged 4 commits into
mainfrom
fix/volume-cache-metadata-eperm
Oct 15, 2026
Merged

deanq merged 4 commits into
mainfrom
fix/volume-cache-metadata-eperm

Conversation

@deanq

@deanq deanq commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Bug

VolumeCache._copy_file used shutil.copy2, which calls copystat (utime, then chmod) after copying the data. FUSE object stores such as Global Store reject both with EPERM, so the entire copy raised. The except OSError logged it at debug level and returned False.

As a result, every file of 256 KiB or more, which covers all model weights, silently dropped out of the mirror. The manifest correctly left them out, so each cold start re-downloaded the model and nothing ever reported an error. I found this while benchmarking VolumeCache on Global Store (deanq/flash-model-cache-bench, finding 4).

Fix

  • Copy the data with shutil.copyfile, then carry the mtime (os.utime) and mode (shutil.copymode) over separately, each best-effort with a debug log.
  • Large files are reconciled against the manifest's recorded source metadata (size, mtime) in both directions, not against the mirror file's own mtime. When the volume rejects utime, mirror files carry copy-time mtimes, and comparing those could skip a same-size update on sync or overwrite a newer container file on hydrate (review feedback).
    • Sync: copy when the mirror file is missing or a different size, or when the source differs from its prior manifest entry. With no prior entry, only an exact size+mtime match counts as current, so a lost manifest self-heals by recopying.
    • Hydrate: copy when the container file doesn't satisfy the manifest entry (_meta_satisfied_by_local), and stamp the manifest's source mtime on the restored file so the next sync is a no-op.
  • Only the permission bits are lost, and only on a filesystem that refuses chmod.
  • Real copy failures are now logged with log.warn rather than log.debug, so a cache that is silently not caching shows up in worker logs.

Tests

The new tests failed before the fix and pass after it:

  • test_big_file_round_trips_when_volume_rejects_metadata_ops: sync, delete, hydrate with os.utime/os.chmod raising EPERM. Before the fix, 0 files were restored.
  • test_sync_converges_when_volume_rejects_metadata_ops: a second sync re-copies nothing.
  • test_copy_failure_is_logged_as_warning.

Two more tests:

  • test_copy_file_preserves_mtime_and_mode guards the normal-filesystem behavior (mtime and 0o700 preserved).
  • The existing test_copy_file_returns_false_and_cleans_tmp_on_failure now patches copyfile.

Review regression tests, simulating a volume that rejects utime/chmod only under the volume path:

  • test_sync_recopies_same_size_update_when_volume_rejects_utime (failed before)
  • test_hydrate_keeps_newer_local_file_when_volume_rejects_utime (failed before)
  • test_hydrate_restores_source_mtime_so_next_sync_is_noop (failed before)
  • test_sync_recopies_when_mirror_file_is_missing (guard)

Full suite: uv run pytest, 1478 passed.

Not addressed

On Global Store, the benchmark also saw 7B hydrates come back about half-complete even with this copy fix. The cause wasn't identified, so this PR doesn't claim to fix it. Symlinks under the HF cache's snapshots/ aren't mirrored either, by design.

shutil.copy2 runs copystat after the data copy, and FUSE object stores
such as Global Store reject utime/chmod with EPERM. The whole copy then
failed and was logged at debug level, so every file >= 256 KiB silently
dropped out of the mirror and each cold start re-downloaded the model.

Copy the data with copyfile and carry mtime and mode over best-effort.
A copy-time mtime is never older than the source, so both diff
directions still converge. Log real copy failures as warnings.
Comment thread tests/test_serverless/test_utils/test_rp_volume_cache.py Fixed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃煛 Changes recommended

Copy-time timestamps can hide source updates and cause hydration to restore stale bytes or overwrite newer local data.

1 open finding
What changed in this PR

Updates VolumeCache to copy files on volumes that reject timestamp or permission changes.

Changes:

  • Separates data copying from best-effort metadata preservation.
  • Logs copy failures as warnings.
  • Adds metadata-rejection, convergence, and preservation tests.
File Description
tests/鈥媡est_serverless/鈥媡est_utils/鈥媡est_rp_volume_cache.py Tests metadata failures, round trips, convergence, and warning logs.
runpod/鈥媠erverless/鈥媢tils/鈥媟p_volume_cache.py Makes metadata copying best-effort and raises copy-failure log visibility.

馃 Review effort: Balanced


馃挕 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread runpod/serverless/utils/rp_volume_cache.py Outdated
When the volume rejects utime, mirror files carry copy-time mtimes, so
comparing physical mtimes could skip a same-size update on sync or
overwrite a newer container file on hydrate. Compare big files against
the manifest's recorded source metadata in both directions, keep a
size/existence check on the mirror, and stamp the source mtime on
restore so the next sync is a no-op.
@deanq
deanq requested a review from a team October 14, 2026 19:37
Comment thread runpod/serverless/utils/rp_volume_cache.py Outdated
_sync_needs_copy compared source and manifest mtimes with abs(), so a
source older than the recorded entry also triggered a recopy. That made
every big file rewrite across workers with separate downloads (same
bytes, different mtimes) and let a stale worker overwrite newer mirror
bytes. Recopy only when the source is strictly newer than the manifest.
@deanq
deanq merged commit 1d7ae99 into main Oct 15, 2026
10 checks passed
@deanq
deanq deleted the fix/volume-cache-metadata-eperm branch October 15, 2026 06:07
@promptless

promptless Bot commented Oct 15, 2026

Copy link
Copy Markdown

Promptless documentation updates

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.

4 participants