Skip to content

fix(vfs): batch filesystem sync across page-cache mappings - #2312

Merged
fslongjin merged 2 commits into
DragonOS-Community:masterfrom
fslongjin:codex/batch-filesystem-sync-writeback
Sep 21, 2026
Merged

fslongjin merged 2 commits into
DragonOS-Community:masterfrom
fslongjin:codex/batch-filesystem-sync-writeback

Conversation

@fslongjin

Copy link
Copy Markdown
Member

Summary

Fix the small-file transaction amplification in filesystem-wide sync by separating bounded cross-mapping submission from completion waits.

  • Start up to eight mappings, using the existing PageCache writeback capacity, before acquiring the filesystem's synchronous-progress guard.
  • Run only the first claimed Token batch on the sync caller; reuse the existing asynchronous continuation for successors. Legacy backends and ordinary range-WRITE dispatch retain their asynchronous behavior.
  • Retain the canonical inode and cache through data completion and metadata writeback. Preserve the frozen range on partial dispatch failure, drain all started handles, and check mapping errseq without consuming a file descriptor's error cursor.
  • Use an allocation-free bounded completion scan so ENOMEM cannot interrupt draining already-started I/O.
  • Add PageCache success/error/deferred-error coverage and ext4 mixed-size, multi-window, concurrent-redirty, and remount tests.

No journal format, commit/checkpoint protocol, flush barrier, worker pool, or retry timer is changed. An inline first batch may still block in an existing backend; it is not an unbounded per-file drain or a promise of nonblocking I/O.

Root cause

The previous mount loop called a complete synchronous PageCache operation for each inode. ext4 completes those pages only after journal durability, while an active synchronous request forces newly published work to seal promptly. Small files therefore repeatedly paid a full transaction/checkpoint barrier sequence before the next mapping was started.

The new split follows Linux's for_sync principle: organize writeback across mappings before the outer completion wait, while retaining metadata and filesystem durability boundaries. Partial-start errors keep their completion target instead of discarding an already-started range.

Validation

  • make kernel, focused Rust formatting checks, and git diff --check passed.
  • KVM guests with both 1 and 2 vCPUs passed 46 ext4 tests, 1 PageCache accounting test (including the new kernel selftests), and 4 sync_file_range tests.
  • Repeated real DragonOS repository file:// clone, checkout, sync, and git fsck --full succeeded.
  • A freshly rebuilt unmodified a4aaa794d control measured checkout-following sync at 30.718 / 31.817 seconds, with 20,551 / 20,516 flushes. Four patched runs measured 5.491–7.854 seconds, with 2,659–2,716 flushes (about 87% fewer).
  • The 128 × 64KiB small-file probe changed from 642 to 97 flushes in the final-kernel run. Single-file 8MiB fsync retained 168 flushes.
  • Three-role adversarial review covered concurrency/security, logic/error handling, and system/performance concerns; no unresolved blocking findings remained.

Boundaries

These are controlled functional checks and repeated measurements, not a statistically cache/layout-normalized benchmark. Clone itself remains variable: a patched run showed a roughly 10-second post-child-exit gap, and the unmodified control also showed a roughly 5-second gap in that phase. This change does not claim to diagnose or eliminate that separate tail-latency behavior, or to accelerate every Git phase.

Power-cut testing and the full gVisor suite were not run. Remount tests are not a substitute for power-loss testing, because unmount can perform additional synchronization. The existing journal durability protocol and barriers are unchanged.

Global filesystem synchronization previously waited for each mapping's
durable data completion before starting the next mapping. With ext4's
synchronous journal requests, this repeatedly sealed tiny transactions
and paid the complete journal/checkpoint barrier cost for small files.

Start bounded windows using the existing asynchronous writeback budget,
then request synchronous progress and drain every mapping in the window.
Submit the first claimed Token batch on the sync caller's stack so its
acceptance is not deferred behind the window's forced commit. Reuse the
existing continuation for successors; keep Legacy and ordinary range
WRITE dispatch asynchronous and preserve journal durability barriers.

Retain the canonical inode and page cache across data completion and
metadata writeback. Preserve the frozen range after a partial dispatch
failure, sample mapping errseq before starting, and report errors only
after draining the accepted work. Replace the completion scan's temporary
allocation with a bounded array so memory pressure cannot interrupt drain.

Add PageCache success/submission-error/deferred-error selftests and ext4
multi-window, mixed-size, concurrent-redirty, and remount coverage.

Validation:
- make kernel and focused rustfmt/diff checks passed.
- KVM guests with both one and two vCPUs passed 46 ext4, one PageCache
  accounting (including kernel selftests), and four sync_file_range tests.
- Repeated DragonOS repository clone/checkout/sync/fsck validation passed.
- The measured checkout sync barrier count fell from about 20,500 to
  about 2,700 without changing the journal protocol or single-file fsync.
- Three-role adversarial review found no unresolved blocking findings.

Signed-off-by: longjin <longjin@dragonos.org>
@github-actions github-actions Bot added the Bug fix A bug is fixed in this pull request label Sep 21, 2026
@fslongjin

Copy link
Copy Markdown
Member Author

@codex review

PageCache domain completion only records the write_inode error and passes it through unchanged. Use inspect_err rather than map_err to express that side effect and satisfy the denied clippy::manual_inspect lint invoked by make fmt.

Validated with make fmt, FMT_CHECK=1 make fmt (the CI command), and make kernel. The error value, superblock reporting, and writeback completion ordering are unchanged.

Signed-off-by: longjin <longjin@dragonos.org>
@fslongjin
fslongjin merged commit afaaee5 into DragonOS-Community:master Sep 21, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug fix A bug is fixed in this pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant