bcachefs: swap file support via SWP_FS_OPS#1072
Conversation
d0ab5e4 to
5587b10
Compare
Add investigation notes, audit results, and a QEMU-based reproducer for the SRCU-held-across-blocking-operations deadlock. The reproducer boots a 128 MB VM with pre-populated tiered bcachefs (SSD -> HDD), eats 200 MB of memory to force heavy reclaim, and checks whether the system stays responsive while the reconcile thread runs. prepare-vm-disks.sh - format + pre-populate disk images on the host build-initramfs.sh - build static musl Rust init (~430 KB) run-vm-test.sh - run test VM, analyze results vm-init-rs/ - Rust init binary (pedantic clippy clean) INVESTIGATION.md - crash analysis, full audit, GitHub issue survey
Audit and fix 25 sites where bch2_trans_unlock() (which only drops btree locks) is used before a potentially long-blocking operation, leaving the SRCU read lock held. If that blocking operation triggers page reclaim, and the shrinker needs to free old btree nodes, it must wait for the SRCU grace period -- which can't complete because we're holding the read lock. This creates a reclaim deadlock under memory pressure. The fix at each site is to use bch2_trans_unlock_long() (which also drops the SRCU read lock) instead of bch2_trans_unlock(). Also introduce a drop_locks_long_do() macro -- the SRCU-dropping variant of drop_locks_do() -- and convert the callers that pass blocking operations (mutex_lock, wait_event, journal reservation, etc.). Also clean up bch2_fsck_ask_yn(): the deferred unlock_long workaround (wait 2s then upgrade to unlock_long) is no longer needed since we now drop SRCU immediately before waiting for user input. Sites fixed: btree/cache.c 4 GFP_KERNEL allocs, sync I/O, wait_on_bit_io btree/commit.c 3 GFP_KERNEL alloc, journal reclaim wait, journal res btree/interior.c 5 mutex, closure_sync, wait_event, down_read btree/locking.c 1 mutex_lock btree/read.c 1 sync disk I/O alloc/foreground.c 1 mutex_lock data/update.c 3 GFP_KERNEL bio alloc, closure_sync (x2) data/ec/io.c 1 kvmalloc + multi-device I/O data/migrate.c 1 closure_wait_event for in-flight writes data/write.c 1 nocow lock + GFP_KERNEL vfs/buffered.c 1 filemap_alloc_folio + GFP_KERNEL vfs/fs.c 1 __wait_on_freeing_inode init/error.c 1 user input wait (+ cleanup) debug/tests.c 1 journal flush Reported on a 128 GB desktop (bcachefs root, tiered SSD+HDD): the reconcile thread held SRCU for 13+ seconds during GFP_KERNEL bio allocation in bch2_data_update_init(), blocking memory reclaim and causing a full system deadlock. Related: koverstreet#934 Related: koverstreet#636 Signed-off-by: Matthias Goergens <matthias.goergens@gmail.com>
5587b10 to
009db27
Compare
|
I'm using this branch on my desktop now. It's working quite well so far. |
|
A few things: Housekeeping:
The swap implementation itself: The
Testing: You've built your own QEMU test framework — nice work on the — ProofOfConcept |
Add copy-on-write swap file support to bcachefs using the SWP_FS_OPS path (same mechanism as NFS and btrfs). This enables checksumming, encryption, compression, replication, and multi-device support for swap data. Key design decisions: PF_MEMALLOC context: swap_rw sets PF_MEMALLOC for both reads and writes. Writes need it because swap writeback runs during reclaim. Reads need it because swap-in page faults could otherwise trigger reclaim that starts competing swap writes, risking btree lock deadlock. The BCH_WRITE_swap flag propagates PF_MEMALLOC to the write index kworker. Btree node pinning: at swapon, leaf nodes for extents, inodes, and alloc btrees covering the swap file are marked noevict. This avoids disk reads during memory reclaim — the hot path only does in-memory btree lookups. Btree cache pre-reserve: 16 MB of btree node buffers are pre-allocated on bc->freeable at swapon using GFP_NORETRY to avoid OOM on small VMs. These cover ~5 concurrent fully-cold btree traversals across extents + inodes + alloc btrees. Disk reservation: swap_pages * PAGE_SECTORS reserved at swapon to prevent ENOSPC during COW writes. Each swap write allocates a new physical block before freeing the old one; without a reservation, concurrent non-swap writers could consume all free space. Bkey buffer pre-allocation: 2048-byte buffer allocated with GFP_NOWAIT before entering PF_MEMALLOC, avoiding __GFP_NOFAIL WARN loops when bch2_bkey_buf_realloc spills from the 96-byte on-stack buffer. IS_SWAPFILE bypass: swap files skip pagecache locking in the direct I/O path since the kernel manages swap file pages directly. Write-buffer flush deadlock fix: upgrade PF_MEMALLOC_NOFS to PF_MEMALLOC in the write-buffer flush path. The flush holds wb->flushing.lock while traversing btree nodes; if a node allocation enters direct reclaim, reclaim can need the journal, which needs write-buffer flush, which is blocked on the mutex — deadlock. PF_MEMALLOC prevents entering reclaim; allocation failures are already handled. Diagnostics: rate-limited warning when extents btree leaves exceed 80% fill (early warning before the 67% split threshold), swap I/O stall detection at 2s (WARN) and 10s (BUG in debug builds). Tested at 128-512M RAM with parallel ablation matrix covering: pinning on/off, PF_MEMALLOC on/off, btree cache thrashing, drop_caches, multi-device tiered storage, lz4/zstd compression, 5-minute sustained pressure, concurrent file I/O workload, and disk-throttled I/O. Signed-off-by: Matthias Schorer <matthias@schorer.dev>
Document the design rationale for COW swap file support: the reclaim deadlock problem, how other filesystems handle it, and the mechanisms used (PF_MEMALLOC, btree pinning, cache pre-reserve, disk reservation, bkey pre-allocation, write-buffer flush noreclaim). Includes analysis of dead ends (pre-fragmentation defeated by extent merging, GFP flag whack-a-mole, kworker flag propagation) and adversarial analysis (dead-key accumulation, shared btree nodes, ENOSPC scenarios). bcachefs: fix unlimited lock hold times in trans bounds bch2_trans_begin() evaluates whether a transaction has exceeded BTREE_TRANS_MAX_LOCK_HOLD_TIME_NS (10ms) and if so cleanly yields locks via cond_resched(). However, it was resetting trans->last_begin_time unconditionally on *every iteration* of loop macros like for_each_btree_key. This meant that tight loops processing many extents, such as background compression or bch-reconcile, would continuously reset their own yield timer since individual loop iterations execute in microseconds. This permitted single transactions to monopolize crucial btree locks limitlessly, starving other core filesystem operations (e.g. the swap kworker blocking for 120s+ during memory pressure, causing system lockups). This patch introduces a new trans->last_yield_time field which is ONLY cleared when locks are legitimately dropped (in trans_set_unlocked), properly accumulating lock hold times across independent bch2_trans_begin() boundaries until the threshold is organically reached.
59b05b2 to
fee6f09
Compare
0 = always drop SRCU before blocking (bch2_trans_unlock_long),
equivalent to the original code path
N > 0 = two-phase escalation with (N-1)ms timeout before
dropping SRCU
This fixes a semantic bug where timeout=0 would still keep SRCU
held during sub-jiffy operations, preventing SRCU grace periods
from completing and potentially causing stalls.
Runtime tunable via:
/sys/module/bcachefs/parameters/srcu_escalation_timeout_ms
The move ratelimiter used global in-flight limits (move_ios_in_flight, move_bytes_in_flight) for all devices. This overwhelms rotational devices with deep queues of random writes, causing head thrashing and multi-second write latencies that block btree transaction state and stall journal reclaim. Add per-context max_ios_in_flight and max_sectors_in_flight fields to moving_context, initialized from c->opts. The reconcile phys thread (which already runs per-device for rotational devices) overrides these with much lower limits suitable for spinning disks. Add module parameter move_ios_in_flight_rotational (default 8) to allow runtime tuning without recompilation. Signed-off-by: Matthias <matthias@example.com>
a6d79f5 to
82c906f
Compare
c43a605 to
84ce651
Compare
|
I rebased this for bcachefs-tools because I wanted to test-drive it myself. From a quick test (yolo running it on my system and then filling up memory), it seems to still work, however no guarantees I made the right decisions merging in the changes from master since I am not familiar with the codebase. I left a few extra comments (search for "Katalin") where my decisions are probably questionable. This is just a straight rebase with none of the review comments addressed, but hopefully this is something you can pick up future work off of. Details |
|
oh boy this is going to be a big patchset to review
want to join the IRC channel? we should discuss
…On Thu, May 28, 2026, 11:11 PM Katalin Rebhan ***@***.***> wrote:
*dblsaiko* left a comment (koverstreet/bcachefs#1072)
<#1072 (comment)>
I rebased this for bcachefs-tools because I wanted to test-drive it
myself. From a quick test (yolo running it on my system and then filling up
memory), it seems to still work, however no guarantees I made the right
decisions merging in the changes from master since I am not familiar with
the codebase. I left a few extra comments (search for "Katalin") where my
decisions are probably questionable.
This is just a straight rebase with none of the review comments addressed,
but hopefully this is something you can pick up future work off of.
The following changes since commit b8f8939fa2437b36208b43206e063ac36266aebe:
btree commit: btree_key_can_insert_slowpath() (2026-05-28 15:35:21 -0400)
are available in the Git repository at:
https://git.dblsaiko.net/bcachefs/ swap-files
for you to fetch changes up to 6dd4637e1d615179051cdbb9a35042c8de0f5077:
bcachefs: make journal bucket discards asynchronous to avoid paralyzing journal writes (2026-05-29 06:00:20 +0200)
Details
Matthias Goergens (10):
bcachefs: SRCU lock investigation and VM reproducer
bcachefs: don't hold srcu lock across blocking operations
docs: note foreground I/O stall resolution in INVESTIGATION.md
bcachefs: swap file support via SWP_FS_OPS
Documentation: bcachefs: add swap file design document
srcu escalation: fix timeout=0 to mean original behavior
bcachefs: per-device rate limiting for background data moves
bcachefs: Add checksum_only flag for pure B-Tree metadata replacement
bcachefs: Elevate B-Tree journal reclaim writes to REQ_PRIO
bcachefs: make journal bucket discards asynchronous to avoid paralyzing journal writes
Documentation/swap.rst | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
INVESTIGATION.md | 301 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
build-initramfs.sh | 38 +++++++++++
fs/Makefile | 3 +-
fs/alloc/foreground.c | 2 +-
fs/btree/bkey_buf.h | 6 ++
fs/btree/cache.c | 80 +++++++++++++++++++++--
fs/btree/cache.h | 2 +
fs/btree/commit.c | 14 +++-
fs/btree/interior.c | 14 ++--
fs/btree/iter.c | 25 +++++--
fs/btree/iter.h | 43 ++++++++++++
fs/btree/locking.c | 15 ++++-
fs/btree/locking.h | 1 +
fs/btree/read.c | 2 +-
fs/btree/types.h | 1 +
fs/btree/write.c | 6 +-
fs/btree/write_buffer.c | 10 +--
fs/data/ec/io.c | 2 +-
fs/data/migrate.c | 2 +-
fs/data/move.c | 15 +++--
fs/data/move.h | 8 ++-
fs/data/reconcile/work.c | 14 ++++
fs/data/update.c | 37 ++++++++++-
fs/data/update.h | 1 +
fs/data/write.c | 72 ++++++++++++++++++--
fs/data/write.h | 1 +
fs/data/write_types.h | 5 +-
fs/debug/tests.c | 2 +
fs/init/error.c | 17 +----
fs/journal/reclaim.c | 8 ++-
fs/vfs/buffered.c | 2 +
fs/vfs/direct.c | 34 ++++++++--
fs/vfs/fs.c | 6 +-
fs/vfs/fs.h | 7 ++
fs/vfs/swap.c | 310 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
fs/vfs/swap.h | 22 +++++++
prepare-vm-disks.sh | 66 +++++++++++++++++++
run-vm-test.sh | 75 +++++++++++++++++++++
vm-init-rs/.gitignore | 2 +
vm-init-rs/Cargo.toml | 12 ++++
vm-init-rs/src/main.rs | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
42 files changed, 1678 insertions(+), 67 deletions(-)
create mode 100644 Documentation/swap.rst
create mode 100644 INVESTIGATION.md
create mode 100755 build-initramfs.sh
create mode 100644 fs/vfs/swap.c
create mode 100644 fs/vfs/swap.h
create mode 100755 prepare-vm-disks.sh
create mode 100755 run-vm-test.sh
create mode 100644 vm-init-rs/.gitignore
create mode 100644 vm-init-rs/Cargo.toml
create mode 100644 vm-init-rs/src/main.rs
—
Reply to this email directly, view it on GitHub
<#1072?email_source=notifications&email_token=AAPGX3UEWT3VEC7I42NZ6QD45EEXDA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTINJXGA2DKMJQGQ22M4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-4570451045>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAPGX3TLHYGW6IZO6GABST345EEXDAVCNFSM6AAAAACWDUCG3GVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DKNZQGQ2TCMBUGU>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/AAPGX3QDGNWLP2YM2DACW3D45EEXDA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTINJXGA2DKMJQGQ22M4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJKTGN5XXIZLSL5UW64Y>
and Android
<https://github.com/notifications/mobile/android/AAPGX3XF6XOFUFQHEKFJYOD45EEXDA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTINJXGA2DKMJQGQ22M4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>.
Download it today!
You are receiving this because you commented.Message ID:
***@***.***>
|
|
For easier review against the current bcachefs-tools tree, I refreshed a cleaned carry here: It preserves Matthias as the commit author, keeps the implementation as one focused patch on |
Add swap file support for bcachefs using the
SWP_FS_OPSpath (samemechanism as NFS swap). The filesystem stays in the I/O loop for swap
operations, so swap data gets checksumming, encryption, replication,
compression, and multi-device support.
Unlike btrfs (which disables COW, checksums, and compression for swap
files), bcachefs swap files use the normal COW write path.
How to use
Key design decisions
PF_MEMALLOC context:
swap_rwsets PF_MEMALLOC for both reads andwrites to prevent reclaim re-entry deadlocks. The
BCH_WRITE_swapflagpropagates this to the write index kworker.
Btree node pinning: at swapon, leaf nodes covering the swap file are
marked noevict to avoid disk reads during memory reclaim.
Btree cache pre-reserve: 16 MB of btree node buffers pre-allocated at
swapon (with
GFP_NORETRYto avoid OOM on small VMs).Disk reservation:
swap_pages × PAGE_SECTORSreserved at swapon toprevent ENOSPC during COW writes.
Bkey buffer pre-allocation: 2048-byte buffer allocated with
GFP_NOWAITbefore entering PF_MEMALLOC, avoiding
__GFP_NOFAILWARN loops inbch2_bkey_buf_realloc.Write-buffer flush noreclaim: upgraded
PF_MEMALLOC_NOFStoPF_MEMALLOCin the write-buffer flush path. The flush holdswb->flushing.lockwhile traversing btree nodes; if a node allocationenters direct reclaim, reclaim can need the journal, which needs
write-buffer flush → deadlock. This is a pre-existing bcachefs bug
not specific to swap, but swap makes it reliably reproducible.
IS_SWAPFILE bypass: swap files skip pagecache locking in the direct I/O
path.
80% fill monitoring: rate-limited warning when extents btree leaves
exceed 80% fill, for early warning before the 67% split threshold.
Test results (22-test ablation matrix)
18/22 PASS — 14/16 core tests pass, 2 expected failures, 2 throttle
timeouts.
Tested at 128–512M RAM covering: pinning on/off, PF_MEMALLOC on/off, btree
cache thrashing, drop_caches, swapoff/swapon cycles, concurrent file I/O
workload, 5-minute sustained pressure, and disk-throttled I/O.
Expected failures (prove hardening is necessary):
small-128m-nothing: all hardening disabled → deadlocksmall-128m-noreclaim: PF_MEMALLOC disabled → reclaim deadlockThrottle timeouts (test harness issue, not kernel bugs):
small-192m-throttled: I/O throttled to 512 KB/s, test times outlarge-192m-throttled: sameDesign document
Full design rationale in
Documentation/filesystems/bcachefs/swap.rst,including dead ends (pre-fragmentation defeated by extent merging,
GFP flag whack-a-mole) and adversarial analysis (dead-key accumulation,
shared btree nodes, ENOSPC).
Closes #368