* [PATCH v7 0/5] block: enable RWF_DONTCACHE for block devices
@ 2026-07-30 6:57 Tal Zussman
2026-07-30 6:57 ` [PATCH v7 1/5] block: introduce bio_in_atomic() Tal Zussman
` (4 more replies)
0 siblings, 5 replies; 13+ messages in thread
From: Tal Zussman @ 2026-07-30 6:57 UTC (permalink / raw)
To: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo
Cc: Dave Chinner, Bart Van Assche, Hillf Danton,
Sebastian Andrzej Siewior, linux-block, linux-kernel, linux-xfs,
linux-fsdevel, linux-mm, linux-erofs, Tal Zussman,
Christoph Hellwig
Add support for using RWF_DONTCACHE with block devices.
Dropbehind pruning needs to be done in non-IRQ context, but block
devices complete writeback in IRQ context. To fix this, we defer
dropbehind invalidation to task context. Add infrastructure that lets
bi_end_io callbacks run from a worker, in two forms:
1. BIO_COMPLETE_IN_TASK, a bio flag the submitter sets when it knows
upfront that the callback needs task context, as in the dropbehind
writeback paths.
2. bio_complete_in_task(), a helper that callbacks can invoke from
bi_end_io() when the decision to defer is dynamic, as in iomap
fserror reporting.
These queue the bio to a per-CPU list and schedule a per-CPU work item
to run the completions from task context.
Patch 1 moves erofs's atomic context detection into the block layer as
bio_in_atomic() and converts erofs to use it.
Patch 2 adds the block layer task-context completion infrastructure,
with both the flag and the procedural helper. This builds on top of
suggestions by Matthew and Christoph: the procedural helper and
bio_in_atomic() come from Christoph's "bio completion in task
enhancements / experiments" series [1]. The procedural helper is unused
in this series, but has multiple users planned [1][2].
Patch 3 wires BIO_COMPLETE_IN_TASK into iomap writeback for dropbehind
folios, removes IOMAP_IOEND_DONTCACHE, and removes the DONTCACHE
workqueue deferral from XFS.
Patch 4 sets up DONTCACHE support for buffer-head-based I/O by setting
BIO_COMPLETE_IN_TASK in __bh_submit() for the CONFIG_BUFFER_HEAD path.
Patch 5 enables RWF_DONTCACHE for block devices based on the previous
support. This support is useful for databases that operate on raw block
devices, among other userspace applications.
I tested this (with CONFIG_BUFFER_HEAD=y) for reads and writes on a
single block device on a VM, so results may be noisy.
Reads were tested on the root partition with a 45GB range (~2x RAM).
Writes were tested on a disabled swap partition (~1GB) in a memcg of
size 244MB to force reclaim pressure.
Results:
===== READS (/dev/nvme0n1p2) =====
sec normal MB/s dontcache MB/s
---- ------------ --------------
1 1098.6 1609.0
2 1270.3 1506.6
3 1093.3 1576.5
4 1141.8 2393.9
5 1365.3 2793.8
6 1324.6 2065.9
7 879.6 1920.7
8 1434.1 1662.4
9 1184.9 1857.9
10 1166.4 1702.8
11 1161.4 1653.4
12 1086.9 1555.4
13 1198.5 1718.9
14 1111.9 1752.2
---- ------------ --------------
avg 1173.7 1828.8 (+56%)
==== WRITES (/dev/nvme0n1p3) =====
sec normal MB/s dontcache MB/s
---- ------------ --------------
1 692.4 9297.7
2 4810.8 9342.8
3 5221.7 2955.2
4 396.7 8488.3
5 7249.2 9249.3
6 6695.4 1376.2
7 122.9 9125.8
8 5486.5 9414.7
9 6921.5 8743.5
10 27.9 8997.8
---- ------------ --------------
avg 3762.5 7699.1 (+105%)
[1]: https://lore.kernel.org/all/20260409160243.1008358-1-hch@lst.de/
[2]: https://lore.kernel.org/linux-block/20260723145000.116419-1-hch@lst.de/
---
Changes in v7:
- Rebase on v7.2-rc5.
- 1/5: New patch. Move the atomic context detection into the block
layer as bio_in_atomic() and convert erofs to use it, per Christoph.
- 2/5: Drop the 1-jiffie completion delay and schedule the completion
work immediately. Use a plain work item instead of delayed_work
accordingly, per Jan and Christoph.
- 2/5: Drop the local_lock and use local IRQ disabling instead to
resolve PREEMPT_RT concerns raised by Sashiko.
- 2/5: Drop the need_resched() requeue from the work function loop, per
Christoph and Hillf.
- 2/5: Use BIO_COMPLETE_IN_TASK as a sentinel in bio_complete_in_task()
so a callback running from the worker does not re-defer, per Sashiko.
- 2/5: Requeue pending completions when a CPU comes back online, per
Sashiko.
- 4/5: Check for op_is_write() before deferring to avoid deferring read
completions, per Sashiko.
- 4/5: Set BIO_COMPLETE_IN_TASK in __bh_submit() for dropbehind write
bios instead of introducing block_write_begin_iocb() and threading the
kiocb through buffer_head writeback. This was dead code.
- 5/5: Drop the blkdev_write_begin() conversion accordingly.
- Link to v6: https://lore.kernel.org/r/20260514-blk-dontcache-v6-0-782e2fa7477b@columbia.edu
Changes in v6:
- Remove RFC tag
- Rebase on v7.1-rc3.
- 1/4: Revert to using a bio_list, per Jens.
- 1/4: Restructure and simplify work function loop.
- 1/4: Expose both the flag and procedural version, in order to allow
static and dynamic deferral decisions, per conversation with Matthew
and Christoph at LSFMM.
- 1/4: Use bio_in_atomic() predicate, per Christoph.
- 1/4: Use the CPU hot-unplug protocol from mm/vmstat.c, to take into
account use of delayed_work.
- 1/4: Mark the workqueue WQ_PERCPU.
- 1/4: Add comments.
- 3/4 and 4/4: Split into two patches, per Christoph.
- 3/4: Drop the cont_write_begin() change. Block devices don't go
through cont_write_begin(), so it was out of scope and was left over
from v1.
- Link to v5: https://lore.kernel.org/r/20260408-blk-dontcache-v5-0-0f080c20a96f@columbia.edu
Changes in v5:
- 1/3: Replace local_lock + bio_list with struct llist, per Dave.
- 1/3: Use delayed_work with 1-jiffie delay, per Dave.
- 1/3: Add dedicated workqueue to avoid deadlocks, per Christoph.
- 1/3: Restructure work function as do/while loop and only schedule work
originally when the list was previously empty, per Jens.
- 2/3: Delete IOMAP_IOEND_DONTCACHE and its NOMERGE entry, per Matthew
and Christoph.
- Link to v4: https://lore.kernel.org/r/20260325-blk-dontcache-v4-0-c4b56db43f64@columbia.edu
Changes in v4:
- 1/3: Move dropbehind deferral from folio-level to bio-level using
BIO_COMPLETE_IN_TASK, per Matthew and Jan.
- 1/3: Work function yields on need_resched() to avoid hogging the CPU,
per Jan.
- 2/3: New patch. Set BIO_COMPLETE_IN_TASK on iomap writeback bios for
DONTCACHE folios, removing the need for XFS-specific workqueue
deferral.
- 3/3: Set BIO_COMPLETE_IN_TASK in submit_bh_wbc() for buffer_head
path.
- 3/3: Update commit message to mention CONFIG_BUFFER_HEAD=n path.
- Link to v3: https://lore.kernel.org/r/20260227-blk-dontcache-v3-0-cd309ccd5868@columbia.edu
Changes in v3:
- 1/2: Convert dropbehind deferral to per-CPU folio_batches protected by
local_lock using per-CPU work items, to reduce contention, per Jens.
- 1/2: Call folio_end_dropbehind_irq() directly from
folio_end_writeback(), per Jens.
- 1/2: Add CPU hotplug dead callback to drain the departing CPU's folio
batch.
- 2/2: Introduce block_write_begin_iocb(), per Christoph.
- 2/2: Dropped R-b due to changes.
- Link to v2: https://lore.kernel.org/r/20260225-blk-dontcache-v2-0-70e7ac4f7108@columbia.edu
Changes in v2:
- Add R-b from Jan Kara for 2/2.
- Add patch to defer dropbehind completion from IRQ context via a work
item (1/2).
- Add initial performance numbers to cover letter.
- Link to v1: https://lore.kernel.org/r/20260218-blk-dontcache-v1-1-fad6675ef71f@columbia.edu
---
Tal Zussman (5):
block: introduce bio_in_atomic()
block: add task-context bio completion infrastructure
iomap: use BIO_COMPLETE_IN_TASK for dropbehind writeback
buffer: set BIO_COMPLETE_IN_TASK for dropbehind writeback
block: enable RWF_DONTCACHE for block devices
block/bio.c | 132 +++++++++++++++++++++++++++++++++++++++++++++-
block/fops.c | 2 +-
fs/buffer.c | 3 ++
fs/erofs/zdata.c | 11 +---
fs/iomap/ioend.c | 5 +-
fs/xfs/xfs_aops.c | 4 --
include/linux/bio.h | 39 ++++++++++++++
include/linux/blk_types.h | 1 +
include/linux/iomap.h | 5 +-
9 files changed, 180 insertions(+), 22 deletions(-)
---
base-commit: f5098b6bae761e346ebcd9da7f95622c04733cff
change-id: 20260218-blk-dontcache-338133dd045e
Best regards,
--
Tal Zussman <tz2294@columbia.edu>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v7 1/5] block: introduce bio_in_atomic()
2026-07-30 6:57 [PATCH v7 0/5] block: enable RWF_DONTCACHE for block devices Tal Zussman
@ 2026-07-30 6:57 ` Tal Zussman
2026-07-30 8:06 ` Christoph Hellwig
2026-07-30 9:02 ` Jan Kara
2026-07-30 6:57 ` [PATCH v7 2/5] block: add task-context bio completion infrastructure Tal Zussman
` (3 subsequent siblings)
4 siblings, 2 replies; 13+ messages in thread
From: Tal Zussman @ 2026-07-30 6:57 UTC (permalink / raw)
To: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo
Cc: Dave Chinner, Bart Van Assche, Hillf Danton,
Sebastian Andrzej Siewior, linux-block, linux-kernel, linux-xfs,
linux-fsdevel, linux-mm, linux-erofs, Tal Zussman
Move the atomic context detection logic from erofs's z_erofs_in_atomic()
into the block layer as bio_in_atomic(). This helper returns true when
the current context is unsafe for sleeping bio completion handlers (e.g.,
hard/soft IRQ, preempt-disabled).
The logic was originally added to erofs in commit c99fab6e80b7 ("erofs:
fix atomic context detection when !CONFIG_DEBUG_LOCK_ALLOC"). A
subsequent patch will use it in the block layer's bio completion
infrastructure, so move it to include/linux/bio.h where both subsystems
can share it.
Convert erofs to call the new bio_in_atomic() directly.
Suggested-by: Christoph Hellwig <hch@infradead.org>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
---
fs/erofs/zdata.c | 11 +----------
include/linux/bio.h | 15 +++++++++++++++
2 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c
index 74520e910259..f796bb3ef53b 100644
--- a/fs/erofs/zdata.c
+++ b/fs/erofs/zdata.c
@@ -1427,15 +1427,6 @@ static void z_erofs_decompressqueue_kthread_work(struct kthread_work *work)
}
#endif
-/* Use (kthread_)work in atomic contexts to minimize scheduling overhead */
-static inline bool z_erofs_in_atomic(void)
-{
- if (IS_ENABLED(CONFIG_PREEMPTION) && rcu_preempt_depth())
- return true;
- if (!IS_ENABLED(CONFIG_PREEMPT_COUNT))
- return true;
- return !preemptible();
-}
static void z_erofs_decompress_kickoff(struct z_erofs_decompressqueue *io,
int bios)
@@ -1452,7 +1443,7 @@ static void z_erofs_decompress_kickoff(struct z_erofs_decompressqueue *io,
if (atomic_add_return(bios, &io->pending_bios))
return;
- if (z_erofs_in_atomic()) {
+ if (bio_in_atomic()) {
/* See `sync_decompress` in sysfs-fs-erofs for more details */
if (sbi->sync_decompress == EROFS_SYNC_DECOMPRESS_AUTO)
sbi->sync_decompress = EROFS_SYNC_DECOMPRESS_FORCE_ON;
diff --git a/include/linux/bio.h b/include/linux/bio.h
index 8f33f717b14f..62a983758e09 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -368,6 +368,21 @@ static inline struct bio *bio_alloc(struct block_device *bdev,
void submit_bio(struct bio *bio);
+/**
+ * bio_in_atomic - check if the current context is unsafe for bio completion
+ *
+ * Return: %true in atomic contexts (e.g. hard/soft IRQ, preempt-disabled);
+ * %false when a bio can be safely completed in the current context.
+ */
+static inline bool bio_in_atomic(void)
+{
+ if (IS_ENABLED(CONFIG_PREEMPTION) && rcu_preempt_depth())
+ return true;
+ if (!IS_ENABLED(CONFIG_PREEMPT_COUNT))
+ return true;
+ return !preemptible();
+}
+
extern void bio_endio(struct bio *);
/**
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 2/5] block: add task-context bio completion infrastructure
2026-07-30 6:57 [PATCH v7 0/5] block: enable RWF_DONTCACHE for block devices Tal Zussman
2026-07-30 6:57 ` [PATCH v7 1/5] block: introduce bio_in_atomic() Tal Zussman
@ 2026-07-30 6:57 ` Tal Zussman
2026-07-30 8:09 ` Christoph Hellwig
2026-07-30 9:29 ` Jan Kara
2026-07-30 6:57 ` [PATCH v7 3/5] iomap: use BIO_COMPLETE_IN_TASK for dropbehind writeback Tal Zussman
` (2 subsequent siblings)
4 siblings, 2 replies; 13+ messages in thread
From: Tal Zussman @ 2026-07-30 6:57 UTC (permalink / raw)
To: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo
Cc: Dave Chinner, Bart Van Assche, Hillf Danton,
Sebastian Andrzej Siewior, linux-block, linux-kernel, linux-xfs,
linux-fsdevel, linux-mm, linux-erofs, Tal Zussman
Some bio completion handlers need to run from preemptible task context,
but bio_endio() may be called from IRQ context (e.g., buffer_head
writeback). Callers need a way to ensure their callback eventually runs
from a sleepable context. Add infrastructure for that, in two forms:
1. BIO_COMPLETE_IN_TASK, a bio flag the submitter sets when it knows
in advance that its callback needs task context (e.g., dropbehind
writeback). bio_endio() sees the flag and offloads completion to a
worker automatically.
2. bio_complete_in_task(), a helper that completion callbacks can
invoke from within bi_end_io() when the deferral decision is
dynamic (e.g., fserror reporting).
Both share a per-CPU list drained by a work item on a WQ_PERCPU
workqueue. Producers push the bio onto the local CPU's list and schedule
the work item, which then dispatches each bio's bi_end_io() from task
context.
Both methods are gated on bio_in_atomic(), which returns true in any
context where a sleeping bi_end_io() is unsafe, including
non-preemptible task context.
Two CPU hotplug callbacks are used to drain remaining bios from the
departing CPU's batch, while maintaining the per-CPU behavior. The
CPUHP_AP_ONLINE_DYN callback disables the per-CPU work item while the
CPU is still online, preventing it from running on an unbound worker
later. CPUHP_BP_PREPARE_DYN then drains any bios added between disabling
the work item and CPU offline.
Link: https://lore.kernel.org/all/20260409160243.1008358-1-hch@lst.de/
Suggested-by: Matthew Wilcox <willy@infradead.org>
Suggested-by: Christoph Hellwig <hch@infradead.org>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
---
block/bio.c | 132 +++++++++++++++++++++++++++++++++++++++++++++-
include/linux/bio.h | 24 +++++++++
include/linux/blk_types.h | 1 +
3 files changed, 156 insertions(+), 1 deletion(-)
diff --git a/block/bio.c b/block/bio.c
index 6a2f6fc3413e..a9610950325b 100644
--- a/block/bio.c
+++ b/block/bio.c
@@ -1741,6 +1741,61 @@ void bio_check_pages_dirty(struct bio *bio)
schedule_work(&bio_dirty_work);
}
+/*
+ * Infrastructure for deferring bio completions to task-context via a per-CPU
+ * workqueue. Triggered either by the BIO_COMPLETE_IN_TASK bio flag (static
+ * decision at submit time) or by calling bio_complete_in_task() from
+ * bi_end_io() (dynamic decision at completion time).
+ */
+
+struct bio_complete_batch {
+ struct bio_list list;
+ struct work_struct work;
+ int cpu;
+};
+
+static DEFINE_PER_CPU(struct bio_complete_batch, bio_complete_batch);
+static struct workqueue_struct *bio_complete_wq;
+
+static void bio_complete_work_fn(struct work_struct *w)
+{
+ struct bio_complete_batch *batch =
+ container_of(w, struct bio_complete_batch, work);
+
+ while (1) {
+ struct bio_list list;
+ struct bio *bio;
+
+ local_irq_disable();
+ list = batch->list;
+ bio_list_init(&batch->list);
+ local_irq_enable();
+
+ if (bio_list_empty(&list))
+ break;
+
+ while ((bio = bio_list_pop(&list)))
+ bio->bi_end_io(bio);
+ }
+}
+
+void __bio_complete_in_task(struct bio *bio)
+{
+ struct bio_complete_batch *batch;
+ unsigned long flags;
+ bool was_empty;
+
+ local_irq_save(flags);
+ batch = this_cpu_ptr(&bio_complete_batch);
+ was_empty = bio_list_empty(&batch->list);
+ bio_list_add(&batch->list, bio);
+ local_irq_restore(flags);
+
+ if (was_empty)
+ queue_work_on(batch->cpu, bio_complete_wq, &batch->work);
+}
+EXPORT_SYMBOL_GPL(__bio_complete_in_task);
+
static inline bool bio_remaining_done(struct bio *bio)
{
/*
@@ -1815,7 +1870,9 @@ void bio_endio(struct bio *bio)
}
#endif
- if (bio->bi_end_io)
+ if (bio_flagged(bio, BIO_COMPLETE_IN_TASK) && bio_in_atomic())
+ __bio_complete_in_task(bio);
+ else if (bio->bi_end_io)
bio->bi_end_io(bio);
}
EXPORT_SYMBOL(bio_endio);
@@ -2001,6 +2058,55 @@ int bioset_init(struct bio_set *bs,
}
EXPORT_SYMBOL(bioset_init);
+static int bio_complete_batch_cpu_online(unsigned int cpu)
+{
+ struct bio_complete_batch *batch = &per_cpu(bio_complete_batch, cpu);
+
+ enable_work(&batch->work);
+ if (!bio_list_empty(&batch->list))
+ queue_work_on(cpu, bio_complete_wq, &batch->work);
+ return 0;
+}
+
+/*
+ * Disable this CPU's work item so that it cannot run on an unbound worker
+ * after the CPU is offlined.
+ */
+static int bio_complete_batch_cpu_down_prep(unsigned int cpu)
+{
+ disable_work_sync(&per_cpu(bio_complete_batch, cpu).work);
+ return 0;
+}
+
+/*
+ * Drain a dead CPU's deferred bio completions. The CPU is dead and the worker
+ * is canceled so no locking is needed.
+ */
+static int bio_complete_batch_cpu_dead(unsigned int cpu)
+{
+ struct bio_complete_batch *batch =
+ per_cpu_ptr(&bio_complete_batch, cpu);
+ struct bio *bio;
+
+ while ((bio = bio_list_pop(&batch->list)))
+ bio->bi_end_io(bio);
+
+ return 0;
+}
+
+static void __init bio_complete_batch_init(int cpu)
+{
+ struct bio_complete_batch *batch =
+ per_cpu_ptr(&bio_complete_batch, cpu);
+
+ bio_list_init(&batch->list);
+ INIT_WORK(&batch->work, bio_complete_work_fn);
+ batch->cpu = cpu;
+
+ if (!cpu_online(cpu))
+ disable_work_sync(&batch->work);
+}
+
static int __init init_bio(void)
{
int i;
@@ -2015,6 +2121,30 @@ static int __init init_bio(void)
SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
}
+ for_each_possible_cpu(i)
+ bio_complete_batch_init(i);
+
+ bio_complete_wq = alloc_workqueue("bio_complete",
+ WQ_MEM_RECLAIM | WQ_PERCPU, 0);
+ if (!bio_complete_wq)
+ panic("bio: can't allocate bio_complete workqueue\n");
+
+ /*
+ * bio task-context completion draining on hot-unplugged CPUs:
+ *
+ * 1. Stop the per-CPU work item while the CPU is still online, so
+ * that it cannot run on an unbound worker later.
+ * 2. Drain leftover bios added between worker disabling and CPU
+ * offlining.
+ */
+ cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,
+ "block/bio:complete:online",
+ bio_complete_batch_cpu_online,
+ bio_complete_batch_cpu_down_prep);
+ cpuhp_setup_state_nocalls(CPUHP_BP_PREPARE_DYN,
+ "block/bio:complete:dead",
+ NULL, bio_complete_batch_cpu_dead);
+
cpuhp_setup_state_multi(CPUHP_BIO_DEAD, "block/bio:dead", NULL,
bio_cpu_dead);
diff --git a/include/linux/bio.h b/include/linux/bio.h
index 62a983758e09..a8091a3e9d87 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -383,6 +383,30 @@ static inline bool bio_in_atomic(void)
return !preemptible();
}
+void __bio_complete_in_task(struct bio *bio);
+
+/**
+ * bio_complete_in_task - ensure a bio is completed in preemptible task context
+ * @bio: bio to complete
+ *
+ * If called from non-task context, offload the bio completion to a worker
+ * thread and return %true. Else return %false and do nothing.
+ *
+ * Uses BIO_COMPLETE_IN_TASK as a sentinel: if set, the bio was already
+ * deferred and we are running in the worker — return %false so the
+ * callback proceeds instead of re-deferring.
+ */
+static inline bool bio_complete_in_task(struct bio *bio)
+{
+ if (bio_flagged(bio, BIO_COMPLETE_IN_TASK))
+ return false;
+ if (!bio_in_atomic())
+ return false;
+ bio_set_flag(bio, BIO_COMPLETE_IN_TASK);
+ __bio_complete_in_task(bio);
+ return true;
+}
+
extern void bio_endio(struct bio *);
/**
diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h
index 8808ee76e73c..d49d97a050d0 100644
--- a/include/linux/blk_types.h
+++ b/include/linux/blk_types.h
@@ -322,6 +322,7 @@ enum {
BIO_REMAPPED,
BIO_ZONE_WRITE_PLUGGING, /* bio handled through zone write plugging */
BIO_EMULATES_ZONE_APPEND, /* bio emulates a zone append operation */
+ BIO_COMPLETE_IN_TASK, /* complete bi_end_io() in task context */
BIO_FLAG_LAST
};
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 3/5] iomap: use BIO_COMPLETE_IN_TASK for dropbehind writeback
2026-07-30 6:57 [PATCH v7 0/5] block: enable RWF_DONTCACHE for block devices Tal Zussman
2026-07-30 6:57 ` [PATCH v7 1/5] block: introduce bio_in_atomic() Tal Zussman
2026-07-30 6:57 ` [PATCH v7 2/5] block: add task-context bio completion infrastructure Tal Zussman
@ 2026-07-30 6:57 ` Tal Zussman
2026-07-30 8:13 ` Christoph Hellwig
2026-07-30 6:57 ` [PATCH v7 4/5] buffer: set " Tal Zussman
2026-07-30 6:57 ` [PATCH v7 5/5] block: enable RWF_DONTCACHE for block devices Tal Zussman
4 siblings, 1 reply; 13+ messages in thread
From: Tal Zussman @ 2026-07-30 6:57 UTC (permalink / raw)
To: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo
Cc: Dave Chinner, Bart Van Assche, Hillf Danton,
Sebastian Andrzej Siewior, linux-block, linux-kernel, linux-xfs,
linux-fsdevel, linux-mm, linux-erofs, Christoph Hellwig,
Tal Zussman
Set BIO_COMPLETE_IN_TASK on iomap writeback bios when a dropbehind folio
is added. This ensures that bi_end_io runs in task context, where
folio_end_dropbehind() can safely invalidate folios.
With the bio layer now handling task-context deferral generically,
IOMAP_IOEND_DONTCACHE is no longer needed, as XFS no longer needs to
route DONTCACHE ioends through its completion workqueue. Remove the flag
and its NOMERGE entry.
Without the NOMERGE, regular I/Os that get merged with a dropbehind
folio will also have their completion deferred to task context.
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
---
fs/iomap/ioend.c | 5 +++--
fs/xfs/xfs_aops.c | 4 ----
include/linux/iomap.h | 5 +----
3 files changed, 4 insertions(+), 10 deletions(-)
diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c
index 30468d51b5ad..1ae8a8fb8503 100644
--- a/fs/iomap/ioend.c
+++ b/fs/iomap/ioend.c
@@ -238,8 +238,6 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio,
if (wpc->iomap.flags & IOMAP_F_SHARED)
ioend_flags |= IOMAP_IOEND_SHARED;
- if (folio_test_dropbehind(folio))
- ioend_flags |= IOMAP_IOEND_DONTCACHE;
if (pos == wpc->iomap.offset && (wpc->iomap.flags & IOMAP_F_BOUNDARY))
ioend_flags |= IOMAP_IOEND_BOUNDARY;
@@ -256,6 +254,9 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio,
if (!bio_add_folio(&ioend->io_bio, folio, map_len, poff))
goto new_ioend;
+ if (folio_test_dropbehind(folio))
+ bio_set_flag(&ioend->io_bio, BIO_COMPLETE_IN_TASK);
+
/*
* Clamp io_offset and io_size to the incore EOF so that ondisk
* file size updates in the ioend completion are byte-accurate.
diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
index 51293b6f331f..cd8de8c82d78 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -522,10 +522,6 @@ xfs_ioend_needs_wq_completion(
if (ioend->io_flags & (IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_SHARED))
return true;
- /* Page cache invalidation cannot be done in irq context. */
- if (ioend->io_flags & IOMAP_IOEND_DONTCACHE)
- return true;
-
return false;
}
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index 56b43d594e6e..68af4935a107 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -404,16 +404,13 @@ sector_t iomap_bmap(struct address_space *mapping, sector_t bno,
#define IOMAP_IOEND_BOUNDARY (1U << 2)
/* is direct I/O */
#define IOMAP_IOEND_DIRECT (1U << 3)
-/* is DONTCACHE I/O */
-#define IOMAP_IOEND_DONTCACHE (1U << 4)
/*
* Flags that if set on either ioend prevent the merge of two ioends.
* (IOMAP_IOEND_BOUNDARY also prevents merges, but only one-way)
*/
#define IOMAP_IOEND_NOMERGE_FLAGS \
- (IOMAP_IOEND_SHARED | IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_DIRECT | \
- IOMAP_IOEND_DONTCACHE)
+ (IOMAP_IOEND_SHARED | IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_DIRECT)
/*
* Structure for writeback I/O completions.
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 4/5] buffer: set BIO_COMPLETE_IN_TASK for dropbehind writeback
2026-07-30 6:57 [PATCH v7 0/5] block: enable RWF_DONTCACHE for block devices Tal Zussman
` (2 preceding siblings ...)
2026-07-30 6:57 ` [PATCH v7 3/5] iomap: use BIO_COMPLETE_IN_TASK for dropbehind writeback Tal Zussman
@ 2026-07-30 6:57 ` Tal Zussman
2026-07-30 9:31 ` Jan Kara
2026-07-30 6:57 ` [PATCH v7 5/5] block: enable RWF_DONTCACHE for block devices Tal Zussman
4 siblings, 1 reply; 13+ messages in thread
From: Tal Zussman @ 2026-07-30 6:57 UTC (permalink / raw)
To: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo
Cc: Dave Chinner, Bart Van Assche, Hillf Danton,
Sebastian Andrzej Siewior, linux-block, linux-kernel, linux-xfs,
linux-fsdevel, linux-mm, linux-erofs, Christoph Hellwig,
Tal Zussman
Set BIO_COMPLETE_IN_TASK in __bh_submit() for write bios when the folio
has dropbehind set, so that buffer_head writeback completions get
deferred to task context where folio_end_dropbehind() can safely
invalidate folios.
Read completions are not deferred since dropbehind invalidation for
reads is handled synchronously by the reader.
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
---
fs/buffer.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/fs/buffer.c b/fs/buffer.c
index 9af5f061a1f8..6f099847240e 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -1203,6 +1203,9 @@ static void __bh_submit(struct buffer_head *bh, blk_opf_t opf,
bio = bio_alloc(bh->b_bdev, 1, opf, GFP_NOIO);
+ if (folio_test_dropbehind(bh->b_folio) && op_is_write(opf))
+ bio_set_flag(bio, BIO_COMPLETE_IN_TASK);
+
if (IS_ENABLED(CONFIG_FS_ENCRYPTION))
buffer_set_crypto_ctx(bio, bh, GFP_NOIO);
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 5/5] block: enable RWF_DONTCACHE for block devices
2026-07-30 6:57 [PATCH v7 0/5] block: enable RWF_DONTCACHE for block devices Tal Zussman
` (3 preceding siblings ...)
2026-07-30 6:57 ` [PATCH v7 4/5] buffer: set " Tal Zussman
@ 2026-07-30 6:57 ` Tal Zussman
2026-07-30 9:33 ` Jan Kara
4 siblings, 1 reply; 13+ messages in thread
From: Tal Zussman @ 2026-07-30 6:57 UTC (permalink / raw)
To: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo
Cc: Dave Chinner, Bart Van Assche, Hillf Danton,
Sebastian Andrzej Siewior, linux-block, linux-kernel, linux-xfs,
linux-fsdevel, linux-mm, linux-erofs, Christoph Hellwig,
Tal Zussman
Block device buffered reads and writes already pass through
filemap_read() and iomap_file_buffered_write() respectively, both of
which handle IOCB_DONTCACHE. Enable RWF_DONTCACHE for block device files
by setting FOP_DONTCACHE in def_blk_fops.
For CONFIG_BUFFER_HEAD=y, writeback goes through buffer_head's
__bh_submit() which sets BIO_COMPLETE_IN_TASK on dropbehind folios. For
CONFIG_BUFFER_HEAD=n, writeback goes through iomap which handles it via
BIO_COMPLETE_IN_TASK on the ioend bio.
This support is useful for databases that operate on raw block devices,
among other userspace applications.
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
---
block/fops.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/block/fops.c b/block/fops.c
index 15783a6180de..8b39c53b6393 100644
--- a/block/fops.c
+++ b/block/fops.c
@@ -941,7 +941,7 @@ const struct file_operations def_blk_fops = {
.splice_write = iter_file_splice_write,
.fallocate = blkdev_fallocate,
.uring_cmd = blkdev_uring_cmd,
- .fop_flags = FOP_BUFFER_RASYNC,
+ .fop_flags = FOP_BUFFER_RASYNC | FOP_DONTCACHE,
};
static __init int blkdev_init(void)
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v7 1/5] block: introduce bio_in_atomic()
2026-07-30 6:57 ` [PATCH v7 1/5] block: introduce bio_in_atomic() Tal Zussman
@ 2026-07-30 8:06 ` Christoph Hellwig
2026-07-30 9:02 ` Jan Kara
1 sibling, 0 replies; 13+ messages in thread
From: Christoph Hellwig @ 2026-07-30 8:06 UTC (permalink / raw)
To: Tal Zussman
Cc: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo, Dave Chinner,
Bart Van Assche, Hillf Danton, Sebastian Andrzej Siewior,
linux-block, linux-kernel, linux-xfs, linux-fsdevel, linux-mm,
linux-erofs
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 2/5] block: add task-context bio completion infrastructure
2026-07-30 6:57 ` [PATCH v7 2/5] block: add task-context bio completion infrastructure Tal Zussman
@ 2026-07-30 8:09 ` Christoph Hellwig
2026-07-30 9:29 ` Jan Kara
1 sibling, 0 replies; 13+ messages in thread
From: Christoph Hellwig @ 2026-07-30 8:09 UTC (permalink / raw)
To: Tal Zussman
Cc: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo, Dave Chinner,
Bart Van Assche, Hillf Danton, Sebastian Andrzej Siewior,
linux-block, linux-kernel, linux-xfs, linux-fsdevel, linux-mm,
linux-erofs
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 3/5] iomap: use BIO_COMPLETE_IN_TASK for dropbehind writeback
2026-07-30 6:57 ` [PATCH v7 3/5] iomap: use BIO_COMPLETE_IN_TASK for dropbehind writeback Tal Zussman
@ 2026-07-30 8:13 ` Christoph Hellwig
0 siblings, 0 replies; 13+ messages in thread
From: Christoph Hellwig @ 2026-07-30 8:13 UTC (permalink / raw)
To: Tal Zussman
Cc: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo, Dave Chinner,
Bart Van Assche, Hillf Danton, Sebastian Andrzej Siewior,
linux-block, linux-kernel, linux-xfs, linux-fsdevel, linux-mm,
linux-erofs, Christoph Hellwig
On Thu, Jul 30, 2026 at 02:57:53AM -0400, Tal Zussman wrote:
> Set BIO_COMPLETE_IN_TASK on iomap writeback bios when a dropbehind folio
> is added. This ensures that bi_end_io runs in task context, where
> folio_end_dropbehind() can safely invalidate folios.
>
> With the bio layer now handling task-context deferral generically,
> IOMAP_IOEND_DONTCACHE is no longer needed, as XFS no longer needs to
> route DONTCACHE ioends through its completion workqueue. Remove the flag
> and its NOMERGE entry.
>
> Without the NOMERGE, regular I/Os that get merged with a dropbehind
> folio will also have their completion deferred to task context.
>
> Reviewed-by: Christoph Hellwig <hch@lst.de>
> Signed-off-by: Tal Zussman <tz2294@columbia.edu>
This seems to be missing the bug fix you identified for the double
deferral?
I although thought a bit how that could be done cleaner, see below:
> ioend_flags |= IOMAP_IOEND_SHARED;
> - if (folio_test_dropbehind(folio))
> - ioend_flags |= IOMAP_IOEND_DONTCACHE;
I'd keep this flag.
> if (pos == wpc->iomap.offset && (wpc->iomap.flags & IOMAP_F_BOUNDARY))
> ioend_flags |= IOMAP_IOEND_BOUNDARY;
>
> @@ -256,6 +254,9 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio,
> if (!bio_add_folio(&ioend->io_bio, folio, map_len, poff))
> goto new_ioend;
>
> + if (folio_test_dropbehind(folio))
> + bio_set_flag(&ioend->io_bio, BIO_COMPLETE_IN_TASK);
And move setting the flag to iomap_ioend_writeback_submit, and only
for the case where no io_bio.bi_end_io is already assigned.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 1/5] block: introduce bio_in_atomic()
2026-07-30 6:57 ` [PATCH v7 1/5] block: introduce bio_in_atomic() Tal Zussman
2026-07-30 8:06 ` Christoph Hellwig
@ 2026-07-30 9:02 ` Jan Kara
1 sibling, 0 replies; 13+ messages in thread
From: Jan Kara @ 2026-07-30 9:02 UTC (permalink / raw)
To: Tal Zussman
Cc: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo, Dave Chinner,
Bart Van Assche, Hillf Danton, Sebastian Andrzej Siewior,
linux-block, linux-kernel, linux-xfs, linux-fsdevel, linux-mm,
linux-erofs
On Thu 30-07-26 02:57:51, Tal Zussman wrote:
> Move the atomic context detection logic from erofs's z_erofs_in_atomic()
> into the block layer as bio_in_atomic(). This helper returns true when
> the current context is unsafe for sleeping bio completion handlers (e.g.,
> hard/soft IRQ, preempt-disabled).
>
> The logic was originally added to erofs in commit c99fab6e80b7 ("erofs:
> fix atomic context detection when !CONFIG_DEBUG_LOCK_ALLOC"). A
> subsequent patch will use it in the block layer's bio completion
> infrastructure, so move it to include/linux/bio.h where both subsystems
> can share it.
>
> Convert erofs to call the new bio_in_atomic() directly.
>
> Suggested-by: Christoph Hellwig <hch@infradead.org>
> Signed-off-by: Tal Zussman <tz2294@columbia.edu>
Looks good. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> fs/erofs/zdata.c | 11 +----------
> include/linux/bio.h | 15 +++++++++++++++
> 2 files changed, 16 insertions(+), 10 deletions(-)
>
> diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c
> index 74520e910259..f796bb3ef53b 100644
> --- a/fs/erofs/zdata.c
> +++ b/fs/erofs/zdata.c
> @@ -1427,15 +1427,6 @@ static void z_erofs_decompressqueue_kthread_work(struct kthread_work *work)
> }
> #endif
>
> -/* Use (kthread_)work in atomic contexts to minimize scheduling overhead */
> -static inline bool z_erofs_in_atomic(void)
> -{
> - if (IS_ENABLED(CONFIG_PREEMPTION) && rcu_preempt_depth())
> - return true;
> - if (!IS_ENABLED(CONFIG_PREEMPT_COUNT))
> - return true;
> - return !preemptible();
> -}
>
> static void z_erofs_decompress_kickoff(struct z_erofs_decompressqueue *io,
> int bios)
> @@ -1452,7 +1443,7 @@ static void z_erofs_decompress_kickoff(struct z_erofs_decompressqueue *io,
>
> if (atomic_add_return(bios, &io->pending_bios))
> return;
> - if (z_erofs_in_atomic()) {
> + if (bio_in_atomic()) {
> /* See `sync_decompress` in sysfs-fs-erofs for more details */
> if (sbi->sync_decompress == EROFS_SYNC_DECOMPRESS_AUTO)
> sbi->sync_decompress = EROFS_SYNC_DECOMPRESS_FORCE_ON;
> diff --git a/include/linux/bio.h b/include/linux/bio.h
> index 8f33f717b14f..62a983758e09 100644
> --- a/include/linux/bio.h
> +++ b/include/linux/bio.h
> @@ -368,6 +368,21 @@ static inline struct bio *bio_alloc(struct block_device *bdev,
>
> void submit_bio(struct bio *bio);
>
> +/**
> + * bio_in_atomic - check if the current context is unsafe for bio completion
> + *
> + * Return: %true in atomic contexts (e.g. hard/soft IRQ, preempt-disabled);
> + * %false when a bio can be safely completed in the current context.
> + */
> +static inline bool bio_in_atomic(void)
> +{
> + if (IS_ENABLED(CONFIG_PREEMPTION) && rcu_preempt_depth())
> + return true;
> + if (!IS_ENABLED(CONFIG_PREEMPT_COUNT))
> + return true;
> + return !preemptible();
> +}
> +
> extern void bio_endio(struct bio *);
>
> /**
>
> --
> 2.39.5
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 2/5] block: add task-context bio completion infrastructure
2026-07-30 6:57 ` [PATCH v7 2/5] block: add task-context bio completion infrastructure Tal Zussman
2026-07-30 8:09 ` Christoph Hellwig
@ 2026-07-30 9:29 ` Jan Kara
1 sibling, 0 replies; 13+ messages in thread
From: Jan Kara @ 2026-07-30 9:29 UTC (permalink / raw)
To: Tal Zussman
Cc: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo, Dave Chinner,
Bart Van Assche, Hillf Danton, Sebastian Andrzej Siewior,
linux-block, linux-kernel, linux-xfs, linux-fsdevel, linux-mm,
linux-erofs
On Thu 30-07-26 02:57:52, Tal Zussman wrote:
> Some bio completion handlers need to run from preemptible task context,
> but bio_endio() may be called from IRQ context (e.g., buffer_head
> writeback). Callers need a way to ensure their callback eventually runs
> from a sleepable context. Add infrastructure for that, in two forms:
>
> 1. BIO_COMPLETE_IN_TASK, a bio flag the submitter sets when it knows
> in advance that its callback needs task context (e.g., dropbehind
> writeback). bio_endio() sees the flag and offloads completion to a
> worker automatically.
>
> 2. bio_complete_in_task(), a helper that completion callbacks can
> invoke from within bi_end_io() when the deferral decision is
> dynamic (e.g., fserror reporting).
>
> Both share a per-CPU list drained by a work item on a WQ_PERCPU
> workqueue. Producers push the bio onto the local CPU's list and schedule
> the work item, which then dispatches each bio's bi_end_io() from task
> context.
>
> Both methods are gated on bio_in_atomic(), which returns true in any
> context where a sleeping bi_end_io() is unsafe, including
> non-preemptible task context.
>
> Two CPU hotplug callbacks are used to drain remaining bios from the
> departing CPU's batch, while maintaining the per-CPU behavior. The
> CPUHP_AP_ONLINE_DYN callback disables the per-CPU work item while the
> CPU is still online, preventing it from running on an unbound worker
> later. CPUHP_BP_PREPARE_DYN then drains any bios added between disabling
> the work item and CPU offline.
>
> Link: https://lore.kernel.org/all/20260409160243.1008358-1-hch@lst.de/
> Suggested-by: Matthew Wilcox <willy@infradead.org>
> Suggested-by: Christoph Hellwig <hch@infradead.org>
> Signed-off-by: Tal Zussman <tz2294@columbia.edu>
Looks good! Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> block/bio.c | 132 +++++++++++++++++++++++++++++++++++++++++++++-
> include/linux/bio.h | 24 +++++++++
> include/linux/blk_types.h | 1 +
> 3 files changed, 156 insertions(+), 1 deletion(-)
>
> diff --git a/block/bio.c b/block/bio.c
> index 6a2f6fc3413e..a9610950325b 100644
> --- a/block/bio.c
> +++ b/block/bio.c
> @@ -1741,6 +1741,61 @@ void bio_check_pages_dirty(struct bio *bio)
> schedule_work(&bio_dirty_work);
> }
>
> +/*
> + * Infrastructure for deferring bio completions to task-context via a per-CPU
> + * workqueue. Triggered either by the BIO_COMPLETE_IN_TASK bio flag (static
> + * decision at submit time) or by calling bio_complete_in_task() from
> + * bi_end_io() (dynamic decision at completion time).
> + */
> +
> +struct bio_complete_batch {
> + struct bio_list list;
> + struct work_struct work;
> + int cpu;
> +};
> +
> +static DEFINE_PER_CPU(struct bio_complete_batch, bio_complete_batch);
> +static struct workqueue_struct *bio_complete_wq;
> +
> +static void bio_complete_work_fn(struct work_struct *w)
> +{
> + struct bio_complete_batch *batch =
> + container_of(w, struct bio_complete_batch, work);
> +
> + while (1) {
> + struct bio_list list;
> + struct bio *bio;
> +
> + local_irq_disable();
> + list = batch->list;
> + bio_list_init(&batch->list);
> + local_irq_enable();
> +
> + if (bio_list_empty(&list))
> + break;
> +
> + while ((bio = bio_list_pop(&list)))
> + bio->bi_end_io(bio);
> + }
> +}
> +
> +void __bio_complete_in_task(struct bio *bio)
> +{
> + struct bio_complete_batch *batch;
> + unsigned long flags;
> + bool was_empty;
> +
> + local_irq_save(flags);
> + batch = this_cpu_ptr(&bio_complete_batch);
> + was_empty = bio_list_empty(&batch->list);
> + bio_list_add(&batch->list, bio);
> + local_irq_restore(flags);
> +
> + if (was_empty)
> + queue_work_on(batch->cpu, bio_complete_wq, &batch->work);
> +}
> +EXPORT_SYMBOL_GPL(__bio_complete_in_task);
> +
> static inline bool bio_remaining_done(struct bio *bio)
> {
> /*
> @@ -1815,7 +1870,9 @@ void bio_endio(struct bio *bio)
> }
> #endif
>
> - if (bio->bi_end_io)
> + if (bio_flagged(bio, BIO_COMPLETE_IN_TASK) && bio_in_atomic())
> + __bio_complete_in_task(bio);
> + else if (bio->bi_end_io)
> bio->bi_end_io(bio);
> }
> EXPORT_SYMBOL(bio_endio);
> @@ -2001,6 +2058,55 @@ int bioset_init(struct bio_set *bs,
> }
> EXPORT_SYMBOL(bioset_init);
>
> +static int bio_complete_batch_cpu_online(unsigned int cpu)
> +{
> + struct bio_complete_batch *batch = &per_cpu(bio_complete_batch, cpu);
> +
> + enable_work(&batch->work);
> + if (!bio_list_empty(&batch->list))
> + queue_work_on(cpu, bio_complete_wq, &batch->work);
> + return 0;
> +}
> +
> +/*
> + * Disable this CPU's work item so that it cannot run on an unbound worker
> + * after the CPU is offlined.
> + */
> +static int bio_complete_batch_cpu_down_prep(unsigned int cpu)
> +{
> + disable_work_sync(&per_cpu(bio_complete_batch, cpu).work);
> + return 0;
> +}
> +
> +/*
> + * Drain a dead CPU's deferred bio completions. The CPU is dead and the worker
> + * is canceled so no locking is needed.
> + */
> +static int bio_complete_batch_cpu_dead(unsigned int cpu)
> +{
> + struct bio_complete_batch *batch =
> + per_cpu_ptr(&bio_complete_batch, cpu);
> + struct bio *bio;
> +
> + while ((bio = bio_list_pop(&batch->list)))
> + bio->bi_end_io(bio);
> +
> + return 0;
> +}
> +
> +static void __init bio_complete_batch_init(int cpu)
> +{
> + struct bio_complete_batch *batch =
> + per_cpu_ptr(&bio_complete_batch, cpu);
> +
> + bio_list_init(&batch->list);
> + INIT_WORK(&batch->work, bio_complete_work_fn);
> + batch->cpu = cpu;
> +
> + if (!cpu_online(cpu))
> + disable_work_sync(&batch->work);
> +}
> +
> static int __init init_bio(void)
> {
> int i;
> @@ -2015,6 +2121,30 @@ static int __init init_bio(void)
> SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
> }
>
> + for_each_possible_cpu(i)
> + bio_complete_batch_init(i);
> +
> + bio_complete_wq = alloc_workqueue("bio_complete",
> + WQ_MEM_RECLAIM | WQ_PERCPU, 0);
> + if (!bio_complete_wq)
> + panic("bio: can't allocate bio_complete workqueue\n");
> +
> + /*
> + * bio task-context completion draining on hot-unplugged CPUs:
> + *
> + * 1. Stop the per-CPU work item while the CPU is still online, so
> + * that it cannot run on an unbound worker later.
> + * 2. Drain leftover bios added between worker disabling and CPU
> + * offlining.
> + */
> + cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,
> + "block/bio:complete:online",
> + bio_complete_batch_cpu_online,
> + bio_complete_batch_cpu_down_prep);
> + cpuhp_setup_state_nocalls(CPUHP_BP_PREPARE_DYN,
> + "block/bio:complete:dead",
> + NULL, bio_complete_batch_cpu_dead);
> +
> cpuhp_setup_state_multi(CPUHP_BIO_DEAD, "block/bio:dead", NULL,
> bio_cpu_dead);
>
> diff --git a/include/linux/bio.h b/include/linux/bio.h
> index 62a983758e09..a8091a3e9d87 100644
> --- a/include/linux/bio.h
> +++ b/include/linux/bio.h
> @@ -383,6 +383,30 @@ static inline bool bio_in_atomic(void)
> return !preemptible();
> }
>
> +void __bio_complete_in_task(struct bio *bio);
> +
> +/**
> + * bio_complete_in_task - ensure a bio is completed in preemptible task context
> + * @bio: bio to complete
> + *
> + * If called from non-task context, offload the bio completion to a worker
> + * thread and return %true. Else return %false and do nothing.
> + *
> + * Uses BIO_COMPLETE_IN_TASK as a sentinel: if set, the bio was already
> + * deferred and we are running in the worker — return %false so the
> + * callback proceeds instead of re-deferring.
> + */
> +static inline bool bio_complete_in_task(struct bio *bio)
> +{
> + if (bio_flagged(bio, BIO_COMPLETE_IN_TASK))
> + return false;
> + if (!bio_in_atomic())
> + return false;
> + bio_set_flag(bio, BIO_COMPLETE_IN_TASK);
> + __bio_complete_in_task(bio);
> + return true;
> +}
> +
> extern void bio_endio(struct bio *);
>
> /**
> diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h
> index 8808ee76e73c..d49d97a050d0 100644
> --- a/include/linux/blk_types.h
> +++ b/include/linux/blk_types.h
> @@ -322,6 +322,7 @@ enum {
> BIO_REMAPPED,
> BIO_ZONE_WRITE_PLUGGING, /* bio handled through zone write plugging */
> BIO_EMULATES_ZONE_APPEND, /* bio emulates a zone append operation */
> + BIO_COMPLETE_IN_TASK, /* complete bi_end_io() in task context */
> BIO_FLAG_LAST
> };
>
>
> --
> 2.39.5
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 4/5] buffer: set BIO_COMPLETE_IN_TASK for dropbehind writeback
2026-07-30 6:57 ` [PATCH v7 4/5] buffer: set " Tal Zussman
@ 2026-07-30 9:31 ` Jan Kara
0 siblings, 0 replies; 13+ messages in thread
From: Jan Kara @ 2026-07-30 9:31 UTC (permalink / raw)
To: Tal Zussman
Cc: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo, Dave Chinner,
Bart Van Assche, Hillf Danton, Sebastian Andrzej Siewior,
linux-block, linux-kernel, linux-xfs, linux-fsdevel, linux-mm,
linux-erofs, Christoph Hellwig
On Thu 30-07-26 02:57:54, Tal Zussman wrote:
> Set BIO_COMPLETE_IN_TASK in __bh_submit() for write bios when the folio
> has dropbehind set, so that buffer_head writeback completions get
> deferred to task context where folio_end_dropbehind() can safely
> invalidate folios.
>
> Read completions are not deferred since dropbehind invalidation for
> reads is handled synchronously by the reader.
>
> Reviewed-by: Christoph Hellwig <hch@lst.de>
> Signed-off-by: Tal Zussman <tz2294@columbia.edu>
Looks good. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> fs/buffer.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/fs/buffer.c b/fs/buffer.c
> index 9af5f061a1f8..6f099847240e 100644
> --- a/fs/buffer.c
> +++ b/fs/buffer.c
> @@ -1203,6 +1203,9 @@ static void __bh_submit(struct buffer_head *bh, blk_opf_t opf,
>
> bio = bio_alloc(bh->b_bdev, 1, opf, GFP_NOIO);
>
> + if (folio_test_dropbehind(bh->b_folio) && op_is_write(opf))
> + bio_set_flag(bio, BIO_COMPLETE_IN_TASK);
> +
> if (IS_ENABLED(CONFIG_FS_ENCRYPTION))
> buffer_set_crypto_ctx(bio, bh, GFP_NOIO);
>
>
> --
> 2.39.5
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 5/5] block: enable RWF_DONTCACHE for block devices
2026-07-30 6:57 ` [PATCH v7 5/5] block: enable RWF_DONTCACHE for block devices Tal Zussman
@ 2026-07-30 9:33 ` Jan Kara
0 siblings, 0 replies; 13+ messages in thread
From: Jan Kara @ 2026-07-30 9:33 UTC (permalink / raw)
To: Tal Zussman
Cc: Jens Axboe, Matthew Wilcox (Oracle), Christian Brauner,
Darrick J. Wong, Carlos Maiolino, Alexander Viro, Jan Kara,
Christoph Hellwig, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo, Dave Chinner,
Bart Van Assche, Hillf Danton, Sebastian Andrzej Siewior,
linux-block, linux-kernel, linux-xfs, linux-fsdevel, linux-mm,
linux-erofs, Christoph Hellwig
On Thu 30-07-26 02:57:55, Tal Zussman wrote:
> Block device buffered reads and writes already pass through
> filemap_read() and iomap_file_buffered_write() respectively, both of
> which handle IOCB_DONTCACHE. Enable RWF_DONTCACHE for block device files
> by setting FOP_DONTCACHE in def_blk_fops.
>
> For CONFIG_BUFFER_HEAD=y, writeback goes through buffer_head's
> __bh_submit() which sets BIO_COMPLETE_IN_TASK on dropbehind folios. For
> CONFIG_BUFFER_HEAD=n, writeback goes through iomap which handles it via
> BIO_COMPLETE_IN_TASK on the ioend bio.
>
> This support is useful for databases that operate on raw block devices,
> among other userspace applications.
>
> Reviewed-by: Christoph Hellwig <hch@lst.de>
> Signed-off-by: Tal Zussman <tz2294@columbia.edu>
Looks good. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> block/fops.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/block/fops.c b/block/fops.c
> index 15783a6180de..8b39c53b6393 100644
> --- a/block/fops.c
> +++ b/block/fops.c
> @@ -941,7 +941,7 @@ const struct file_operations def_blk_fops = {
> .splice_write = iter_file_splice_write,
> .fallocate = blkdev_fallocate,
> .uring_cmd = blkdev_uring_cmd,
> - .fop_flags = FOP_BUFFER_RASYNC,
> + .fop_flags = FOP_BUFFER_RASYNC | FOP_DONTCACHE,
> };
>
> static __init int blkdev_init(void)
>
> --
> 2.39.5
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-07-30 9:34 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-30 6:57 [PATCH v7 0/5] block: enable RWF_DONTCACHE for block devices Tal Zussman
2026-07-30 6:57 ` [PATCH v7 1/5] block: introduce bio_in_atomic() Tal Zussman
2026-07-30 8:06 ` Christoph Hellwig
2026-07-30 9:02 ` Jan Kara
2026-07-30 6:57 ` [PATCH v7 2/5] block: add task-context bio completion infrastructure Tal Zussman
2026-07-30 8:09 ` Christoph Hellwig
2026-07-30 9:29 ` Jan Kara
2026-07-30 6:57 ` [PATCH v7 3/5] iomap: use BIO_COMPLETE_IN_TASK for dropbehind writeback Tal Zussman
2026-07-30 8:13 ` Christoph Hellwig
2026-07-30 6:57 ` [PATCH v7 4/5] buffer: set " Tal Zussman
2026-07-30 9:31 ` Jan Kara
2026-07-30 6:57 ` [PATCH v7 5/5] block: enable RWF_DONTCACHE for block devices Tal Zussman
2026-07-30 9:33 ` Jan Kara
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).