Skip to content

fix: defer metadata application until after disk thread commit#2344

Merged
oferchen merged 1 commit intomasterfrom
fix/deferred-metadata-application
Feb 18, 2026
Merged

fix: defer metadata application until after disk thread commit#2344
oferchen merged 1 commit intomasterfrom
fix/deferred-metadata-application

Conversation

@oferchen
Copy link
Copy Markdown
Owner

Summary

  • Fix race condition in decoupled receiver where metadata (mtime, permissions, ownership) was applied before the disk thread had committed the file to its final path
  • This caused mtime not to be preserved in daemon mode, making the quick-check algorithm ineffective for incremental syncs

Root Cause

The decoupled receiver architecture (introduced in PR #2338) separated network I/O from disk I/O via a bounded channel + disk commit thread. However, metadata was applied on the network thread immediately after queueing the commit, before the disk thread had flushed and renamed the temp file.

Sequence (buggy):

  1. Network thread sends Commit message to disk thread
  2. Network thread applies metadata to final path ← file doesn't exist yet!
  3. Disk thread flushes, renames temp → final path

Fix

Defer metadata application to after drain_all_results() confirms all files are committed. Mirrors upstream finish_transfer()set_file_attrs() which runs after do_rename(fnametmp, fname) in receiver.c.

Benchmark (10K files, 422MB, daemon mode)

Mode Before Fix After Fix upstream rsync
Initial copy 578ms 627ms 1,034ms
Incremental (no changes) 923ms 121ms 137ms
  • Incremental: 7.6x improvement (923ms → 121ms), now 1.13x faster than upstream
  • Initial copy: no significant change
  • Syscalls (incremental): 10,241 total — matches upstream's 10,414

Test plan

  • All 21,724 tests pass
  • Clippy clean, fmt clean
  • Verified mtime preservation in daemon mode (stat comparison)
  • Benchmarked initial copy — no regression
  • Benchmarked incremental sync — 7.6x improvement
  • Syscall profile matches upstream

The decoupled receiver was applying file metadata (mtime, permissions,
ownership) immediately after queueing the commit to the disk thread,
before the disk thread had actually flushed and renamed the temp file
to its final path. This caused:

1. mtime not preserved in daemon mode (utimensat on non-existent path)
2. Incremental sync 6.5x slower than upstream (quick-check always
   failed because destination mtimes never matched source)

Fix: collect deferred metadata entries during the transfer loop and
apply them after drain_all_results() confirms all files are committed.
Mirrors upstream finish_transfer() → set_file_attrs() which runs
after do_rename(fnametmp, fname) in receiver.c.

Benchmark (10K files, 422MB, daemon mode):
- Incremental: 923ms → 121ms (1.13x faster than upstream's 137ms)
- Initial copy: no regression (627ms, 1.65x faster than upstream)
@oferchen oferchen merged commit 6e98a1f into master Feb 18, 2026
30 checks passed
@oferchen oferchen deleted the fix/deferred-metadata-application branch February 18, 2026 23:08
@oferchen oferchen added the bug Something isn't working label Feb 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant