Skip to content

fix: prevent apply_commit from clobbering size/hash on generation mismatch#75

Merged
XciD merged 1 commit intomainfrom
fix/apply-commit-clobber
Mar 26, 2026
Merged

fix: prevent apply_commit from clobbering size/hash on generation mismatch#75
XciD merged 1 commit intomainfrom
fix/apply-commit-clobber

Conversation

@XciD
Copy link
Copy Markdown
Member

@XciD XciD commented Mar 26, 2026

Summary

Fixes REVIEW.md finding 3 in #69 : apply_commit overwrote size and xet_hash unconditionally before checking clear_dirty_if(dirty_generation). If a concurrent writer advanced the generation, the in-progress file's size was clobbered with the stale flushed value.

Scenario:

  1. Writer A flushes 10 MB file (captures gen=5)
  2. Writer B extends file to 15 MB (gen=6)
  3. Flush completes, apply_commit("hash", 10MB, gen=5) runs
  4. Old code: sets size=10MB, then clear_dirty_if(5) returns false (gen is 6)
  5. Applications see 10 MB until next flush, even though staging file is 15 MB

Fix: Move xet_hash, size, and pending_deletes.clear() inside the clear_dirty_if success branch.

One-line semantic change in src/virtual_fs/inode.rs, existing test updated.

…match

When a concurrent writer advances the dirty generation while a flush is
in flight, apply_commit would overwrite size and xet_hash with the stale
flushed values before checking the generation. This caused applications
to see the wrong file size until the next flush.

Move size/hash/pending_deletes updates inside the clear_dirty_if success
branch so they are only applied when the generation matches.
@github-actions
Copy link
Copy Markdown

Benchmark Results

============================================================
  Benchmark — 50MB
------------------------------------------------------------
  Metric                                 FUSE          NFS
  ------------------------------ ------------ ------------
  Sequential read                    279.4 MB/s     248.8 MB/s
  Sequential re-read                1582.8 MB/s    2433.6 MB/s
  Range read (1MB@25MB)               32.6 ms         0.2 ms
  Random reads (100x4KB avg)          30.9 ms         0.0 ms
  Sequential write (FUSE)           1361.0 MB/s
  Close latency (CAS+Hub)            0.081 s
  Write end-to-end                   424.5 MB/s
  Dedup write                       1770.3 MB/s
  Dedup close latency                0.818 s
  Dedup end-to-end                    59.1 MB/s
============================================================
============================================================
  Benchmark — 200MB
------------------------------------------------------------
  Metric                                 FUSE          NFS
  ------------------------------ ------------ ------------
  Sequential read                   1030.6 MB/s     996.3 MB/s
  Sequential re-read                1624.2 MB/s    2473.1 MB/s
  Range read (1MB@25MB)               37.1 ms         0.2 ms
  Random reads (100x4KB avg)          32.6 ms         0.0 ms
  Sequential write (FUSE)           1568.4 MB/s
  Close latency (CAS+Hub)            0.104 s
  Write end-to-end                   864.9 MB/s
  Dedup write                       1653.6 MB/s
  Dedup close latency                0.088 s
  Dedup end-to-end                   958.8 MB/s
============================================================
============================================================
  Benchmark — 500MB
------------------------------------------------------------
  Metric                                 FUSE          NFS
  ------------------------------ ------------ ------------
  Sequential read                   1492.7 MB/s    1401.1 MB/s
  Sequential re-read                1787.0 MB/s    2451.5 MB/s
  Range read (1MB@25MB)               63.5 ms         0.2 ms
  Random reads (100x4KB avg)          32.5 ms         0.0 ms
  Sequential write (FUSE)           1450.4 MB/s
  Close latency (CAS+Hub)            0.464 s
  Write end-to-end                   618.5 MB/s
  Dedup write                       1409.8 MB/s
  Dedup close latency                0.118 s
  Dedup end-to-end                  1058.5 MB/s
============================================================
============================================================
  fio Benchmark Results
------------------------------------------------------------
  Job                        FUSE MB/s   NFS MB/s  FUSE IOPS   NFS IOPS
  ------------------------- ---------- ---------- ---------- ----------
  seq-read-100M                  369.0      454.5                      
  seq-reread-100M               2439.0       18.1                      
  rand-read-4k-100M                0.1        0.1         18         20
  seq-read-5x10M                 793.7      847.5                      
  rand-read-10x1M                  0.1        0.1         37         38
  Random Read Latency           FUSE avg      NFS avg
  ------------------------- ------------ ------------
  rand-read-4k-100M           54804.4 us   50735.1 us
  rand-read-10x1M             26970.8 us   26178.9 us
============================================================

@github-actions
Copy link
Copy Markdown

POSIX Compliance (pjdfstest)

============================================================
  pjdfstest POSIX Compliance Results
------------------------------------------------------------
  Files: 130/130 passed    Tests: 832 total (0 subtests failed)
  Result: PASS
------------------------------------------------------------
  Category               Passed    Total   Status
  -------------------- -------- -------- --------
  chflags                     5        5       OK
  chmod                       8        8       OK
  chown                       6        6       OK
  ftruncate                  13       13       OK
  granular                    5        5       OK
  mkdir                       9        9       OK
  open                       19       19       OK
  posix_fallocate             1        1       OK
  rename                     10       10       OK
  rmdir                      11       11       OK
  symlink                    10       10       OK
  truncate                   13       13       OK
  unlink                     11       11       OK
  utimensat                   9        9       OK
============================================================

@XciD XciD marked this pull request as ready for review March 26, 2026 12:46
@XciD XciD merged commit 55f79ab into main Mar 26, 2026
6 checks passed
@XciD XciD deleted the fix/apply-commit-clobber branch March 26, 2026 12:47
This was referenced Mar 26, 2026
XciD added a commit that referenced this pull request Mar 26, 2026
Bump version to 0.1.1.

## Changes since v0.1.0

### Bug fixes
- fix: prevent file descriptor exhaustion on bulk NFS deletes (#68)
- fix: schedule flush after NFS exclusive create with empty files (#71)
- fix: re-check dirty status before poll deletes inode, TOCTOU race
(#67)
- fix: prevent poll from deleting inodes with open file handles (#74)
- fix: prevent apply_commit from clobbering size/hash on generation
mismatch (#75)
- fix: serialize setattr truncate with write to prevent inode size race
(#76)
- fix: tolerate rename Phase 3 failure after remote mutation (#77)

### Performance
- perf: chunk upload_files to bound FD usage during flush (#72)
- perf: skip redundant HEAD revalidation after flush commit (#73)
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.

1 participant