* [PATCH 1/7] filemap: Export filemap_invalidate_pages() to modules
2026-08-20 19:33 [PATCH 0/7] Remove aops->launder_folio Matthew Wilcox (Oracle)
@ 2026-08-20 19:33 ` Matthew Wilcox (Oracle)
2026-08-20 19:33 ` [PATCH 2/7] fuse: Use filemap_invalidate_pages() Matthew Wilcox (Oracle)
` (6 subsequent siblings)
7 siblings, 0 replies; 18+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-08-20 19:33 UTC (permalink / raw)
To: Christian Brauner
Cc: Matthew Wilcox (Oracle), Jan Kara, Chris Mason, David Sterba,
Miklos Szeredi, Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
This is a better API for filesystems to use than
invalidate_inode_pages2() / invalidate_inode_pages2_range().
However, the 'nowait' argument is unnecessary for them. It's also
wrongly implemented as it will call invalidate_inode_pages2_range()
even after filemap_range_has_page() returns false.
Move the filemap_range_has_page() call into the two existing callers
and add kernel-doc.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
block/ioctl.c | 14 ++++++++++----
include/linux/pagemap.h | 2 +-
mm/filemap.c | 42 +++++++++++++++++++++++++++--------------
3 files changed, 39 insertions(+), 19 deletions(-)
diff --git a/block/ioctl.c b/block/ioctl.c
index 3d4ea1537457..db5238b817b4 100644
--- a/block/ioctl.c
+++ b/block/ioctl.c
@@ -908,10 +908,16 @@ static int blkdev_cmd_discard(struct io_uring_cmd *cmd,
if (err)
return err;
- err = filemap_invalidate_pages(bdev->bd_mapping, start,
- start + len - 1, nowait);
- if (err)
- return err;
+ if (nowait) {
+ if (filemap_range_has_page(bdev->bd_mapping, start,
+ start + len - 1))
+ return -EAGAIN;
+ } else {
+ err = filemap_invalidate_pages(bdev->bd_mapping, start,
+ start + len - 1);
+ if (err)
+ return err;
+ }
while (true) {
bio = blk_alloc_discard_bio(bdev, §or, &nr_sects, gfp);
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 2c3718d592d6..ed99c8ab196a 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -33,7 +33,7 @@ int invalidate_inode_pages2_range(struct address_space *mapping,
int kiocb_invalidate_pages(struct kiocb *iocb, size_t count);
void kiocb_invalidate_post_direct_write(struct kiocb *iocb, size_t count);
int filemap_invalidate_pages(struct address_space *mapping,
- loff_t pos, loff_t end, bool nowait);
+ loff_t pos, loff_t end);
int write_inode_now(struct inode *, int sync);
int filemap_fdatawrite(struct address_space *);
diff --git a/mm/filemap.c b/mm/filemap.c
index d721986d5f46..fedb521d773f 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -2911,20 +2911,27 @@ int kiocb_write_and_wait(struct kiocb *iocb, size_t count)
}
EXPORT_SYMBOL_GPL(kiocb_write_and_wait);
+/**
+ * filemap_invalidate_pages - Invalidate pages from the page cache
+ * @mapping: Address space to invalidate
+ * @pos: First byte to invalidate
+ * @end: Last byte (inclusive) to invalidate
+ *
+ * Invalidates the folios containing @pos and @end from the page cache
+ * (as well as all folios between them), so may remove more pages from
+ * the page cache than you ask for.
+ *
+ * Context: May sleep. Caller may wish to hold mapping_invalidate_lock to
+ * prevent new pages being instantiated in this range.
+ * Return: 0 on success or negative errno.
+ */
int filemap_invalidate_pages(struct address_space *mapping,
- loff_t pos, loff_t end, bool nowait)
+ loff_t pos, loff_t end)
{
- int ret;
+ int ret = filemap_write_and_wait_range(mapping, pos, end);
- if (nowait) {
- /* we could block if there are any pages in the range */
- if (filemap_range_has_page(mapping, pos, end))
- return -EAGAIN;
- } else {
- ret = filemap_write_and_wait_range(mapping, pos, end);
- if (ret)
- return ret;
- }
+ if (ret)
+ return ret;
/*
* After a write we want buffered reads to be sure to go to disk to get
@@ -2935,14 +2942,21 @@ int filemap_invalidate_pages(struct address_space *mapping,
return invalidate_inode_pages2_range(mapping, pos >> PAGE_SHIFT,
end >> PAGE_SHIFT);
}
+EXPORT_SYMBOL_GPL(filemap_invalidate_pages);
int kiocb_invalidate_pages(struct kiocb *iocb, size_t count)
{
struct address_space *mapping = iocb->ki_filp->f_mapping;
+ loff_t end = iocb->ki_pos + count - 1;
+
+ if (iocb->ki_flags & IOCB_NOWAIT) {
+ /* we could block if there are any pages in the range */
+ if (filemap_range_has_page(mapping, iocb->ki_pos, end))
+ return -EAGAIN;
+ return 0;
+ }
- return filemap_invalidate_pages(mapping, iocb->ki_pos,
- iocb->ki_pos + count - 1,
- iocb->ki_flags & IOCB_NOWAIT);
+ return filemap_invalidate_pages(mapping, iocb->ki_pos, end);
}
EXPORT_SYMBOL_GPL(kiocb_invalidate_pages);
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 2/7] fuse: Use filemap_invalidate_pages()
2026-08-20 19:33 [PATCH 0/7] Remove aops->launder_folio Matthew Wilcox (Oracle)
2026-08-20 19:33 ` [PATCH 1/7] filemap: Export filemap_invalidate_pages() to modules Matthew Wilcox (Oracle)
@ 2026-08-20 19:33 ` Matthew Wilcox (Oracle)
2026-08-20 20:37 ` Bernd Schubert
2026-08-24 9:05 ` Miklos Szeredi
2026-08-20 19:33 ` [PATCH 3/7] btrfs: " Matthew Wilcox (Oracle)
` (5 subsequent siblings)
7 siblings, 2 replies; 18+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-08-20 19:33 UTC (permalink / raw)
To: Christian Brauner
Cc: Matthew Wilcox (Oracle), Jan Kara, Chris Mason, David Sterba,
Miklos Szeredi, Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
FUSE relies on invalidate_inode_pages2() / invalidate_inode_pages2_range()
doing writeback by calling fuse_launder_folio(). While this works, it
is inefficient as each page is written back and waited for individually.
Far better to call filemap_invalidate_pages() which will do a bulk write
first, then remove the page cache.
With this done, fuse_launder_folio() no longer needs to exist so
delete it.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
fs/fuse/dax.c | 15 +++------------
fs/fuse/dir.c | 12 +++++++-----
fs/fuse/file.c | 46 ++++++----------------------------------------
fs/fuse/inode.c | 17 +++++------------
4 files changed, 21 insertions(+), 69 deletions(-)
diff --git a/fs/fuse/dax.c b/fs/fuse/dax.c
index 8b53625ac7ab..45843975e28b 100644
--- a/fs/fuse/dax.c
+++ b/fs/fuse/dax.c
@@ -835,19 +835,10 @@ static int dmap_writeback_invalidate(struct inode *inode,
loff_t start_pos = dmap->itn.start << FUSE_DAX_SHIFT;
loff_t end_pos = (start_pos + FUSE_DAX_SZ - 1);
- ret = filemap_fdatawrite_range(inode->i_mapping, start_pos, end_pos);
- if (ret) {
- pr_debug("fuse: filemap_fdatawrite_range() failed. err=%d start_pos=0x%llx, end_pos=0x%llx\n",
- ret, start_pos, end_pos);
- return ret;
- }
-
- ret = invalidate_inode_pages2_range(inode->i_mapping,
- start_pos >> PAGE_SHIFT,
- end_pos >> PAGE_SHIFT);
+ ret = filemap_invalidate_pages(inode->i_mapping, start_pos, end_pos);
if (ret)
- pr_debug("fuse: invalidate_inode_pages2_range() failed err=%d\n",
- ret);
+ pr_debug("fuse: filemap_invalidate_pages() failed. err=%d start_pos=0x%llx, end_pos=0x%llx\n",
+ ret, start_pos, end_pos);
return ret;
}
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 0e2a1039fa43..c6c72c0ee669 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -913,7 +913,8 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
if (fm->fc->atomic_o_trunc && trunc)
truncate_pagecache(inode, 0);
else if (!(ff->open_flags & FOPEN_KEEP_CACHE))
- invalidate_inode_pages2(inode->i_mapping);
+ filemap_invalidate_pages(inode->i_mapping, 0,
+ OFFSET_MAX);
}
return err;
@@ -1904,7 +1905,8 @@ static int fuse_dir_open(struct inode *inode, struct file *file)
if (ff->open_flags & (FOPEN_STREAM | FOPEN_NONSEEKABLE))
nonseekable_open(inode, file);
if (!(ff->open_flags & FOPEN_KEEP_CACHE))
- invalidate_inode_pages2(inode->i_mapping);
+ filemap_invalidate_pages(inode->i_mapping, 0,
+ OFFSET_MAX);
}
return err;
@@ -2277,13 +2279,13 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
spin_unlock(&fi->lock);
/*
- * Only call invalidate_inode_pages2() after removing
- * FUSE_NOWRITE, otherwise fuse_launder_folio() would deadlock.
+ * Only call filemap_invalidate_pages() after removing
+ * FUSE_NOWRITE, otherwise it would deadlock.
*/
if ((is_truncate || !is_wb) &&
S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) {
truncate_pagecache(inode, outarg.attr.size);
- invalidate_inode_pages2(mapping);
+ filemap_invalidate_pages(mapping, 0, OFFSET_MAX);
}
clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state);
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index ceada75310b8..2496161f9298 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -294,7 +294,8 @@ static int fuse_open(struct inode *inode, struct file *file)
if (is_truncate)
truncate_pagecache(inode, 0);
else if (!(ff->open_flags & FOPEN_KEEP_CACHE))
- invalidate_inode_pages2(inode->i_mapping);
+ filemap_invalidate_pages(inode->i_mapping, 0,
+ OFFSET_MAX);
}
if (dax_truncate)
filemap_invalidate_unlock(inode->i_mapping);
@@ -644,10 +645,8 @@ static void fuse_aio_invalidate_worker(struct work_struct *work)
struct fuse_io_priv *io = container_of(work, struct fuse_io_priv, work);
struct address_space *mapping = io->iocb->ki_filp->f_mapping;
ssize_t res = fuse_get_res_by_io(io);
- pgoff_t start = io->offset >> PAGE_SHIFT;
- pgoff_t end = (io->offset + res - 1) >> PAGE_SHIFT;
- invalidate_inode_pages2_range(mapping, start, end);
+ filemap_invalidate_pages(mapping, io->offset, io->offset + res - 1);
io->iocb->ki_complete(io->iocb, res);
kref_put(&io->refcnt, fuse_io_release);
}
@@ -1675,8 +1674,6 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,
size_t nmax = write ? fc->max_write : fc->max_read;
loff_t pos = *ppos;
size_t count = iov_iter_count(iter);
- pgoff_t idx_from = pos >> PAGE_SHIFT;
- pgoff_t idx_to = (pos + count - 1) >> PAGE_SHIFT;
ssize_t res = 0;
int err = 0;
struct fuse_io_args *ia;
@@ -1689,7 +1686,7 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,
return -ENOMEM;
if (fopen_direct_io) {
- res = filemap_write_and_wait_range(mapping, pos, pos + count - 1);
+ res = filemap_invalidate_pages(mapping, pos, pos + count - 1);
if (res) {
fuse_io_free(ia);
return res;
@@ -1703,14 +1700,6 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,
inode_unlock(inode);
}
- if (fopen_direct_io && write) {
- res = invalidate_inode_pages2_range(mapping, idx_from, idx_to);
- if (res) {
- fuse_io_free(ia);
- return res;
- }
- }
-
io->should_dirty = !write && user_backed_iter(iter);
while (count) {
ssize_t nres;
@@ -1824,9 +1813,7 @@ static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from)
* write, to invalidate read-ahead cache that may have
* with the write.
*/
- invalidate_inode_pages2_range(mapping,
- pos >> PAGE_SHIFT,
- (pos + res - 1) >> PAGE_SHIFT);
+ filemap_invalidate_pages(mapping, pos, pos + res - 1);
}
}
fuse_dio_unlock(iocb, exclusive);
@@ -2324,26 +2311,6 @@ static int fuse_writepages(struct address_space *mapping,
return iomap_writepages(&wpc);
}
-static int fuse_launder_folio(struct folio *folio)
-{
- int err = 0;
- struct fuse_fill_wb_data data = {};
- struct iomap_writepage_ctx wpc = {
- .inode = folio->mapping->host,
- .iomap.type = IOMAP_MAPPED,
- .ops = &fuse_writeback_ops,
- .wb_ctx = &data,
- };
-
- if (folio_clear_dirty_for_io(folio)) {
- err = iomap_writeback_folio(&wpc, folio);
- err = fuse_iomap_writeback_submit(&wpc, err);
- if (!err)
- folio_wait_writeback(folio);
- }
- return err;
-}
-
/*
* Write back dirty data/metadata now (there may not be any suitable
* open files later for data)
@@ -2427,7 +2394,7 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
if ((vma->vm_flags & VM_MAYSHARE) && !fc->direct_io_allow_mmap)
return -ENODEV;
- invalidate_inode_pages2(file->f_mapping);
+ filemap_invalidate_pages(file->f_mapping, 0, OFFSET_MAX);
if (!(vma->vm_flags & VM_MAYSHARE)) {
/* MAP_PRIVATE */
@@ -3102,7 +3069,6 @@ static const struct address_space_operations fuse_file_aops = {
.read_folio = fuse_read_folio,
.readahead = fuse_readahead,
.writepages = fuse_writepages,
- .launder_folio = fuse_launder_folio,
.dirty_folio = iomap_dirty_folio,
.release_folio = iomap_release_folio,
.invalidate_folio = iomap_invalidate_folio,
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index d975073c6029..2c9a93a2173a 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -382,7 +382,8 @@ static void fuse_change_attributes_i(struct inode *inode, struct fuse_attr *attr
}
if (inval)
- invalidate_inode_pages2(inode->i_mapping);
+ filemap_invalidate_pages(inode->i_mapping, 0,
+ OFFSET_MAX);
}
if (IS_ENABLED(CONFIG_FUSE_DAX))
@@ -547,8 +548,6 @@ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid,
{
struct fuse_inode *fi;
struct inode *inode;
- pgoff_t pg_start;
- pgoff_t pg_end;
inode = fuse_ilookup(fc, nodeid, NULL);
if (!inode)
@@ -561,15 +560,9 @@ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid,
fuse_invalidate_attr(inode);
forget_all_cached_acls(inode);
- if (offset >= 0) {
- pg_start = offset >> PAGE_SHIFT;
- if (len <= 0)
- pg_end = -1;
- else
- pg_end = (offset + len - 1) >> PAGE_SHIFT;
- invalidate_inode_pages2_range(inode->i_mapping,
- pg_start, pg_end);
- }
+ if (offset >= 0)
+ filemap_invalidate_pages(inode->i_mapping, offset,
+ offset + len - 1);
iput(inode);
return 0;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 2/7] fuse: Use filemap_invalidate_pages()
2026-08-20 19:33 ` [PATCH 2/7] fuse: Use filemap_invalidate_pages() Matthew Wilcox (Oracle)
@ 2026-08-20 20:37 ` Bernd Schubert
2026-08-24 9:05 ` Miklos Szeredi
1 sibling, 0 replies; 18+ messages in thread
From: Bernd Schubert @ 2026-08-20 20:37 UTC (permalink / raw)
To: Matthew Wilcox (Oracle), Christian Brauner
Cc: Jan Kara, Chris Mason, David Sterba, Miklos Szeredi,
Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
On 8/20/26 21:33, Matthew Wilcox (Oracle) wrote:
> FUSE relies on invalidate_inode_pages2() / invalidate_inode_pages2_range()
> doing writeback by calling fuse_launder_folio(). While this works, it
> is inefficient as each page is written back and waited for individually.
> Far better to call filemap_invalidate_pages() which will do a bulk write
> first, then remove the page cache.
>
> With this done, fuse_launder_folio() no longer needs to exist so
> delete it.
>
> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
> ---
> fs/fuse/dax.c | 15 +++------------
> fs/fuse/dir.c | 12 +++++++-----
> fs/fuse/file.c | 46 ++++++----------------------------------------
> fs/fuse/inode.c | 17 +++++------------
> 4 files changed, 21 insertions(+), 69 deletions(-)
>
> diff --git a/fs/fuse/dax.c b/fs/fuse/dax.c
> index 8b53625ac7ab..45843975e28b 100644
> --- a/fs/fuse/dax.c
> +++ b/fs/fuse/dax.c
> @@ -835,19 +835,10 @@ static int dmap_writeback_invalidate(struct inode *inode,
> loff_t start_pos = dmap->itn.start << FUSE_DAX_SHIFT;
> loff_t end_pos = (start_pos + FUSE_DAX_SZ - 1);
>
> - ret = filemap_fdatawrite_range(inode->i_mapping, start_pos, end_pos);
> - if (ret) {
> - pr_debug("fuse: filemap_fdatawrite_range() failed. err=%d start_pos=0x%llx, end_pos=0x%llx\n",
> - ret, start_pos, end_pos);
> - return ret;
> - }
> -
> - ret = invalidate_inode_pages2_range(inode->i_mapping,
> - start_pos >> PAGE_SHIFT,
> - end_pos >> PAGE_SHIFT);
> + ret = filemap_invalidate_pages(inode->i_mapping, start_pos, end_pos);
> if (ret)
> - pr_debug("fuse: invalidate_inode_pages2_range() failed err=%d\n",
> - ret);
> + pr_debug("fuse: filemap_invalidate_pages() failed. err=%d start_pos=0x%llx, end_pos=0x%llx\n",
> + ret, start_pos, end_pos);
>
> return ret;
> }
> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> index 0e2a1039fa43..c6c72c0ee669 100644
> --- a/fs/fuse/dir.c
> +++ b/fs/fuse/dir.c
> @@ -913,7 +913,8 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
> if (fm->fc->atomic_o_trunc && trunc)
> truncate_pagecache(inode, 0);
> else if (!(ff->open_flags & FOPEN_KEEP_CACHE))
> - invalidate_inode_pages2(inode->i_mapping);
> + filemap_invalidate_pages(inode->i_mapping, 0,
> + OFFSET_MAX);
> }
> return err;
>
> @@ -1904,7 +1905,8 @@ static int fuse_dir_open(struct inode *inode, struct file *file)
> if (ff->open_flags & (FOPEN_STREAM | FOPEN_NONSEEKABLE))
> nonseekable_open(inode, file);
> if (!(ff->open_flags & FOPEN_KEEP_CACHE))
> - invalidate_inode_pages2(inode->i_mapping);
> + filemap_invalidate_pages(inode->i_mapping, 0,
> + OFFSET_MAX);
> }
>
> return err;
> @@ -2277,13 +2279,13 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
> spin_unlock(&fi->lock);
>
> /*
> - * Only call invalidate_inode_pages2() after removing
> - * FUSE_NOWRITE, otherwise fuse_launder_folio() would deadlock.
> + * Only call filemap_invalidate_pages() after removing
> + * FUSE_NOWRITE, otherwise it would deadlock.
> */
> if ((is_truncate || !is_wb) &&
> S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) {
> truncate_pagecache(inode, outarg.attr.size);
> - invalidate_inode_pages2(mapping);
> + filemap_invalidate_pages(mapping, 0, OFFSET_MAX);
> }
>
> clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state);
> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> index ceada75310b8..2496161f9298 100644
> --- a/fs/fuse/file.c
> +++ b/fs/fuse/file.c
> @@ -294,7 +294,8 @@ static int fuse_open(struct inode *inode, struct file *file)
> if (is_truncate)
> truncate_pagecache(inode, 0);
> else if (!(ff->open_flags & FOPEN_KEEP_CACHE))
> - invalidate_inode_pages2(inode->i_mapping);
> + filemap_invalidate_pages(inode->i_mapping, 0,
> + OFFSET_MAX);
> }
> if (dax_truncate)
> filemap_invalidate_unlock(inode->i_mapping);
> @@ -644,10 +645,8 @@ static void fuse_aio_invalidate_worker(struct work_struct *work)
> struct fuse_io_priv *io = container_of(work, struct fuse_io_priv, work);
> struct address_space *mapping = io->iocb->ki_filp->f_mapping;
> ssize_t res = fuse_get_res_by_io(io);
> - pgoff_t start = io->offset >> PAGE_SHIFT;
> - pgoff_t end = (io->offset + res - 1) >> PAGE_SHIFT;
>
> - invalidate_inode_pages2_range(mapping, start, end);
> + filemap_invalidate_pages(mapping, io->offset, io->offset + res - 1);
> io->iocb->ki_complete(io->iocb, res);
> kref_put(&io->refcnt, fuse_io_release);
> }
> @@ -1675,8 +1674,6 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,
> size_t nmax = write ? fc->max_write : fc->max_read;
> loff_t pos = *ppos;
> size_t count = iov_iter_count(iter);
> - pgoff_t idx_from = pos >> PAGE_SHIFT;
> - pgoff_t idx_to = (pos + count - 1) >> PAGE_SHIFT;
> ssize_t res = 0;
> int err = 0;
> struct fuse_io_args *ia;
> @@ -1689,7 +1686,7 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,
> return -ENOMEM;
>
> if (fopen_direct_io) {
> - res = filemap_write_and_wait_range(mapping, pos, pos + count - 1);
> + res = filemap_invalidate_pages(mapping, pos, pos + count - 1);
> if (res) {
> fuse_io_free(ia);
> return res;
> @@ -1703,14 +1700,6 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,
> inode_unlock(inode);
> }
>
> - if (fopen_direct_io && write) {
> - res = invalidate_inode_pages2_range(mapping, idx_from, idx_to);
> - if (res) {
> - fuse_io_free(ia);
> - return res;
> - }
> - }
> -
> io->should_dirty = !write && user_backed_iter(iter);
> while (count) {
> ssize_t nres;
> @@ -1824,9 +1813,7 @@ static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from)
> * write, to invalidate read-ahead cache that may have
> * with the write.
> */
> - invalidate_inode_pages2_range(mapping,
> - pos >> PAGE_SHIFT,
> - (pos + res - 1) >> PAGE_SHIFT);
> + filemap_invalidate_pages(mapping, pos, pos + res - 1);
> }
> }
> fuse_dio_unlock(iocb, exclusive);
> @@ -2324,26 +2311,6 @@ static int fuse_writepages(struct address_space *mapping,
> return iomap_writepages(&wpc);
> }
>
> -static int fuse_launder_folio(struct folio *folio)
> -{
> - int err = 0;
> - struct fuse_fill_wb_data data = {};
> - struct iomap_writepage_ctx wpc = {
> - .inode = folio->mapping->host,
> - .iomap.type = IOMAP_MAPPED,
> - .ops = &fuse_writeback_ops,
> - .wb_ctx = &data,
> - };
> -
> - if (folio_clear_dirty_for_io(folio)) {
> - err = iomap_writeback_folio(&wpc, folio);
> - err = fuse_iomap_writeback_submit(&wpc, err);
> - if (!err)
> - folio_wait_writeback(folio);
> - }
> - return err;
> -}
> -
> /*
> * Write back dirty data/metadata now (there may not be any suitable
> * open files later for data)
> @@ -2427,7 +2394,7 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
> if ((vma->vm_flags & VM_MAYSHARE) && !fc->direct_io_allow_mmap)
> return -ENODEV;
>
> - invalidate_inode_pages2(file->f_mapping);
> + filemap_invalidate_pages(file->f_mapping, 0, OFFSET_MAX);
>
> if (!(vma->vm_flags & VM_MAYSHARE)) {
> /* MAP_PRIVATE */
> @@ -3102,7 +3069,6 @@ static const struct address_space_operations fuse_file_aops = {
> .read_folio = fuse_read_folio,
> .readahead = fuse_readahead,
> .writepages = fuse_writepages,
> - .launder_folio = fuse_launder_folio,
> .dirty_folio = iomap_dirty_folio,
> .release_folio = iomap_release_folio,
> .invalidate_folio = iomap_invalidate_folio,
> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> index d975073c6029..2c9a93a2173a 100644
> --- a/fs/fuse/inode.c
> +++ b/fs/fuse/inode.c
> @@ -382,7 +382,8 @@ static void fuse_change_attributes_i(struct inode *inode, struct fuse_attr *attr
> }
>
> if (inval)
> - invalidate_inode_pages2(inode->i_mapping);
> + filemap_invalidate_pages(inode->i_mapping, 0,
> + OFFSET_MAX);
> }
>
> if (IS_ENABLED(CONFIG_FUSE_DAX))
> @@ -547,8 +548,6 @@ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid,
> {
> struct fuse_inode *fi;
> struct inode *inode;
> - pgoff_t pg_start;
> - pgoff_t pg_end;
>
> inode = fuse_ilookup(fc, nodeid, NULL);
> if (!inode)
> @@ -561,15 +560,9 @@ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid,
>
> fuse_invalidate_attr(inode);
> forget_all_cached_acls(inode);
> - if (offset >= 0) {
> - pg_start = offset >> PAGE_SHIFT;
> - if (len <= 0)
> - pg_end = -1;
> - else
> - pg_end = (offset + len - 1) >> PAGE_SHIFT;
> - invalidate_inode_pages2_range(inode->i_mapping,
> - pg_start, pg_end);
> - }
> + if (offset >= 0)
> + filemap_invalidate_pages(inode->i_mapping, offset,
> + offset + len - 1);
> iput(inode);
> return 0;
> }
Please correct me if I'm wrong, but doesn't that changes behavior for
len <= 0? The logic to invalidate till the file end is lost, at least if
offset is != -1? And incostent behavior for len < 1?
Note that the existing behavior is used by libfuse and heavily by some
fuse daemons.
Thanks,
Bernd
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH 2/7] fuse: Use filemap_invalidate_pages()
2026-08-20 19:33 ` [PATCH 2/7] fuse: Use filemap_invalidate_pages() Matthew Wilcox (Oracle)
2026-08-20 20:37 ` Bernd Schubert
@ 2026-08-24 9:05 ` Miklos Szeredi
2026-08-24 13:29 ` Matthew Wilcox
1 sibling, 1 reply; 18+ messages in thread
From: Miklos Szeredi @ 2026-08-24 9:05 UTC (permalink / raw)
To: Matthew Wilcox (Oracle)
Cc: Christian Brauner, Jan Kara, Chris Mason, David Sterba,
Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
On Thu, 20 Aug 2026 at 21:34, Matthew Wilcox (Oracle)
<willy@infradead.org> wrote:
>
> FUSE relies on invalidate_inode_pages2() / invalidate_inode_pages2_range()
> doing writeback by calling fuse_launder_folio(). While this works, it
> is inefficient as each page is written back and waited for individually.
> Far better to call filemap_invalidate_pages() which will do a bulk write
> first, then remove the page cache.
filemap_invalidate_page() suggests to use invalidate_lock to prevent
races with pages coming back. I'm not sure that without it the
behavior is identical.
Maybe add a variant that takes that lock?
Thanks,
Miklos
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/7] fuse: Use filemap_invalidate_pages()
2026-08-24 9:05 ` Miklos Szeredi
@ 2026-08-24 13:29 ` Matthew Wilcox
2026-08-24 13:49 ` Miklos Szeredi
0 siblings, 1 reply; 18+ messages in thread
From: Matthew Wilcox @ 2026-08-24 13:29 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Christian Brauner, Jan Kara, Chris Mason, David Sterba,
Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
On Mon, Aug 24, 2026 at 11:05:46AM +0200, Miklos Szeredi wrote:
> On Thu, 20 Aug 2026 at 21:34, Matthew Wilcox (Oracle)
> <willy@infradead.org> wrote:
> >
> > FUSE relies on invalidate_inode_pages2() / invalidate_inode_pages2_range()
> > doing writeback by calling fuse_launder_folio(). While this works, it
> > is inefficient as each page is written back and waited for individually.
> > Far better to call filemap_invalidate_pages() which will do a bulk write
> > first, then remove the page cache.
>
> filemap_invalidate_page() suggests to use invalidate_lock to prevent
> races with pages coming back. I'm not sure that without it the
> behavior is identical.
>
> Maybe add a variant that takes that lock?
I don't understand what use that would be. As soon as that function
drops the lock, the pages could be reinstated. If the caller needs the
pages to not come back, it must need to hold the invalidate_lock across
the whole operation.
Before any filesystem people get funny ideas about closing the O_DIRECT
race, you can't do that because you'll deadlock on doing I/O to the same
file that you've mmaped.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/7] fuse: Use filemap_invalidate_pages()
2026-08-24 13:29 ` Matthew Wilcox
@ 2026-08-24 13:49 ` Miklos Szeredi
2026-08-24 18:17 ` Matthew Wilcox
0 siblings, 1 reply; 18+ messages in thread
From: Miklos Szeredi @ 2026-08-24 13:49 UTC (permalink / raw)
To: Matthew Wilcox
Cc: Christian Brauner, Jan Kara, Chris Mason, David Sterba,
Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
On Mon, 24 Aug 2026 at 15:29, Matthew Wilcox <willy@infradead.org> wrote:
>
> On Mon, Aug 24, 2026 at 11:05:46AM +0200, Miklos Szeredi wrote:
> > Maybe add a variant that takes that lock?
>
> I don't understand what use that would be. As soon as that function
> drops the lock, the pages could be reinstated. If the caller needs the
> pages to not come back, it must need to hold the invalidate_lock across
> the whole operation.
invalidate_inode_pages2_range() together with launder_page guaranteed
that no dirty data remained in the cache after that call. Yes, the
pages can be reinstated after that but those need faults and the
server can then serialize those against the invalidation.
I don't see that guarantee with the filemap_write_and_wait_range()
(with or without invalidate_lock actually) since the mapping can be
dirtied again without the filesystem's knowledge.
Am I missing something?
Thanks,
Miklos
>
> Before any filesystem people get funny ideas about closing the O_DIRECT
> race, you can't do that because you'll deadlock on doing I/O to the same
> file that you've mmaped.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/7] fuse: Use filemap_invalidate_pages()
2026-08-24 13:49 ` Miklos Szeredi
@ 2026-08-24 18:17 ` Matthew Wilcox
2026-08-24 19:33 ` Miklos Szeredi
0 siblings, 1 reply; 18+ messages in thread
From: Matthew Wilcox @ 2026-08-24 18:17 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Christian Brauner, Jan Kara, Chris Mason, David Sterba,
Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
On Mon, Aug 24, 2026 at 03:49:37PM +0200, Miklos Szeredi wrote:
> On Mon, 24 Aug 2026 at 15:29, Matthew Wilcox <willy@infradead.org> wrote:
> >
> > On Mon, Aug 24, 2026 at 11:05:46AM +0200, Miklos Szeredi wrote:
>
> > > Maybe add a variant that takes that lock?
> >
> > I don't understand what use that would be. As soon as that function
> > drops the lock, the pages could be reinstated. If the caller needs the
> > pages to not come back, it must need to hold the invalidate_lock across
> > the whole operation.
>
> invalidate_inode_pages2_range() together with launder_page guaranteed
> that no dirty data remained in the cache after that call. Yes, the
> pages can be reinstated after that but those need faults and the
> server can then serialize those against the invalidation.
>
> I don't see that guarantee with the filemap_write_and_wait_range()
> (with or without invalidate_lock actually) since the mapping can be
> dirtied again without the filesystem's knowledge.
>
> Am I missing something?
Well, one of us is!
Before:
fuse_open()
invalidate_inode_pages2()
folio_lock()
folio_unmap_invalidate()
folio_launder()
folio_unlock()
After:
fuse_open()
filemap_invalidate_pages()
filemap_write_and_wait_range()
invalidate_inode_pages2_range()
folio_lock()
folio_unmap_invalidate()
folio_test_dirty()
folio_unlock()
so what's the serialisation that the filesystem can perform in the first
case that it can't perform in the second case?
or alternatively, what's the serialisation that would be useful by
adding a lock/unlock of the invalidate_lock inside
filemap_invalidate_pages()?
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/7] fuse: Use filemap_invalidate_pages()
2026-08-24 18:17 ` Matthew Wilcox
@ 2026-08-24 19:33 ` Miklos Szeredi
2026-08-24 20:47 ` Matthew Wilcox
0 siblings, 1 reply; 18+ messages in thread
From: Miklos Szeredi @ 2026-08-24 19:33 UTC (permalink / raw)
To: Matthew Wilcox
Cc: Christian Brauner, Jan Kara, Chris Mason, David Sterba,
Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
On Mon, 24 Aug 2026 at 20:17, Matthew Wilcox <willy@infradead.org> wrote:
> Before:
>
> fuse_open()
> invalidate_inode_pages2()
> folio_lock()
> folio_unmap_invalidate()
> folio_launder()
> folio_unlock()
>
> After:
>
> fuse_open()
> filemap_invalidate_pages()
> filemap_write_and_wait_range()
> invalidate_inode_pages2_range()
> folio_lock()
> folio_unmap_invalidate()
> folio_test_dirty()
> folio_unlock()
>
> so what's the serialisation that the filesystem can perform in the first
> case that it can't perform in the second case?
In the second case filemap_write_and_wait_range() won't write protect
or unmap the page, so it may become dirty after the writeback.
That can't happen in the first case, since the page is written and
unmapped while under page lock.
> or alternatively, what's the serialisation that would be useful by
> adding a lock/unlock of the invalidate_lock inside
> filemap_invalidate_pages()?
Nothing.
What would prevent this if we'd have writeback + unmap + writeback.
Thanks,
Miklos
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/7] fuse: Use filemap_invalidate_pages()
2026-08-24 19:33 ` Miklos Szeredi
@ 2026-08-24 20:47 ` Matthew Wilcox
2026-08-25 7:08 ` Miklos Szeredi
0 siblings, 1 reply; 18+ messages in thread
From: Matthew Wilcox @ 2026-08-24 20:47 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Christian Brauner, Jan Kara, Chris Mason, David Sterba,
Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
On Mon, Aug 24, 2026 at 09:33:03PM +0200, Miklos Szeredi wrote:
> On Mon, 24 Aug 2026 at 20:17, Matthew Wilcox <willy@infradead.org> wrote:
>
> > Before:
> >
> > fuse_open()
> > invalidate_inode_pages2()
> > folio_lock()
> > folio_unmap_invalidate()
> > folio_launder()
> > folio_unlock()
> >
> > After:
> >
> > fuse_open()
> > filemap_invalidate_pages()
> > filemap_write_and_wait_range()
> > invalidate_inode_pages2_range()
> > folio_lock()
> > folio_unmap_invalidate()
> > folio_test_dirty()
> > folio_unlock()
> >
> > so what's the serialisation that the filesystem can perform in the first
> > case that it can't perform in the second case?
>
> In the second case filemap_write_and_wait_range() won't write protect
> or unmap the page, so it may become dirty after the writeback.
But that can also happen in the first case. page_mkwrite() can be called
immediately after the folio is unlocked, for example. Or the folio can
be evicted and replaced with a different folio which is then dirtied.
I've widened the race window, no doubt. But it was always there.
If you want to prevent something like that from happening, you need
to be holding the invalidate_lock across the call to
filemap_invalidate_folio() and whatever other thing you're doing that
needs those pages clean.
> That can't happen in the first case, since the page is written and
> unmapped while under page lock.
>
> > or alternatively, what's the serialisation that would be useful by
> > adding a lock/unlock of the invalidate_lock inside
> > filemap_invalidate_pages()?
>
> Nothing.
>
> What would prevent this if we'd have writeback + unmap + writeback.
We could do that -- but it won't solve the problem because the pages
could still be redirtied after the second writeback.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 2/7] fuse: Use filemap_invalidate_pages()
2026-08-24 20:47 ` Matthew Wilcox
@ 2026-08-25 7:08 ` Miklos Szeredi
0 siblings, 0 replies; 18+ messages in thread
From: Miklos Szeredi @ 2026-08-25 7:08 UTC (permalink / raw)
To: Matthew Wilcox
Cc: Christian Brauner, Jan Kara, Chris Mason, David Sterba,
Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
On Mon, 24 Aug 2026 at 22:47, Matthew Wilcox <willy@infradead.org> wrote:
>
> On Mon, Aug 24, 2026 at 09:33:03PM +0200, Miklos Szeredi wrote:
> > In the second case filemap_write_and_wait_range() won't write protect
> > or unmap the page, so it may become dirty after the writeback.
>
> But that can also happen in the first case. page_mkwrite() can be called
> immediately after the folio is unlocked, for example. Or the folio can
> be evicted and replaced with a different folio which is then dirtied.
Let me try to make up a very concrete example. It's not realistic and
very unlikely to exist, yet something more subtle might be relying on
this behavior. I don't know. At this point I'm just trying to show
you that the two are really not equivalent, not that this has any
practical significance.
Let's imagine a distributed filesystem based on fuse. It wants to
serialize accesses to a file to avoid sub page writes reverting to
stale data on cached writeback. Cached writeback is unavoidable in
case of writable mmap, so it tries to handle this case correctly by
totally excluding cached aliases existing on different nodes. If one
node reads a range of pages, that range is invalidated on all other
nodes.
The below example show store to a single page on node A and B with and
without launder_page, starting with both having the page mmapped but
not yet in cache.
1) with launder_page
store on A
- send READ
- server checks that no aliases exists
- returns page
- page is mapped in address space and dirtied
store on B
- send READ
- server sees alias on A, sends INVALIDATE
- INVALIDATE is processed on A:
+ page is locked,
+ unmapped
+ data written to server via launder_page
+ unlocked
- returns updated page
- page is mapped in address space and dirtied
store on A concurrently with previous store on B
a) happens before page is unmapped:
- change is written by launder_page together with previous store
b) happens after page is unmapped:
- page lock waits for writeback to finish
- sends READ
- server sees in progress READ on B
- wait until READ on B is finished
- sends INVALIDATE to B
- ...
2) without launder_page
store on A: same as above
store on B
- send READ
- server sees alias on A, sends INVALIDATE
- INVALIDATE is processed on A:
+ page is written back
+ unmapped
- returns updated page
- page is mapped in address space and dirtied
store on A concurrently with previous store on B
a) happens before page writeback:
- change is written together with previous store
b) happens after page writeback but before unmap:
- since page is still mapped, store succeeds, page becomes dirty
- at this point we have differing cache contents on A and B
Thanks,
Miklos
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 3/7] btrfs: Use filemap_invalidate_pages()
2026-08-20 19:33 [PATCH 0/7] Remove aops->launder_folio Matthew Wilcox (Oracle)
2026-08-20 19:33 ` [PATCH 1/7] filemap: Export filemap_invalidate_pages() to modules Matthew Wilcox (Oracle)
2026-08-20 19:33 ` [PATCH 2/7] fuse: Use filemap_invalidate_pages() Matthew Wilcox (Oracle)
@ 2026-08-20 19:33 ` Matthew Wilcox (Oracle)
2026-08-24 21:57 ` Boris Burkov
2026-08-20 19:33 ` [PATCH 4/7] nfs: " Matthew Wilcox (Oracle)
` (4 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-08-20 19:33 UTC (permalink / raw)
To: Christian Brauner
Cc: Matthew Wilcox (Oracle), Jan Kara, Chris Mason, David Sterba,
Miklos Szeredi, Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
btrfs relies on invalidate_inode_pages2() /
invalidate_inode_pages2_range() doing writeback by calling
btrfs_launder_folio(). While this works, it is inefficient as each
folio is written back and waited for individually. Far better to call
filemap_invalidate_pages() which will do a bulk write first, then remove
the page cache.
With this done, btrfs_launder_folio() no longer needs to exist so
delete it.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
fs/btrfs/direct-io.c | 2 +-
fs/btrfs/disk-io.c | 11 +++++++----
fs/btrfs/free-space-cache.c | 4 ++--
fs/btrfs/inode.c | 12 ++----------
fs/btrfs/volumes.c | 4 ++--
5 files changed, 14 insertions(+), 19 deletions(-)
diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index 460326d34143..4bb5890d0898 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -115,7 +115,7 @@ static int lock_extent_direct(struct inode *inode, u64 lockstart, u64 lockend,
/*
* We could trigger writeback for this range (and wait
* for it to complete) and then invalidate the pages for
- * this range (through invalidate_inode_pages2_range()),
+ * this range (through filemap_invalidate_pages()),
* but that can lead us to a deadlock with a concurrent
* call to readahead (a buffered read or a defrag call
* triggered a readahead) on a page lock due to an
diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 2f1666d9544e..2cf9189225c9 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -3304,7 +3304,8 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info)
struct extent_buffer *eb;
int ret;
- ret = invalidate_inode_pages2(fs_info->btree_inode->i_mapping);
+ ret = filemap_invalidate_pages(fs_info->btree_inode->i_mapping, 0,
+ OFFSET_MAX);
if (likely(ret == 0))
return;
@@ -3339,7 +3340,8 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info)
rcu_read_lock();
}
rcu_read_unlock();
- invalidate_inode_pages2(fs_info->btree_inode->i_mapping);
+ filemap_invalidate_pages(fs_info->btree_inode->i_mapping, 0,
+ OFFSET_MAX);
}
static u32 calc_block_max_order(u32 sectorsize_bits)
@@ -4739,7 +4741,8 @@ static void btrfs_destroy_delalloc_inodes(struct btrfs_root *root)
unsigned int nofs_flag;
nofs_flag = memalloc_nofs_save();
- invalidate_inode_pages2(inode->i_mapping);
+ filemap_invalidate_pages(inode->i_mapping, 0,
+ OFFSET_MAX);
memalloc_nofs_restore(nofs_flag);
iput(inode);
}
@@ -4837,7 +4840,7 @@ static void btrfs_cleanup_bg_io(struct btrfs_block_group *cache)
unsigned int nofs_flag;
nofs_flag = memalloc_nofs_save();
- invalidate_inode_pages2(inode->i_mapping);
+ filemap_invalidate_pages(inode->i_mapping, 0, OFFSET_MAX);
memalloc_nofs_restore(nofs_flag);
BTRFS_I(inode)->generation = 0;
diff --git a/fs/btrfs/free-space-cache.c b/fs/btrfs/free-space-cache.c
index e2af75a205ea..a5d5eba2c85d 100644
--- a/fs/btrfs/free-space-cache.c
+++ b/fs/btrfs/free-space-cache.c
@@ -1307,7 +1307,7 @@ static int __btrfs_wait_cache_io(struct btrfs_root *root,
io_ctl->entries, io_ctl->bitmaps);
out:
if (ret) {
- invalidate_inode_pages2(inode->i_mapping);
+ filemap_invalidate_pages(inode->i_mapping, 0, OFFSET_MAX);
BTRFS_I(inode)->generation = 0;
if (block_group)
btrfs_debug(root->fs_info,
@@ -1500,7 +1500,7 @@ static int __btrfs_write_out_cache(struct inode *inode,
io_ctl->inode = NULL;
io_ctl_free(io_ctl);
if (ret) {
- invalidate_inode_pages2(inode->i_mapping);
+ filemap_invalidate_pages(inode->i_mapping, 0, OFFSET_MAX);
BTRFS_I(inode)->generation = 0;
}
btrfs_update_inode(trans, BTRFS_I(inode));
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 2534cd9284d5..a1bb73f083aa 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -7616,12 +7616,6 @@ static void wait_subpage_spinlock(struct folio *folio)
spin_unlock_irq(&bfs->lock);
}
-static int btrfs_launder_folio(struct folio *folio)
-{
- return btrfs_qgroup_free_data(folio_to_inode(folio), NULL, folio_pos(folio),
- folio_size(folio), NULL);
-}
-
static bool __btrfs_release_folio(struct folio *folio, gfp_t gfp_flags)
{
if (try_release_extent_mapping(folio, gfp_flags)) {
@@ -10027,9 +10021,8 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
ret = btrfs_wait_ordered_range(inode, start, num_bytes);
if (ret)
goto out_cb;
- ret = invalidate_inode_pages2_range(inode->vfs_inode.i_mapping,
- start >> PAGE_SHIFT,
- end >> PAGE_SHIFT);
+ ret = filemap_invalidate_pages(inode->vfs_inode.i_mapping,
+ start, end);
if (ret)
goto out_cb;
btrfs_lock_extent(io_tree, start, end, &cached_state);
@@ -10782,7 +10775,6 @@ static const struct address_space_operations btrfs_aops = {
.writepages = btrfs_writepages,
.readahead = btrfs_readahead,
.invalidate_folio = btrfs_invalidate_folio,
- .launder_folio = btrfs_launder_folio,
.release_folio = btrfs_release_folio,
.migrate_folio = btrfs_migrate_folio,
.dirty_folio = btrfs_data_dirty_folio,
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index 6eab4cc73ce4..0301465ab87f 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -1370,8 +1370,8 @@ struct btrfs_super_block *btrfs_read_disk_super(struct block_device *bdev,
* Drop the page of the primary superblock, so later read will
* always read from the device.
*/
- invalidate_inode_pages2_range(mapping, bytenr >> PAGE_SHIFT,
- (bytenr + BTRFS_SUPER_INFO_SIZE) >> PAGE_SHIFT);
+ filemap_invalidate_pages(mapping, bytenr,
+ bytenr + BTRFS_SUPER_INFO_SIZE - 1);
}
filemap_invalidate_lock_shared(mapping);
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 3/7] btrfs: Use filemap_invalidate_pages()
2026-08-20 19:33 ` [PATCH 3/7] btrfs: " Matthew Wilcox (Oracle)
@ 2026-08-24 21:57 ` Boris Burkov
0 siblings, 0 replies; 18+ messages in thread
From: Boris Burkov @ 2026-08-24 21:57 UTC (permalink / raw)
To: Matthew Wilcox (Oracle)
Cc: Christian Brauner, Jan Kara, Chris Mason, David Sterba,
Miklos Szeredi, Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
On Thu, Aug 20, 2026 at 08:33:36PM +0100, Matthew Wilcox (Oracle) wrote:
> btrfs relies on invalidate_inode_pages2() /
> invalidate_inode_pages2_range() doing writeback by calling
> btrfs_launder_folio(). While this works, it is inefficient as each
I don't believe btrfs_launder_folio() is doing writeback, it is just
dropping otherwise leaked qgroup reservation. So this part of the
commit message feels inaccurate to me, at least.
> folio is written back and waited for individually. Far better to call
> filemap_invalidate_pages() which will do a bulk write first, then remove
> the page cache.
>
> With this done, btrfs_launder_folio() no longer needs to exist so
> delete it.
>
> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
> ---
> fs/btrfs/direct-io.c | 2 +-
> fs/btrfs/disk-io.c | 11 +++++++----
> fs/btrfs/free-space-cache.c | 4 ++--
> fs/btrfs/inode.c | 12 ++----------
> fs/btrfs/volumes.c | 4 ++--
> 5 files changed, 14 insertions(+), 19 deletions(-)
>
> diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
> index 460326d34143..4bb5890d0898 100644
> --- a/fs/btrfs/direct-io.c
> +++ b/fs/btrfs/direct-io.c
> @@ -115,7 +115,7 @@ static int lock_extent_direct(struct inode *inode, u64 lockstart, u64 lockend,
> /*
> * We could trigger writeback for this range (and wait
> * for it to complete) and then invalidate the pages for
> - * this range (through invalidate_inode_pages2_range()),
> + * this range (through filemap_invalidate_pages()),
> * but that can lead us to a deadlock with a concurrent
> * call to readahead (a buffered read or a defrag call
> * triggered a readahead) on a page lock due to an
> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
> index 2f1666d9544e..2cf9189225c9 100644
> --- a/fs/btrfs/disk-io.c
> +++ b/fs/btrfs/disk-io.c
> @@ -3304,7 +3304,8 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info)
> struct extent_buffer *eb;
> int ret;
>
> - ret = invalidate_inode_pages2(fs_info->btree_inode->i_mapping);
> + ret = filemap_invalidate_pages(fs_info->btree_inode->i_mapping, 0,
> + OFFSET_MAX);
> if (likely(ret == 0))
> return;
>
> @@ -3339,7 +3340,8 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info)
> rcu_read_lock();
> }
> rcu_read_unlock();
> - invalidate_inode_pages2(fs_info->btree_inode->i_mapping);
> + filemap_invalidate_pages(fs_info->btree_inode->i_mapping, 0,
> + OFFSET_MAX);
> }
>
> static u32 calc_block_max_order(u32 sectorsize_bits)
> @@ -4739,7 +4741,8 @@ static void btrfs_destroy_delalloc_inodes(struct btrfs_root *root)
> unsigned int nofs_flag;
>
> nofs_flag = memalloc_nofs_save();
> - invalidate_inode_pages2(inode->i_mapping);
> + filemap_invalidate_pages(inode->i_mapping, 0,
> + OFFSET_MAX);
I believe that this was the original motivating call to
invalidate_inode_pages2 that I cared about. The context it runs in is
when the filesystem has failed on a transaction and needs to invalidate
the remaining dirty pages that have delalloc associated with them.
I am trying to figure out if trying to force the writeback in this
semi-failed context will also result in freeing the reservation, and
also testing this patch series on the test that I originally fixed with
launder_folio().
With all that said, thank you for working on cleaning up the mess I made
by adding this "creative" usage of ->launder_folio(). Hopefully we can
figure this out cleanly.
Thanks,
Boris
> memalloc_nofs_restore(nofs_flag);
> iput(inode);
> }
> @@ -4837,7 +4840,7 @@ static void btrfs_cleanup_bg_io(struct btrfs_block_group *cache)
> unsigned int nofs_flag;
>
> nofs_flag = memalloc_nofs_save();
> - invalidate_inode_pages2(inode->i_mapping);
> + filemap_invalidate_pages(inode->i_mapping, 0, OFFSET_MAX);
> memalloc_nofs_restore(nofs_flag);
>
> BTRFS_I(inode)->generation = 0;
> diff --git a/fs/btrfs/free-space-cache.c b/fs/btrfs/free-space-cache.c
> index e2af75a205ea..a5d5eba2c85d 100644
> --- a/fs/btrfs/free-space-cache.c
> +++ b/fs/btrfs/free-space-cache.c
> @@ -1307,7 +1307,7 @@ static int __btrfs_wait_cache_io(struct btrfs_root *root,
> io_ctl->entries, io_ctl->bitmaps);
> out:
> if (ret) {
> - invalidate_inode_pages2(inode->i_mapping);
> + filemap_invalidate_pages(inode->i_mapping, 0, OFFSET_MAX);
> BTRFS_I(inode)->generation = 0;
> if (block_group)
> btrfs_debug(root->fs_info,
> @@ -1500,7 +1500,7 @@ static int __btrfs_write_out_cache(struct inode *inode,
> io_ctl->inode = NULL;
> io_ctl_free(io_ctl);
> if (ret) {
> - invalidate_inode_pages2(inode->i_mapping);
> + filemap_invalidate_pages(inode->i_mapping, 0, OFFSET_MAX);
> BTRFS_I(inode)->generation = 0;
> }
> btrfs_update_inode(trans, BTRFS_I(inode));
> diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
> index 2534cd9284d5..a1bb73f083aa 100644
> --- a/fs/btrfs/inode.c
> +++ b/fs/btrfs/inode.c
> @@ -7616,12 +7616,6 @@ static void wait_subpage_spinlock(struct folio *folio)
> spin_unlock_irq(&bfs->lock);
> }
>
> -static int btrfs_launder_folio(struct folio *folio)
> -{
> - return btrfs_qgroup_free_data(folio_to_inode(folio), NULL, folio_pos(folio),
> - folio_size(folio), NULL);
> -}
> -
> static bool __btrfs_release_folio(struct folio *folio, gfp_t gfp_flags)
> {
> if (try_release_extent_mapping(folio, gfp_flags)) {
> @@ -10027,9 +10021,8 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
> ret = btrfs_wait_ordered_range(inode, start, num_bytes);
> if (ret)
> goto out_cb;
> - ret = invalidate_inode_pages2_range(inode->vfs_inode.i_mapping,
> - start >> PAGE_SHIFT,
> - end >> PAGE_SHIFT);
> + ret = filemap_invalidate_pages(inode->vfs_inode.i_mapping,
> + start, end);
> if (ret)
> goto out_cb;
> btrfs_lock_extent(io_tree, start, end, &cached_state);
> @@ -10782,7 +10775,6 @@ static const struct address_space_operations btrfs_aops = {
> .writepages = btrfs_writepages,
> .readahead = btrfs_readahead,
> .invalidate_folio = btrfs_invalidate_folio,
> - .launder_folio = btrfs_launder_folio,
> .release_folio = btrfs_release_folio,
> .migrate_folio = btrfs_migrate_folio,
> .dirty_folio = btrfs_data_dirty_folio,
> diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
> index 6eab4cc73ce4..0301465ab87f 100644
> --- a/fs/btrfs/volumes.c
> +++ b/fs/btrfs/volumes.c
> @@ -1370,8 +1370,8 @@ struct btrfs_super_block *btrfs_read_disk_super(struct block_device *bdev,
> * Drop the page of the primary superblock, so later read will
> * always read from the device.
> */
> - invalidate_inode_pages2_range(mapping, bytenr >> PAGE_SHIFT,
> - (bytenr + BTRFS_SUPER_INFO_SIZE) >> PAGE_SHIFT);
> + filemap_invalidate_pages(mapping, bytenr,
> + bytenr + BTRFS_SUPER_INFO_SIZE - 1);
> }
>
> filemap_invalidate_lock_shared(mapping);
> --
> 2.47.3
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 4/7] nfs: Use filemap_invalidate_pages()
2026-08-20 19:33 [PATCH 0/7] Remove aops->launder_folio Matthew Wilcox (Oracle)
` (2 preceding siblings ...)
2026-08-20 19:33 ` [PATCH 3/7] btrfs: " Matthew Wilcox (Oracle)
@ 2026-08-20 19:33 ` Matthew Wilcox (Oracle)
2026-08-20 19:33 ` [PATCH 5/7] orangefs: " Matthew Wilcox (Oracle)
` (3 subsequent siblings)
7 siblings, 0 replies; 18+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-08-20 19:33 UTC (permalink / raw)
To: Christian Brauner
Cc: Matthew Wilcox (Oracle), Jan Kara, Chris Mason, David Sterba,
Miklos Szeredi, Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
nfs relies on invalidate_inode_pages2() / invalidate_inode_pages2_range()
doing writeback by calling nfs_launder_folio(). While this works, it is
inefficient as each folio is written back and waited for individually.
Far better to call filemap_invalidate_pages() which will do a bulk write
first, then remove the page cache.
With this done, nfs_launder_folio() no longer needs to exist so delete it.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
fs/nfs/dir.c | 7 ++++---
fs/nfs/direct.c | 5 ++---
fs/nfs/file.c | 24 ------------------------
fs/nfs/inode.c | 5 +++--
fs/nfs/nfs42proc.c | 3 +--
fs/nfs/nfstrace.h | 1 -
6 files changed, 10 insertions(+), 35 deletions(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index c7caffb31935..265471ef5dae 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1035,7 +1035,8 @@ static int find_and_lock_cache_page(struct nfs_readdir_descriptor *desc)
nfs_readdir_folio_unlock_and_put_cached(desc);
trace_nfs_readdir_cache_fill_done(inode, res);
if (res == -EBADCOOKIE || res == -ENOTSYNC) {
- invalidate_inode_pages2(desc->file->f_mapping);
+ filemap_invalidate_pages(desc->file->f_mapping,
+ 0, OFFSET_MAX);
nfs_readdir_rewind_search(desc);
trace_nfs_readdir_invalidate_cache_range(
inode, 0, MAX_LFS_FILESIZE);
@@ -1050,8 +1051,8 @@ static int find_and_lock_cache_page(struct nfs_readdir_descriptor *desc)
memcmp(nfsi->cookieverf, verf, sizeof(nfsi->cookieverf))) {
memcpy(nfsi->cookieverf, verf,
sizeof(nfsi->cookieverf));
- invalidate_inode_pages2_range(desc->file->f_mapping, 1,
- -1);
+ filemap_invalidate_pages(desc->file->f_mapping,
+ PAGE_SIZE, -1);
trace_nfs_readdir_invalidate_cache_range(
inode, 1, MAX_LFS_FILESIZE);
}
diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c
index e626c72495e6..56ad855b9027 100644
--- a/fs/nfs/direct.c
+++ b/fs/nfs/direct.c
@@ -1019,7 +1019,7 @@ ssize_t nfs_file_direct_write(struct kiocb *iocb, struct iov_iter *iter,
nfs_add_stats(mapping->host, NFSIOS_DIRECTWRITTENBYTES, count);
pos = iocb->ki_pos;
- end = (pos + iov_iter_count(iter) - 1) >> PAGE_SHIFT;
+ end = pos + iov_iter_count(iter) - 1;
task_io_account_write(count);
@@ -1060,8 +1060,7 @@ ssize_t nfs_file_direct_write(struct kiocb *iocb, struct iov_iter *iter,
FLUSH_COND_STABLE);
if (mapping->nrpages) {
- invalidate_inode_pages2_range(mapping,
- pos >> PAGE_SHIFT, end);
+ filemap_invalidate_pages(mapping, pos, end);
}
nfs_end_io_direct(inode);
diff --git a/fs/nfs/file.c b/fs/nfs/file.c
index a0d8f1c1cf10..cfc9c0135845 100644
--- a/fs/nfs/file.c
+++ b/fs/nfs/file.c
@@ -552,29 +552,6 @@ static void nfs_check_dirty_writeback(struct folio *folio,
*dirty = true;
}
-/*
- * Attempt to clear the private state associated with a page when an error
- * occurs that requires the cached contents of an inode to be written back or
- * destroyed
- * - Called if either PG_private or fscache is set on the page
- * - Caller holds page lock
- * - Return 0 if successful, -error otherwise
- */
-static int nfs_launder_folio(struct folio *folio)
-{
- struct inode *inode = folio->mapping->host;
- int ret;
-
- dfprintk(PAGECACHE, "NFS: launder_folio(%llu, %llu)\n",
- inode->i_ino, folio_pos(folio));
-
- folio_wait_private_2(folio); /* [DEPRECATED] */
- ret = nfs_wb_folio(inode, folio);
- trace_nfs_launder_folio_done(inode, folio_pos(folio),
- folio_size(folio), ret);
- return ret;
-}
-
static int nfs_swap_activate(struct swap_info_struct *sis, struct file *file,
sector_t *span)
{
@@ -633,7 +610,6 @@ const struct address_space_operations nfs_file_aops = {
.invalidate_folio = nfs_invalidate_folio,
.release_folio = nfs_release_folio,
.migrate_folio = nfs_migrate_folio,
- .launder_folio = nfs_launder_folio,
.is_dirty_writeback = nfs_check_dirty_writeback,
.error_remove_folio = generic_error_remove_folio,
.swap_activate = nfs_swap_activate,
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 5bcd4027d203..089dd7df1790 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -1323,7 +1323,8 @@ void nfs_file_clear_open_context(struct file *filp)
* every page again.
*/
if (ctx->error < 0)
- invalidate_inode_pages2(inode->i_mapping);
+ filemap_invalidate_pages(inode->i_mapping, 0,
+ OFFSET_MAX);
filp->private_data = NULL;
put_nfs_open_context_sync(ctx);
}
@@ -1461,7 +1462,7 @@ static int nfs_invalidate_mapping(struct inode *inode, struct address_space *map
if (ret < 0)
return ret;
}
- ret = invalidate_inode_pages2(mapping);
+ ret = filemap_invalidate_pages(mapping, 0, OFFSET_MAX);
if (ret < 0)
return ret;
}
diff --git a/fs/nfs/nfs42proc.c b/fs/nfs/nfs42proc.c
index ab86246fc364..8555fd0a7f71 100644
--- a/fs/nfs/nfs42proc.c
+++ b/fs/nfs/nfs42proc.c
@@ -395,8 +395,7 @@ static void nfs42_copy_dest_done(struct file *file, loff_t pos, loff_t len,
loff_t end = newsize - 1;
nfs_truncate_last_folio(mapping, oldsize, pos);
- WARN_ON_ONCE(invalidate_inode_pages2_range(mapping, pos >> PAGE_SHIFT,
- end >> PAGE_SHIFT));
+ WARN_ON_ONCE(filemap_invalidate_pages(mapping, pos, end));
spin_lock(&inode->i_lock);
if (newsize > i_size_read(inode))
diff --git a/fs/nfs/nfstrace.h b/fs/nfs/nfstrace.h
index 4ada21f4eebd..7774b86dbd60 100644
--- a/fs/nfs/nfstrace.h
+++ b/fs/nfs/nfstrace.h
@@ -1069,7 +1069,6 @@ DEFINE_NFS_FOLIO_EVENT(nfs_writeback_folio);
DEFINE_NFS_FOLIO_EVENT_DONE(nfs_writeback_folio_done);
DEFINE_NFS_FOLIO_EVENT(nfs_invalidate_folio);
-DEFINE_NFS_FOLIO_EVENT_DONE(nfs_launder_folio_done);
DEFINE_NFS_FOLIO_EVENT(nfs_try_to_update_request);
DEFINE_NFS_FOLIO_EVENT_DONE(nfs_try_to_update_request_done);
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 5/7] orangefs: Use filemap_invalidate_pages()
2026-08-20 19:33 [PATCH 0/7] Remove aops->launder_folio Matthew Wilcox (Oracle)
` (3 preceding siblings ...)
2026-08-20 19:33 ` [PATCH 4/7] nfs: " Matthew Wilcox (Oracle)
@ 2026-08-20 19:33 ` Matthew Wilcox (Oracle)
2026-08-20 19:33 ` [PATCH 6/7] orangefs: Remove launder_folio implementation Matthew Wilcox (Oracle)
` (2 subsequent siblings)
7 siblings, 0 replies; 18+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-08-20 19:33 UTC (permalink / raw)
To: Christian Brauner
Cc: Matthew Wilcox (Oracle), Jan Kara, Chris Mason, David Sterba,
Miklos Szeredi, Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
This pair of calls simply open-codes Use filemap_invalidate_pages(),
so convert it.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
fs/orangefs/file.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/fs/orangefs/file.c b/fs/orangefs/file.c
index 42591252e239..36e3685f7e89 100644
--- a/fs/orangefs/file.c
+++ b/fs/orangefs/file.c
@@ -306,9 +306,7 @@ int orangefs_revalidate_mapping(struct inode *inode)
spin_unlock(&inode->i_lock);
unmap_mapping_range(mapping, 0, 0, 0);
- ret = filemap_write_and_wait(mapping);
- if (!ret)
- ret = invalidate_inode_pages2(mapping);
+ ret = filemap_invalidate_pages(mapping, 0, OFFSET_MAX);
orangefs_inode->mapping_time = jiffies +
orangefs_cache_timeout_msecs*HZ/1000;
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 6/7] orangefs: Remove launder_folio implementation
2026-08-20 19:33 [PATCH 0/7] Remove aops->launder_folio Matthew Wilcox (Oracle)
` (4 preceding siblings ...)
2026-08-20 19:33 ` [PATCH 5/7] orangefs: " Matthew Wilcox (Oracle)
@ 2026-08-20 19:33 ` Matthew Wilcox (Oracle)
2026-08-20 19:33 ` [PATCH 7/7] Remove folio_launder() Matthew Wilcox (Oracle)
2026-08-25 15:50 ` [PATCH 0/7] Remove aops->launder_folio Jan Kara
7 siblings, 0 replies; 18+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-08-20 19:33 UTC (permalink / raw)
To: Christian Brauner
Cc: Matthew Wilcox (Oracle), Jan Kara, Chris Mason, David Sterba,
Miklos Szeredi, Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
There doesn't seem to be a good reason to implement ->launder_folio
in orangefs. Leave orangefs_launder_folio() alone as it is used
in several places and removing it entirely would be tricky.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
fs/orangefs/inode.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c
index 7143b64b5b25..c1f5658ba8af 100644
--- a/fs/orangefs/inode.c
+++ b/fs/orangefs/inode.c
@@ -616,7 +616,6 @@ static const struct address_space_operations orangefs_address_operations = {
.release_folio = orangefs_release_folio,
.free_folio = orangefs_free_folio,
.migrate_folio = filemap_migrate_folio,
- .launder_folio = orangefs_launder_folio,
.direct_IO = orangefs_direct_IO,
};
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 7/7] Remove folio_launder()
2026-08-20 19:33 [PATCH 0/7] Remove aops->launder_folio Matthew Wilcox (Oracle)
` (5 preceding siblings ...)
2026-08-20 19:33 ` [PATCH 6/7] orangefs: Remove launder_folio implementation Matthew Wilcox (Oracle)
@ 2026-08-20 19:33 ` Matthew Wilcox (Oracle)
2026-08-25 15:50 ` [PATCH 0/7] Remove aops->launder_folio Jan Kara
7 siblings, 0 replies; 18+ messages in thread
From: Matthew Wilcox (Oracle) @ 2026-08-20 19:33 UTC (permalink / raw)
To: Christian Brauner
Cc: Matthew Wilcox (Oracle), Jan Kara, Chris Mason, David Sterba,
Miklos Szeredi, Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
When we do direct I/O, we need to evict any page cache that overlaps
the range in the file. First we write back any dirty folios in the
range, then we lock each folio and remove it from the CPU page tables.
This leaves a wide window for userspace to re-dirty the folio by storing
to a shared writable mmap.
Many filesystems respond to this situation by failing the call to
release_folio(), but some try to writeback the dirty folio again, formerly
in their release_folio() method and now in their launder_folio() method.
Remove this inconsistency between filesystems by checking
whether the folio is dirty in the VFS and failing the call to
folio_unmap_invalidate(). Since we hold the folio locked and unmapped
at this time, there is no way to dirty the folio after this point.
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
Documentation/filesystems/locking.rst | 8 --------
Documentation/filesystems/vfs.rst | 6 ------
include/linux/fs.h | 1 -
mm/truncate.c | 15 ++-------------
4 files changed, 2 insertions(+), 28 deletions(-)
diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst
index 08d01bc62c31..fd11f5c5d91e 100644
--- a/Documentation/filesystems/locking.rst
+++ b/Documentation/filesystems/locking.rst
@@ -261,7 +261,6 @@ prototypes::
int (*direct_IO)(struct kiocb *, struct iov_iter *iter);
int (*migrate_folio)(struct address_space *, struct folio *dst,
struct folio *src, enum migrate_mode);
- int (*launder_folio)(struct folio *);
bool (*is_partially_uptodate)(struct folio *, size_t from, size_t count);
int (*error_remove_folio)(struct address_space *, struct folio *);
int (*swap_activate)(struct swap_info_struct *sis, struct file *f, sector_t *span)
@@ -286,7 +285,6 @@ release_folio: yes
free_folio: yes
direct_IO:
migrate_folio: yes (both)
-launder_folio: yes
is_partially_uptodate: yes
error_remove_folio: yes
swap_activate: no
@@ -344,12 +342,6 @@ try_to_free_buffers().
->free_folio() is called when the kernel has dropped the folio
from the page cache.
-->launder_folio() may be called prior to releasing a folio if
-it is still found to be dirty. It returns zero if the folio was successfully
-cleaned, or an error value if not. Note that in order to prevent the folio
-getting mapped back in and redirtied, it needs to be kept locked
-across the entire operation.
-
->swap_activate() will be called to prepare the given file for swap. It
should perform any validation and preparation necessary to ensure that
writes can be performed with minimal memory allocation. It should call
diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
index 7c753148af88..bff64713bd09 100644
--- a/Documentation/filesystems/vfs.rst
+++ b/Documentation/filesystems/vfs.rst
@@ -768,7 +768,6 @@ cache in your filesystem. The following members are defined:
ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter);
int (*migrate_folio)(struct mapping *, struct folio *dst,
struct folio *src, enum migrate_mode);
- int (*launder_folio) (struct folio *);
bool (*is_partially_uptodate) (struct folio *, size_t from,
size_t count);
@@ -942,11 +941,6 @@ cache in your filesystem. The following members are defined:
folio to this function. migrate_folio should transfer any private
data across and update any references that it has to the folio.
-``launder_folio``
- Called before freeing a folio - it writes back the dirty folio.
- To prevent redirtying the folio, it is kept locked during the
- whole operation.
-
``is_partially_uptodate``
Called by the VM when reading a file through the pagecache when
the underlying blocksize is smaller than the size of the folio.
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 50ce731a2b78..e0f4f518fe24 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -428,7 +428,6 @@ struct address_space_operations {
*/
int (*migrate_folio)(struct address_space *, struct folio *dst,
struct folio *src, enum migrate_mode);
- int (*launder_folio)(struct folio *);
bool (*is_partially_uptodate) (struct folio *, size_t from,
size_t count);
void (*is_dirty_writeback) (struct folio *, bool *dirty, bool *wb);
diff --git a/mm/truncate.c b/mm/truncate.c
index b58ba940be47..7f7d65fa926a 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -603,15 +603,6 @@ unsigned long invalidate_mapping_pages(struct address_space *mapping,
}
EXPORT_SYMBOL(invalidate_mapping_pages);
-static int folio_launder(struct address_space *mapping, struct folio *folio)
-{
- if (!folio_test_dirty(folio))
- return 0;
- if (folio->mapping != mapping || mapping->a_ops->launder_folio == NULL)
- return 0;
- return mapping->a_ops->launder_folio(folio);
-}
-
/*
* This is like mapping_evict_folio(), except it ignores the folio's
* refcount. We do this because invalidate_inode_pages2() needs stronger
@@ -623,7 +614,6 @@ int folio_unmap_invalidate(struct address_space *mapping, struct folio *folio,
gfp_t gfp)
{
void (*free_folio)(struct folio *);
- int ret;
VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
@@ -631,9 +621,8 @@ int folio_unmap_invalidate(struct address_space *mapping, struct folio *folio,
unmap_mapping_folio(folio);
BUG_ON(folio_mapped(folio));
- ret = folio_launder(mapping, folio);
- if (ret)
- return ret;
+ if (folio_test_dirty(folio))
+ return -EBUSY;
if (folio->mapping != mapping)
return -EBUSY;
if (!filemap_release_folio(folio, gfp))
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 0/7] Remove aops->launder_folio
2026-08-20 19:33 [PATCH 0/7] Remove aops->launder_folio Matthew Wilcox (Oracle)
` (6 preceding siblings ...)
2026-08-20 19:33 ` [PATCH 7/7] Remove folio_launder() Matthew Wilcox (Oracle)
@ 2026-08-25 15:50 ` Jan Kara
7 siblings, 0 replies; 18+ messages in thread
From: Jan Kara @ 2026-08-25 15:50 UTC (permalink / raw)
To: Matthew Wilcox (Oracle)
Cc: Christian Brauner, Jan Kara, Chris Mason, David Sterba,
Miklos Szeredi, Trond Myklebust, Anna Schumaker, Mike Marshall,
Martin Brandenburg, Alexander Viro, linux-fsdevel, linux-mm,
linux-block, linux-btrfs, fuse-devel, linux-nfs, devel,
Pavel Begunkov
On Thu 20-08-26 20:33:33, Matthew Wilcox (Oracle) wrote:
> There's really no reason to implement ->launder_folio. Most filesystems
> don't. The only problem is that some filesystems rely on dirty folios
> being written back this way, so we need to make sure that folios are
> written back before invalidating them. filemap_invalidate_pages()
> works well for that, once we remove the 'nowait' parameter.
I like the idea (and was myself wondering from time to time whether
launder_folio is actually needed) but what was putting me off was that when
using folio_launder() you can clean a folio while keeping folio locked and
so you can guarantee later eviction without someone messing with the folio
between writeout & ->release_folio. Maybe nobody actually needs this, which
would be great, but it would be great to hear more from the folks using
launder_folio about their reasons...
Honza
>
> Matthew Wilcox (Oracle) (7):
> filemap: Export filemap_invalidate_pages() to modules
> fuse: Use filemap_invalidate_pages()
> btrfs: Use filemap_invalidate_pages()
> nfs: Use filemap_invalidate_pages()
> orangefs: Use filemap_invalidate_pages()
> orangefs: Remove launder_folio implementation
> Remove folio_launder()
>
> Documentation/filesystems/locking.rst | 8 -----
> Documentation/filesystems/vfs.rst | 6 ----
> block/ioctl.c | 14 +++++---
> fs/btrfs/direct-io.c | 2 +-
> fs/btrfs/disk-io.c | 11 ++++---
> fs/btrfs/free-space-cache.c | 4 +--
> fs/btrfs/inode.c | 12 ++-----
> fs/btrfs/volumes.c | 4 +--
> fs/fuse/dax.c | 15 ++-------
> fs/fuse/dir.c | 12 ++++---
> fs/fuse/file.c | 46 ++++-----------------------
> fs/fuse/inode.c | 17 +++-------
> fs/nfs/dir.c | 7 ++--
> fs/nfs/direct.c | 5 ++-
> fs/nfs/file.c | 24 --------------
> fs/nfs/inode.c | 5 +--
> fs/nfs/nfs42proc.c | 3 +-
> fs/nfs/nfstrace.h | 1 -
> fs/orangefs/file.c | 4 +--
> fs/orangefs/inode.c | 1 -
> include/linux/fs.h | 1 -
> include/linux/pagemap.h | 2 +-
> mm/filemap.c | 42 ++++++++++++++++--------
> mm/truncate.c | 15 ++-------
> 24 files changed, 87 insertions(+), 174 deletions(-)
>
> --
> 2.47.3
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 18+ messages in thread