Hello,
this patch series modifies ext4 so that we stop using ext4_writepage() for
writeout of ordered data during transaction commit (through
generic_writepages() from jbd2_journal_submit_inode_data_buffers()). Instead we
directly call ext4_writepages() from the
ext4_journal_submit_inode_data_buffers(). This is part of Christoph's effort
to get rid of the .writepage() callback in all filesystems.
Note that we still keep ext4_writepage() function for writeout of journalled
data. That can be also dealt with but let's untangle things one by one. Also
note that jbd2 still uses generic_writepages() in its
jbd2_journal_submit_inode_data_buffers() helper because it is still used from
OCFS2. Again, something to be dealt with in a separate patchset.
Patches pass for me tests on both 1k and 4k filesystems with dioreadlock and
without it.
Honza
ext4_writepage() should not be called for ordered data anymore. Remove
support for it from the function.
Signed-off-by: Jan Kara <[email protected]>
---
fs/ext4/inode.c | 116 ++++++------------------------------------------
1 file changed, 13 insertions(+), 103 deletions(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index c131b611dabf..0c8e700265f1 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -1642,12 +1642,6 @@ static void ext4_print_free_blocks(struct inode *inode)
return;
}
-static int ext4_bh_delay_or_unwritten(handle_t *handle, struct inode *inode,
- struct buffer_head *bh)
-{
- return (buffer_delay(bh) || buffer_unwritten(bh)) && buffer_dirty(bh);
-}
-
/*
* ext4_insert_delayed_block - adds a delayed block to the extents status
* tree, incrementing the reserved cluster/block
@@ -1962,56 +1956,17 @@ static int __ext4_journalled_writepage(struct page *page,
}
/*
- * Note that we don't need to start a transaction unless we're journaling data
- * because we should have holes filled from ext4_page_mkwrite(). We even don't
- * need to file the inode to the transaction's list in ordered mode because if
- * we are writing back data added by write(), the inode is already there and if
- * we are writing back data modified via mmap(), no one guarantees in which
- * transaction the data will hit the disk. In case we are journaling data, we
- * cannot start transaction directly because transaction start ranks above page
- * lock so we have to do some magic.
- *
- * This function can get called via...
- * - ext4_writepages after taking page lock (have journal handle)
- * - journal_submit_inode_data_buffers (no journal handle)
- * - shrink_page_list via the kswapd/direct reclaim (no journal handle)
- * - grab_page_cache when doing write_begin (have journal handle)
- *
- * We don't do any block allocation in this function. If we have page with
- * multiple blocks we need to write those buffer_heads that are mapped. This
- * is important for mmaped based write. So if we do with blocksize 1K
- * truncate(f, 1024);
- * a = mmap(f, 0, 4096);
- * a[0] = 'a';
- * truncate(f, 4096);
- * we have in the page first buffer_head mapped via page_mkwrite call back
- * but other buffer_heads would be unmapped but dirty (dirty done via the
- * do_wp_page). So writepage should write the first block. If we modify
- * the mmap area beyond 1024 we will again get a page_fault and the
- * page_mkwrite callback will do the block allocation and mark the
- * buffer_heads mapped.
- *
- * We redirty the page if we have any buffer_heads that is either delay or
- * unwritten in the page.
- *
- * We can get recursively called as show below.
- *
- * ext4_writepage() -> kmalloc() -> __alloc_pages() -> page_launder() ->
- * ext4_writepage()
- *
- * But since we don't do any block allocation we should not deadlock.
- * Page also have the dirty flag cleared so we don't get recurive page_lock.
+ * This function is now used only when journaling data. We cannot start
+ * transaction directly because transaction start ranks above page lock so we
+ * have to do some magic.
*/
static int ext4_writepage(struct page *page,
struct writeback_control *wbc)
{
struct folio *folio = page_folio(page);
- int ret = 0;
loff_t size;
unsigned int len;
- struct buffer_head *page_bufs = NULL;
struct inode *inode = page->mapping->host;
- struct ext4_io_submit io_submit;
if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb)))) {
folio_invalidate(folio, 0, folio_size(folio));
@@ -2036,60 +1991,16 @@ static int ext4_writepage(struct page *page,
return 0;
}
- page_bufs = page_buffers(page);
- /*
- * We cannot do block allocation or other extent handling in this
- * function. If there are buffers needing that, we have to redirty
- * the page. But we may reach here when we do a journal commit via
- * journal_submit_inode_data_buffers() and in that case we must write
- * allocated buffers to achieve data=ordered mode guarantees.
- *
- * Also, if there is only one buffer per page (the fs block
- * size == the page size), if one buffer needs block
- * allocation or needs to modify the extent tree to clear the
- * unwritten flag, we know that the page can't be written at
- * all, so we might as well refuse the write immediately.
- * Unfortunately if the block size != page size, we can't as
- * easily detect this case using ext4_walk_page_buffers(), but
- * for the extremely common case, this is an optimization that
- * skips a useless round trip through ext4_bio_write_page().
- */
- if (ext4_walk_page_buffers(NULL, inode, page_bufs, 0, len, NULL,
- ext4_bh_delay_or_unwritten)) {
- redirty_page_for_writepage(wbc, page);
- if ((current->flags & PF_MEMALLOC) ||
- (inode->i_sb->s_blocksize == PAGE_SIZE)) {
- /*
- * For memory cleaning there's no point in writing only
- * some buffers. So just bail out. Warn if we came here
- * from direct reclaim.
- */
- WARN_ON_ONCE((current->flags & (PF_MEMALLOC|PF_KSWAPD))
- == PF_MEMALLOC);
- unlock_page(page);
- return 0;
- }
- }
-
- if (PageChecked(page) && ext4_should_journal_data(inode))
- /*
- * It's mmapped pagecache. Add buffers and journal it. There
- * doesn't seem much point in redirtying the page here.
- */
- return __ext4_journalled_writepage(page, len);
-
- ext4_io_submit_init(&io_submit, wbc);
- io_submit.io_end = ext4_init_io_end(inode, GFP_NOFS);
- if (!io_submit.io_end) {
- redirty_page_for_writepage(wbc, page);
+ WARN_ON_ONCE(!ext4_should_journal_data(inode));
+ if (!PageChecked(page)) {
unlock_page(page);
- return -ENOMEM;
+ return 0;
}
- ret = ext4_bio_write_page(&io_submit, page, len);
- ext4_io_submit(&io_submit);
- /* Drop io_end reference we got from init */
- ext4_put_io_end_defer(io_submit.io_end);
- return ret;
+ /*
+ * It's mmapped pagecache. Add buffers and journal it. There
+ * doesn't seem much point in redirtying the page here.
+ */
+ return __ext4_journalled_writepage(page, len);
}
static int mpage_submit_page(struct mpage_da_data *mpd, struct page *page)
@@ -3142,9 +3053,8 @@ static int ext4_da_write_end(struct file *file,
* i_disksize since writeback will push i_disksize upto i_size
* eventually. If the end of the current write is > i_size and
* inside an allocated block (ext4_da_should_update_i_disksize()
- * check), we need to update i_disksize here as neither
- * ext4_writepage() nor certain ext4_writepages() paths not
- * allocating blocks update i_disksize.
+ * check), we need to update i_disksize here as ext4_writepages() need
+ * not do it in this case.
*
* Note that we defer inode dirtying to generic_write_end() /
* ext4_da_write_inline_data_end().
--
2.35.3
nr_submitted is the same as nr_to_submit. Drop one of them.
Signed-off-by: Jan Kara <[email protected]>
---
fs/ext4/page-io.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
index 4f9ecacd10aa..2bdfb8a046d9 100644
--- a/fs/ext4/page-io.c
+++ b/fs/ext4/page-io.c
@@ -437,7 +437,6 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
unsigned block_start;
struct buffer_head *bh, *head;
int ret = 0;
- int nr_submitted = 0;
int nr_to_submit = 0;
struct writeback_control *wbc = io->io_wbc;
bool keep_towrite = false;
@@ -566,7 +565,6 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
continue;
io_submit_add_bh(io, inode,
bounce_page ? bounce_page : page, bh);
- nr_submitted++;
} while ((bh = bh->b_this_page) != head);
unlock:
unlock_page(page);
--
2.35.3
Move protection by percpu_rwsem from ext4_do_writepages() to
ext4_writepages(). We will not want to grab this protection during
transaction commits as that would be prone to deadlocks and the
protection is not needed. Move the shutdown state checking as well since
we want to be able to complete commit while the shutdown is in progress.
Signed-off-by: Jan Kara <[email protected]>
---
fs/ext4/inode.c | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index fbea77ab470f..00c4d12f8270 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -2718,10 +2718,6 @@ static int ext4_do_writepages(struct mpage_da_data *mpd)
struct blk_plug plug;
bool give_up_on_write = false;
- if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
- return -EIO;
-
- percpu_down_read(&sbi->s_writepages_rwsem);
trace_ext4_writepages(inode, wbc);
/*
@@ -2930,20 +2926,28 @@ static int ext4_do_writepages(struct mpage_da_data *mpd)
out_writepages:
trace_ext4_writepages_result(inode, wbc, ret,
nr_to_write - wbc->nr_to_write);
- percpu_up_read(&sbi->s_writepages_rwsem);
return ret;
}
static int ext4_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
+ struct super_block *sb = mapping->host->i_sb;
struct mpage_da_data mpd = {
.inode = mapping->host,
.wbc = wbc,
.can_map = 1,
};
+ int ret;
+
+ if (unlikely(ext4_forced_shutdown(EXT4_SB(sb))))
+ return -EIO;
- return ext4_do_writepages(&mpd);
+ percpu_down_read(&EXT4_SB(sb)->s_writepages_rwsem);
+ ret = ext4_do_writepages(&mpd);
+ percpu_up_read(&EXT4_SB(sb)->s_writepages_rwsem);
+
+ return ret;
}
static int ext4_dax_writepages(struct address_space *mapping,
--
2.35.3
Since we want to transition transaction commits to use ext4_writepages()
for writing back ordered, add handling of page redirtying into
ext4_bio_write_page(). Also move buffer dirty bit clearing into the same
place other buffer state handling.
Signed-off-by: Jan Kara <[email protected]>
---
fs/ext4/page-io.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
index 97fa7b4c645f..4e68ace86f11 100644
--- a/fs/ext4/page-io.c
+++ b/fs/ext4/page-io.c
@@ -482,6 +482,13 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
/* A hole? We can safely clear the dirty bit */
if (!buffer_mapped(bh))
clear_buffer_dirty(bh);
+ /*
+ * Keeping dirty some buffer we cannot write? Make
+ * sure to redirty the page. This happens e.g. when
+ * doing writeout for transaction commit.
+ */
+ if (buffer_dirty(bh) && !PageDirty(page))
+ redirty_page_for_writepage(wbc, page);
if (io->io_bio)
ext4_io_submit(io);
continue;
@@ -489,6 +496,7 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
if (buffer_new(bh))
clear_buffer_new(bh);
set_buffer_async_write(bh);
+ clear_buffer_dirty(bh);
nr_to_submit++;
} while ((bh = bh->b_this_page) != head);
@@ -532,7 +540,10 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
printk_ratelimited(KERN_ERR "%s: ret = %d\n", __func__, ret);
redirty_page_for_writepage(wbc, page);
do {
- clear_buffer_async_write(bh);
+ if (buffer_async_write(bh)) {
+ clear_buffer_async_write(bh);
+ set_buffer_dirty(bh);
+ }
bh = bh->b_this_page;
} while (bh != head);
goto unlock;
@@ -546,7 +557,6 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
io_submit_add_bh(io, inode,
bounce_page ? bounce_page : page, bh);
nr_submitted++;
- clear_buffer_dirty(bh);
} while ((bh = bh->b_this_page) != head);
unlock:
--
2.35.3
We submit outstanding IO in ext4_bio_write_page() if we find a buffer we
are not going to write. This is however pointless because we already
handle submission of previous IO in case we detect newly added buffer
head is discontiguous. So just delete the pointless IO submission call.
Signed-off-by: Jan Kara <[email protected]>
---
fs/ext4/page-io.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
index 2bdfb8a046d9..beaec6d81074 100644
--- a/fs/ext4/page-io.c
+++ b/fs/ext4/page-io.c
@@ -489,8 +489,6 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
redirty_page_for_writepage(wbc, page);
keep_towrite = true;
}
- if (io->io_bio)
- ext4_io_submit(io);
continue;
}
if (buffer_new(bh))
--
2.35.3
On 22/11/30 05:35PM, Jan Kara wrote:
> We submit outstanding IO in ext4_bio_write_page() if we find a buffer we
> are not going to write. This is however pointless because we already
> handle submission of previous IO in case we detect newly added buffer
> head is discontiguous. So just delete the pointless IO submission call.
Agreed. io_submit_add_bh() is anyway called at the end for submitting buffers.
And io_submit_add_bh() also has the logic to:
1. submit a discontiguous bio
2. Also submit a bio if the bio gets full (submit_and_retry label).
Hence calling ext4_io_submit() early is not required.
I guess the same will also hold true for at this place.
https://elixir.bootlin.com/linux/v6.1-rc7/source/fs/ext4/page-io.c#L524
But this patch looks good to me. Feel free to add:
Reviewed-by: Ritesh Harjani (IBM) <[email protected]>
>
> Signed-off-by: Jan Kara <[email protected]>
> ---
> fs/ext4/page-io.c | 2 --
> 1 file changed, 2 deletions(-)
>
> diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> index 2bdfb8a046d9..beaec6d81074 100644
> --- a/fs/ext4/page-io.c
> +++ b/fs/ext4/page-io.c
> @@ -489,8 +489,6 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
> redirty_page_for_writepage(wbc, page);
> keep_towrite = true;
> }
> - if (io->io_bio)
> - ext4_io_submit(io);
> continue;
> }
> if (buffer_new(bh))
> --
> 2.35.3
>
On 22/11/30 05:35PM, Jan Kara wrote:
> Since we want to transition transaction commits to use ext4_writepages()
> for writing back ordered, add handling of page redirtying into
> ext4_bio_write_page(). Also move buffer dirty bit clearing into the same
> place other buffer state handling.
>
So when we will move away from ext4_writepage() and will instead call
ext4_writepages() (for transaction commits requiring ordered write handling),
this patch should help with redirtying the page, if any of the page buffer
cannot be written back which is when either the buffer is either marked delayed
(which will require block allocation) or is unwritten (which may also
require some block allocation during unwritten to written conversion).
Also, one other good thing about this patch is, we don't have to loop over all
the buffers seperately to identify whether a page needs to be set redirty or not.
With this change we will redirty the page in the same loop (if required)
and identify all the mapped buffers for writeback.
Moving the clearing of buffer dirty state also looks right to me.
Please feel free to add:
Reviewed-by: Ritesh Harjani (IBM) <[email protected]>
> Signed-off-by: Jan Kara <[email protected]>
> ---
> fs/ext4/page-io.c | 14 ++++++++++++--
> 1 file changed, 12 insertions(+), 2 deletions(-)
>
> diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> index 97fa7b4c645f..4e68ace86f11 100644
> --- a/fs/ext4/page-io.c
> +++ b/fs/ext4/page-io.c
> @@ -482,6 +482,13 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
> /* A hole? We can safely clear the dirty bit */
> if (!buffer_mapped(bh))
> clear_buffer_dirty(bh);
> + /*
> + * Keeping dirty some buffer we cannot write? Make
> + * sure to redirty the page. This happens e.g. when
> + * doing writeout for transaction commit.
> + */
> + if (buffer_dirty(bh) && !PageDirty(page))
> + redirty_page_for_writepage(wbc, page);
> if (io->io_bio)
> ext4_io_submit(io);
> continue;
> @@ -489,6 +496,7 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
> if (buffer_new(bh))
> clear_buffer_new(bh);
> set_buffer_async_write(bh);
> + clear_buffer_dirty(bh);
> nr_to_submit++;
> } while ((bh = bh->b_this_page) != head);
>
> @@ -532,7 +540,10 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
> printk_ratelimited(KERN_ERR "%s: ret = %d\n", __func__, ret);
> redirty_page_for_writepage(wbc, page);
> do {
> - clear_buffer_async_write(bh);
> + if (buffer_async_write(bh)) {
> + clear_buffer_async_write(bh);
> + set_buffer_dirty(bh);
> + }
> bh = bh->b_this_page;
> } while (bh != head);
> goto unlock;
> @@ -546,7 +557,6 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
> io_submit_add_bh(io, inode,
> bounce_page ? bounce_page : page, bh);
> nr_submitted++;
> - clear_buffer_dirty(bh);
> } while ((bh = bh->b_this_page) != head);
>
> unlock:
> --
> 2.35.3
>
On 22/11/30 05:35PM, Jan Kara wrote:
> nr_submitted is the same as nr_to_submit. Drop one of them.
Yup. Since we are not calling end_page_writeback() anymore.
So we don't require nr_submitted too.
Looks good to me. Please feel free to add:
Reviewed-by: Ritesh Harjani (IBM) <[email protected]>
>
> Signed-off-by: Jan Kara <[email protected]>
> ---
> fs/ext4/page-io.c | 2 --
> 1 file changed, 2 deletions(-)
>
> diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> index 4f9ecacd10aa..2bdfb8a046d9 100644
> --- a/fs/ext4/page-io.c
> +++ b/fs/ext4/page-io.c
> @@ -437,7 +437,6 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
> unsigned block_start;
> struct buffer_head *bh, *head;
> int ret = 0;
> - int nr_submitted = 0;
> int nr_to_submit = 0;
> struct writeback_control *wbc = io->io_wbc;
> bool keep_towrite = false;
> @@ -566,7 +565,6 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
> continue;
> io_submit_add_bh(io, inode,
> bounce_page ? bounce_page : page, bh);
> - nr_submitted++;
> } while ((bh = bh->b_this_page) != head);
> unlock:
> unlock_page(page);
> --
> 2.35.3
>
On Thu 01-12-22 12:36:55, Ritesh Harjani (IBM) wrote:
> On 22/11/30 05:35PM, Jan Kara wrote:
> > We submit outstanding IO in ext4_bio_write_page() if we find a buffer we
> > are not going to write. This is however pointless because we already
> > handle submission of previous IO in case we detect newly added buffer
> > head is discontiguous. So just delete the pointless IO submission call.
>
> Agreed. io_submit_add_bh() is anyway called at the end for submitting buffers.
> And io_submit_add_bh() also has the logic to:
> 1. submit a discontiguous bio
> 2. Also submit a bio if the bio gets full (submit_and_retry label).
>
> Hence calling ext4_io_submit() early is not required.
>
> I guess the same will also hold true for at this place.
> https://elixir.bootlin.com/linux/v6.1-rc7/source/fs/ext4/page-io.c#L524
So there the submission is needed because we are OOM and are going to wait
for some memory to free. If we have some bio accumulated, it is pinning
pages in writeback state and memory reclaim can be waiting on them. So if
we don't submit, it is a deadlock possibility or at least asking for
trouble.
> But this patch looks good to me. Feel free to add:
> Reviewed-by: Ritesh Harjani (IBM) <[email protected]>
Thanks for review!
Honza
>
>
>
> >
> > Signed-off-by: Jan Kara <[email protected]>
> > ---
> > fs/ext4/page-io.c | 2 --
> > 1 file changed, 2 deletions(-)
> >
> > diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> > index 2bdfb8a046d9..beaec6d81074 100644
> > --- a/fs/ext4/page-io.c
> > +++ b/fs/ext4/page-io.c
> > @@ -489,8 +489,6 @@ int ext4_bio_write_page(struct ext4_io_submit *io,
> > redirty_page_for_writepage(wbc, page);
> > keep_towrite = true;
> > }
> > - if (io->io_bio)
> > - ext4_io_submit(io);
> > continue;
> > }
> > if (buffer_new(bh))
> > --
> > 2.35.3
> >
--
Jan Kara <[email protected]>
SUSE Labs, CR
On 22/11/30 05:35PM, Jan Kara wrote:
> Move protection by percpu_rwsem from ext4_do_writepages() to
> ext4_writepages(). We will not want to grab this protection during
> transaction commits as that would be prone to deadlocks and the
> protection is not needed. Move the shutdown state checking as well since
> we want to be able to complete commit while the shutdown is in progress.
Yup. Sounds good. Please feel free to add:
Reviewed-by: Ritesh Harjani (IBM) <[email protected]>
>
> Signed-off-by: Jan Kara <[email protected]>
> ---
> fs/ext4/inode.c | 16 ++++++++++------
> 1 file changed, 10 insertions(+), 6 deletions(-)
>
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index fbea77ab470f..00c4d12f8270 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -2718,10 +2718,6 @@ static int ext4_do_writepages(struct mpage_da_data *mpd)
> struct blk_plug plug;
> bool give_up_on_write = false;
>
> - if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
> - return -EIO;
> -
> - percpu_down_read(&sbi->s_writepages_rwsem);
> trace_ext4_writepages(inode, wbc);
>
> /*
> @@ -2930,20 +2926,28 @@ static int ext4_do_writepages(struct mpage_da_data *mpd)
> out_writepages:
> trace_ext4_writepages_result(inode, wbc, ret,
> nr_to_write - wbc->nr_to_write);
> - percpu_up_read(&sbi->s_writepages_rwsem);
> return ret;
> }
>
> static int ext4_writepages(struct address_space *mapping,
> struct writeback_control *wbc)
> {
> + struct super_block *sb = mapping->host->i_sb;
> struct mpage_da_data mpd = {
> .inode = mapping->host,
> .wbc = wbc,
> .can_map = 1,
> };
> + int ret;
> +
> + if (unlikely(ext4_forced_shutdown(EXT4_SB(sb))))
> + return -EIO;
>
> - return ext4_do_writepages(&mpd);
> + percpu_down_read(&EXT4_SB(sb)->s_writepages_rwsem);
> + ret = ext4_do_writepages(&mpd);
> + percpu_up_read(&EXT4_SB(sb)->s_writepages_rwsem);
> +
> + return ret;
> }
>
> static int ext4_dax_writepages(struct address_space *mapping,
> --
> 2.35.3
>
On 22/11/30 05:36PM, Jan Kara wrote:
> ext4_writepage() should not be called for ordered data anymore. Remove
> support for it from the function.
>
> Signed-off-by: Jan Kara <[email protected]>
> ---
> fs/ext4/inode.c | 116 ++++++------------------------------------------
> 1 file changed, 13 insertions(+), 103 deletions(-)
>
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index c131b611dabf..0c8e700265f1 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -1642,12 +1642,6 @@ static void ext4_print_free_blocks(struct inode *inode)
> return;
> }
>
> -static int ext4_bh_delay_or_unwritten(handle_t *handle, struct inode *inode,
> - struct buffer_head *bh)
> -{
> - return (buffer_delay(bh) || buffer_unwritten(bh)) && buffer_dirty(bh);
> -}
> -
> /*
> * ext4_insert_delayed_block - adds a delayed block to the extents status
> * tree, incrementing the reserved cluster/block
> @@ -1962,56 +1956,17 @@ static int __ext4_journalled_writepage(struct page *page,
> }
>
> /*
> - * Note that we don't need to start a transaction unless we're journaling data
> - * because we should have holes filled from ext4_page_mkwrite(). We even don't
> - * need to file the inode to the transaction's list in ordered mode because if
> - * we are writing back data added by write(), the inode is already there and if
> - * we are writing back data modified via mmap(), no one guarantees in which
> - * transaction the data will hit the disk. In case we are journaling data, we
> - * cannot start transaction directly because transaction start ranks above page
> - * lock so we have to do some magic.
> - *
> - * This function can get called via...
> - * - ext4_writepages after taking page lock (have journal handle)
> - * - journal_submit_inode_data_buffers (no journal handle)
> - * - shrink_page_list via the kswapd/direct reclaim (no journal handle)
> - * - grab_page_cache when doing write_begin (have journal handle)
> - *
> - * We don't do any block allocation in this function. If we have page with
> - * multiple blocks we need to write those buffer_heads that are mapped. This
> - * is important for mmaped based write. So if we do with blocksize 1K
> - * truncate(f, 1024);
> - * a = mmap(f, 0, 4096);
> - * a[0] = 'a';
> - * truncate(f, 4096);
> - * we have in the page first buffer_head mapped via page_mkwrite call back
> - * but other buffer_heads would be unmapped but dirty (dirty done via the
> - * do_wp_page). So writepage should write the first block. If we modify
> - * the mmap area beyond 1024 we will again get a page_fault and the
> - * page_mkwrite callback will do the block allocation and mark the
> - * buffer_heads mapped.
> - *
> - * We redirty the page if we have any buffer_heads that is either delay or
> - * unwritten in the page.
> - *
> - * We can get recursively called as show below.
> - *
> - * ext4_writepage() -> kmalloc() -> __alloc_pages() -> page_launder() ->
> - * ext4_writepage()
> - *
> - * But since we don't do any block allocation we should not deadlock.
> - * Page also have the dirty flag cleared so we don't get recurive page_lock.
> + * This function is now used only when journaling data. We cannot start
> + * transaction directly because transaction start ranks above page lock so we
> + * have to do some magic.
> */
> static int ext4_writepage(struct page *page,
> struct writeback_control *wbc)
> {
> struct folio *folio = page_folio(page);
> - int ret = 0;
> loff_t size;
> unsigned int len;
> - struct buffer_head *page_bufs = NULL;
> struct inode *inode = page->mapping->host;
> - struct ext4_io_submit io_submit;
>
> if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb)))) {
> folio_invalidate(folio, 0, folio_size(folio));
> @@ -2036,60 +1991,16 @@ static int ext4_writepage(struct page *page,
> return 0;
> }
>
> - page_bufs = page_buffers(page);
> - /*
> - * We cannot do block allocation or other extent handling in this
> - * function. If there are buffers needing that, we have to redirty
> - * the page. But we may reach here when we do a journal commit via
> - * journal_submit_inode_data_buffers() and in that case we must write
> - * allocated buffers to achieve data=ordered mode guarantees.
Maybe this description can go above mpage_prepare_extent_to_map
for can_map = 0 case.
Looks good to me. Feel free to add:
Reviewed-by: Ritesh Harjani (IBM) <[email protected]>
> - *
> - * Also, if there is only one buffer per page (the fs block
> - * size == the page size), if one buffer needs block
> - * allocation or needs to modify the extent tree to clear the
> - * unwritten flag, we know that the page can't be written at
> - * all, so we might as well refuse the write immediately.
> - * Unfortunately if the block size != page size, we can't as
> - * easily detect this case using ext4_walk_page_buffers(), but
> - * for the extremely common case, this is an optimization that
> - * skips a useless round trip through ext4_bio_write_page().
> - */
> - if (ext4_walk_page_buffers(NULL, inode, page_bufs, 0, len, NULL,
> - ext4_bh_delay_or_unwritten)) {
> - redirty_page_for_writepage(wbc, page);
> - if ((current->flags & PF_MEMALLOC) ||
> - (inode->i_sb->s_blocksize == PAGE_SIZE)) {
> - /*
> - * For memory cleaning there's no point in writing only
> - * some buffers. So just bail out. Warn if we came here
> - * from direct reclaim.
> - */
> - WARN_ON_ONCE((current->flags & (PF_MEMALLOC|PF_KSWAPD))
> - == PF_MEMALLOC);
> - unlock_page(page);
> - return 0;
> - }
> - }
> -
> - if (PageChecked(page) && ext4_should_journal_data(inode))
> - /*
> - * It's mmapped pagecache. Add buffers and journal it. There
> - * doesn't seem much point in redirtying the page here.
> - */
> - return __ext4_journalled_writepage(page, len);
> -
> - ext4_io_submit_init(&io_submit, wbc);
> - io_submit.io_end = ext4_init_io_end(inode, GFP_NOFS);
> - if (!io_submit.io_end) {
> - redirty_page_for_writepage(wbc, page);
> + WARN_ON_ONCE(!ext4_should_journal_data(inode));
> + if (!PageChecked(page)) {
> unlock_page(page);
> - return -ENOMEM;
> + return 0;
> }
> - ret = ext4_bio_write_page(&io_submit, page, len);
> - ext4_io_submit(&io_submit);
> - /* Drop io_end reference we got from init */
> - ext4_put_io_end_defer(io_submit.io_end);
> - return ret;
> + /*
> + * It's mmapped pagecache. Add buffers and journal it. There
> + * doesn't seem much point in redirtying the page here.
> + */
> + return __ext4_journalled_writepage(page, len);
> }
>
> static int mpage_submit_page(struct mpage_da_data *mpd, struct page *page)
> @@ -3142,9 +3053,8 @@ static int ext4_da_write_end(struct file *file,
> * i_disksize since writeback will push i_disksize upto i_size
> * eventually. If the end of the current write is > i_size and
> * inside an allocated block (ext4_da_should_update_i_disksize()
> - * check), we need to update i_disksize here as neither
> - * ext4_writepage() nor certain ext4_writepages() paths not
> - * allocating blocks update i_disksize.
> + * check), we need to update i_disksize here as ext4_writepages() need
> + * not do it in this case.
> *
> * Note that we defer inode dirtying to generic_write_end() /
> * ext4_da_write_inline_data_end().
> --
> 2.35.3
>
On 22/11/30 05:35PM, Jan Kara wrote:
> Hello,
>
> this patch series modifies ext4 so that we stop using ext4_writepage() for
> writeout of ordered data during transaction commit (through
> generic_writepages() from jbd2_journal_submit_inode_data_buffers()). Instead we
> directly call ext4_writepages() from the
> ext4_journal_submit_inode_data_buffers().
Hello Jan,
Do you think we should add a WARN_ON_ONCE() or something in
ext4_do_writepages() function where we might try to start a transaction
at [J]. Since we can now enter into ext4_do_writepages() from two places:
1. writeback
2. jbd2_journal_commit_transaction()
mpage_submit_page
mpage_prepare_extent_to_map
ext4_do_writepages
ext4_normal_submit_inode_data_buffers
ext4_journal_submit_inode_data_buffers
journal_submit_data_buffers
jbd2_journal_commit_transaction
kjournald2
So IIUC, we will call mpage_submit_page() in the first call to
mpage_prepare_extent_to_map() [1] itself. That may set mpd->scanned_until_end = 1
at the end of it. So then we should never enter into the while loop where we
start a journal txn.
But can it ever happen that we ever into this while loop for writing out
pages for journal_submit_data_buffers()?
ext4_do_writepages()
{
<...>
mpd->do_map = 0;
mpd->scanned_until_end = 0;
mpd->io_submit.io_end = ext4_init_io_end(inode, GFP_KERNEL);
<...>
[1] ret = mpage_prepare_extent_to_map(mpd);
/* Unlock pages we didn't use */
mpage_release_unused_pages(mpd, false);
/* Submit prepared bio */
ext4_io_submit(&mpd->io_submit);
ext4_put_io_end_defer(mpd->io_submit.io_end);
mpd->io_submit.io_end = NULL;
<...>
while (!mpd->scanned_until_end && wbc->nr_to_write > 0) {
/* For each extent of pages we use new io_end */
mpd->io_submit.io_end = ext4_init_io_end(inode, GFP_KERNEL);
<....>
BUG_ON(ext4_should_journal_data(inode));
needed_blocks = ext4_da_writepages_trans_blocks(inode);
/* start a new transaction */
[J] handle = ext4_journal_start_with_reserve(inode,
EXT4_HT_WRITE_PAGE, needed_blocks, rsv_blocks);
<...>
mpd->do_map = 1;
trace_ext4_da_write_pages(inode, mpd->first_page, wbc);
[2] ret = mpage_prepare_extent_to_map(mpd);
if (!ret && mpd->map.m_len)
ret = mpage_map_and_submit_extent(handle, mpd,
&give_up_on_write);
<...>
}
<...>
}
-ritesh
On Thu 01-12-22 17:12:05, Ritesh Harjani (IBM) wrote:
> On 22/11/30 05:35PM, Jan Kara wrote:
> > Hello,
> >
> > this patch series modifies ext4 so that we stop using ext4_writepage() for
> > writeout of ordered data during transaction commit (through
> > generic_writepages() from jbd2_journal_submit_inode_data_buffers()). Instead we
> > directly call ext4_writepages() from the
> > ext4_journal_submit_inode_data_buffers().
>
> Hello Jan,
>
> Do you think we should add a WARN_ON_ONCE() or something in
> ext4_do_writepages() function where we might try to start a transaction
> at [J]. Since we can now enter into ext4_do_writepages() from two places:
> 1. writeback
> 2. jbd2_journal_commit_transaction()
> mpage_submit_page
> mpage_prepare_extent_to_map
> ext4_do_writepages
> ext4_normal_submit_inode_data_buffers
> ext4_journal_submit_inode_data_buffers
> journal_submit_data_buffers
> jbd2_journal_commit_transaction
> kjournald2
>
> So IIUC, we will call mpage_submit_page() in the first call to
> mpage_prepare_extent_to_map() [1] itself. That may set mpd->scanned_until_end = 1
> at the end of it. So then we should never enter into the while loop where we
> start a journal txn.
Yes, correct. But I agree a WARN_ON_ONCE in the loop might be useful just
in case. I'll add it. Thanks for the idea.
Honza
--
Jan Kara <[email protected]>
SUSE Labs, CR
On 22/12/01 04:51PM, Ritesh Harjani (IBM) wrote:
> On 22/11/30 05:36PM, Jan Kara wrote:
> > ext4_writepage() should not be called for ordered data anymore. Remove
> > support for it from the function.
> >
> > Signed-off-by: Jan Kara <[email protected]>
> > ---
> > fs/ext4/inode.c | 116 ++++++------------------------------------------
> > 1 file changed, 13 insertions(+), 103 deletions(-)
> >
> > diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> > index c131b611dabf..0c8e700265f1 100644
> > --- a/fs/ext4/inode.c
> > +++ b/fs/ext4/inode.c
> > @@ -1642,12 +1642,6 @@ static void ext4_print_free_blocks(struct inode *inode)
> > return;
> > }
> >
> > -static int ext4_bh_delay_or_unwritten(handle_t *handle, struct inode *inode,
> > - struct buffer_head *bh)
> > -{
> > - return (buffer_delay(bh) || buffer_unwritten(bh)) && buffer_dirty(bh);
> > -}
> > -
> > /*
> > * ext4_insert_delayed_block - adds a delayed block to the extents status
> > * tree, incrementing the reserved cluster/block
> > @@ -1962,56 +1956,17 @@ static int __ext4_journalled_writepage(struct page *page,
> > }
> >
> > /*
> > - * Note that we don't need to start a transaction unless we're journaling data
> > - * because we should have holes filled from ext4_page_mkwrite(). We even don't
> > - * need to file the inode to the transaction's list in ordered mode because if
> > - * we are writing back data added by write(), the inode is already there and if
> > - * we are writing back data modified via mmap(), no one guarantees in which
> > - * transaction the data will hit the disk. In case we are journaling data, we
> > - * cannot start transaction directly because transaction start ranks above page
> > - * lock so we have to do some magic.
> > - *
> > - * This function can get called via...
> > - * - ext4_writepages after taking page lock (have journal handle)
> > - * - journal_submit_inode_data_buffers (no journal handle)
> > - * - shrink_page_list via the kswapd/direct reclaim (no journal handle)
> > - * - grab_page_cache when doing write_begin (have journal handle)
> > - *
> > - * We don't do any block allocation in this function. If we have page with
> > - * multiple blocks we need to write those buffer_heads that are mapped. This
> > - * is important for mmaped based write. So if we do with blocksize 1K
> > - * truncate(f, 1024);
> > - * a = mmap(f, 0, 4096);
> > - * a[0] = 'a';
> > - * truncate(f, 4096);
> > - * we have in the page first buffer_head mapped via page_mkwrite call back
> > - * but other buffer_heads would be unmapped but dirty (dirty done via the
> > - * do_wp_page). So writepage should write the first block. If we modify
> > - * the mmap area beyond 1024 we will again get a page_fault and the
> > - * page_mkwrite callback will do the block allocation and mark the
> > - * buffer_heads mapped.
> > - *
> > - * We redirty the page if we have any buffer_heads that is either delay or
> > - * unwritten in the page.
> > - *
> > - * We can get recursively called as show below.
> > - *
> > - * ext4_writepage() -> kmalloc() -> __alloc_pages() -> page_launder() ->
> > - * ext4_writepage()
> > - *
> > - * But since we don't do any block allocation we should not deadlock.
> > - * Page also have the dirty flag cleared so we don't get recurive page_lock.
> > + * This function is now used only when journaling data. We cannot start
> > + * transaction directly because transaction start ranks above page lock so we
> > + * have to do some magic.
> > */
> > static int ext4_writepage(struct page *page,
> > struct writeback_control *wbc)
> > {
> > struct folio *folio = page_folio(page);
> > - int ret = 0;
> > loff_t size;
> > unsigned int len;
> > - struct buffer_head *page_bufs = NULL;
> > struct inode *inode = page->mapping->host;
> > - struct ext4_io_submit io_submit;
> >
> > if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb)))) {
> > folio_invalidate(folio, 0, folio_size(folio));
> > @@ -2036,60 +1991,16 @@ static int ext4_writepage(struct page *page,
> > return 0;
> > }
> >
> > - page_bufs = page_buffers(page);
> > - /*
> > - * We cannot do block allocation or other extent handling in this
> > - * function. If there are buffers needing that, we have to redirty
> > - * the page. But we may reach here when we do a journal commit via
> > - * journal_submit_inode_data_buffers() and in that case we must write
> > - * allocated buffers to achieve data=ordered mode guarantees.
>
> Maybe this description can go above mpage_prepare_extent_to_map
> for can_map = 0 case.
>
> Looks good to me. Feel free to add:
> Reviewed-by: Ritesh Harjani (IBM) <[email protected]>
>
>
>
> > - *
> > - * Also, if there is only one buffer per page (the fs block
> > - * size == the page size), if one buffer needs block
> > - * allocation or needs to modify the extent tree to clear the
> > - * unwritten flag, we know that the page can't be written at
> > - * all, so we might as well refuse the write immediately.
> > - * Unfortunately if the block size != page size, we can't as
> > - * easily detect this case using ext4_walk_page_buffers(), but
> > - * for the extremely common case, this is an optimization that
> > - * skips a useless round trip through ext4_bio_write_page().
> > - */
> > - if (ext4_walk_page_buffers(NULL, inode, page_bufs, 0, len, NULL,
> > - ext4_bh_delay_or_unwritten)) {
> > - redirty_page_for_writepage(wbc, page);
> > - if ((current->flags & PF_MEMALLOC) ||
> > - (inode->i_sb->s_blocksize == PAGE_SIZE)) {
> > - /*
> > - * For memory cleaning there's no point in writing only
> > - * some buffers. So just bail out. Warn if we came here
> > - * from direct reclaim.
> > - */
> > - WARN_ON_ONCE((current->flags & (PF_MEMALLOC|PF_KSWAPD))
> > - == PF_MEMALLOC);
> > - unlock_page(page);
> > - return 0;
> > - }
> > - }
> > -
> > - if (PageChecked(page) && ext4_should_journal_data(inode))
> > - /*
> > - * It's mmapped pagecache. Add buffers and journal it. There
> > - * doesn't seem much point in redirtying the page here.
> > - */
> > - return __ext4_journalled_writepage(page, len);
> > -
> > - ext4_io_submit_init(&io_submit, wbc);
> > - io_submit.io_end = ext4_init_io_end(inode, GFP_NOFS);
> > - if (!io_submit.io_end) {
> > - redirty_page_for_writepage(wbc, page);
> > + WARN_ON_ONCE(!ext4_should_journal_data(inode));
Oh and one more thing, this will give a WARN_ON_ONCE(), until we change the pageout()
function from reclaim path to not call ->writepage() method.
This until then might cause random fstest to fail for sometime if it observes a
kernel warning message while the test was running.
[ 5081.820019] WARNING: CPU: 3 PID: 125 at fs/ext4/inode.c:1994 ext4_writepage+0x380/0xb80
[ 5081.822884] Modules linked in:
[ 5081.824487] CPU: 3 PID: 125 Comm: kswapd0 Not tainted 6.1.0-rc4-00054-g969d94a2d4d6 #101
[ 5081.825559] Hardware name: IBM pSeries (emulated by qemu) POWER9 (raw) 0x4e1200 0xf000005 of:SLOF,git-6b6c16 pSeries
[ 5081.826743] NIP: c00000000077a2c0 LR: c00000000077a2b4 CTR: c000000000779f40
[ 5081.827547] REGS: c0000000073d72d0 TRAP: 0700 Not tainted (6.1.0-rc4-00054-g969d94a2d4d6)
<...>
[ 5081.862838] NIP [c00000000077a2c0] ext4_writepage+0x380/0xb80
[ 5081.864963] LR [c00000000077a2b4] ext4_writepage+0x374/0xb80
[ 5081.865995] Call Trace:
[ 5081.866510] ext4_writepage+0x190/0xb80 (unreliable)
[ 5081.867438] pageout+0x1b0/0x550
[ 5081.868110] shrink_folio_list+0xb48/0x1400
[ 5081.868803] shrink_inactive_list+0x2ec/0x6b0
[ 5081.869504] shrink_lruvec+0x6f0/0x7b0
[ 5081.870160] shrink_node+0x5e4/0x980
[ 5081.870801] balance_pgdat+0x4cc/0x910
[ 5081.871453] kswapd+0x6e4/0x820
[ 5081.872062] kthread+0x168/0x170
[ 5081.872691] ret_from_kernel_thread+0x5c/0x64
-ritesh
> > + if (!PageChecked(page)) {
> > unlock_page(page);
> > - return -ENOMEM;
> > + return 0;
> > }
> > - ret = ext4_bio_write_page(&io_submit, page, len);
> > - ext4_io_submit(&io_submit);
> > - /* Drop io_end reference we got from init */
> > - ext4_put_io_end_defer(io_submit.io_end);
> > - return ret;
> > + /*
> > + * It's mmapped pagecache. Add buffers and journal it. There
> > + * doesn't seem much point in redirtying the page here.
> > + */
> > + return __ext4_journalled_writepage(page, len);
> > }
> >
> > static int mpage_submit_page(struct mpage_da_data *mpd, struct page *page)
> > @@ -3142,9 +3053,8 @@ static int ext4_da_write_end(struct file *file,
> > * i_disksize since writeback will push i_disksize upto i_size
> > * eventually. If the end of the current write is > i_size and
> > * inside an allocated block (ext4_da_should_update_i_disksize()
> > - * check), we need to update i_disksize here as neither
> > - * ext4_writepage() nor certain ext4_writepages() paths not
> > - * allocating blocks update i_disksize.
> > + * check), we need to update i_disksize here as ext4_writepages() need
> > + * not do it in this case.
> > *
> > * Note that we defer inode dirtying to generic_write_end() /
> > * ext4_da_write_inline_data_end().
> > --
> > 2.35.3
> >
On 22/12/01 11:35AM, Jan Kara wrote:
> On Thu 01-12-22 12:36:55, Ritesh Harjani (IBM) wrote:
> > On 22/11/30 05:35PM, Jan Kara wrote:
> > > We submit outstanding IO in ext4_bio_write_page() if we find a buffer we
> > > are not going to write. This is however pointless because we already
> > > handle submission of previous IO in case we detect newly added buffer
> > > head is discontiguous. So just delete the pointless IO submission call.
> >
> > Agreed. io_submit_add_bh() is anyway called at the end for submitting buffers.
> > And io_submit_add_bh() also has the logic to:
> > 1. submit a discontiguous bio
> > 2. Also submit a bio if the bio gets full (submit_and_retry label).
> >
> > Hence calling ext4_io_submit() early is not required.
> >
> > I guess the same will also hold true for at this place.
> > https://elixir.bootlin.com/linux/v6.1-rc7/source/fs/ext4/page-io.c#L524
>
> So there the submission is needed because we are OOM and are going to wait
> for some memory to free. If we have some bio accumulated, it is pinning
> pages in writeback state and memory reclaim can be waiting on them. So if
> we don't submit, it is a deadlock possibility or at least asking for
> trouble.
Aah! right. I didn't see the ret == -ENOMEM compare there.
Thanks!
-ritesh
On Thu 01-12-22 19:06:19, Ritesh Harjani (IBM) wrote:
> On 22/12/01 04:51PM, Ritesh Harjani (IBM) wrote:
> > On 22/11/30 05:36PM, Jan Kara wrote:
> > > - ext4_io_submit_init(&io_submit, wbc);
> > > - io_submit.io_end = ext4_init_io_end(inode, GFP_NOFS);
> > > - if (!io_submit.io_end) {
> > > - redirty_page_for_writepage(wbc, page);
> > > + WARN_ON_ONCE(!ext4_should_journal_data(inode));
>
> Oh and one more thing, this will give a WARN_ON_ONCE(), until we change the pageout()
> function from reclaim path to not call ->writepage() method.
> This until then might cause random fstest to fail for sometime if it observes a
> kernel warning message while the test was running.
>
> [ 5081.820019] WARNING: CPU: 3 PID: 125 at fs/ext4/inode.c:1994 ext4_writepage+0x380/0xb80
> [ 5081.822884] Modules linked in:
> [ 5081.824487] CPU: 3 PID: 125 Comm: kswapd0 Not tainted 6.1.0-rc4-00054-g969d94a2d4d6 #101
> [ 5081.825559] Hardware name: IBM pSeries (emulated by qemu) POWER9 (raw) 0x4e1200 0xf000005 of:SLOF,git-6b6c16 pSeries
> [ 5081.826743] NIP: c00000000077a2c0 LR: c00000000077a2b4 CTR: c000000000779f40
> [ 5081.827547] REGS: c0000000073d72d0 TRAP: 0700 Not tainted (6.1.0-rc4-00054-g969d94a2d4d6)
> <...>
> [ 5081.862838] NIP [c00000000077a2c0] ext4_writepage+0x380/0xb80
> [ 5081.864963] LR [c00000000077a2b4] ext4_writepage+0x374/0xb80
> [ 5081.865995] Call Trace:
> [ 5081.866510] ext4_writepage+0x190/0xb80 (unreliable)
> [ 5081.867438] pageout+0x1b0/0x550
> [ 5081.868110] shrink_folio_list+0xb48/0x1400
> [ 5081.868803] shrink_inactive_list+0x2ec/0x6b0
> [ 5081.869504] shrink_lruvec+0x6f0/0x7b0
> [ 5081.870160] shrink_node+0x5e4/0x980
> [ 5081.870801] balance_pgdat+0x4cc/0x910
> [ 5081.871453] kswapd+0x6e4/0x820
> [ 5081.872062] kthread+0x168/0x170
> [ 5081.872691] ret_from_kernel_thread+0x5c/0x64
Hum, right. It didn't trigger for me :). I'll see how to fix this.
Honza
--
Jan Kara <[email protected]>
SUSE Labs, CR
On Thu, Dec 01, 2022 at 03:11:44PM +0100, Jan Kara wrote:
> Hum, right. It didn't trigger for me :). I'll see how to fix this.
Just stop wiring up ->writepage (except for journalled data mode).