2024-01-25 12:20:35

by Christoph Hellwig

[permalink] [raw]
Subject: Convert write_cache_pages() to an iterator v5

Hi all,

this is an evolution of the series Matthew Wilcox originally sent in June
2023. Based on comments from Jan and Brian this now actually untangles
some of the more confusing conditional in the writeback code before
refactoring it into the iterator.

This version also adds a new RFC patch at the end that totally changes
the API again to a while loop, based on commnts from Jan and some of
my idea. Let me know if that is a good idea or not, and if yes I'll
fold the changes into the earlier patches.

Changes since v4:
- added back the (rebased) iomap conversion now that the conflict is in
mainline
- add a new patch to change the iterator

Changes since v3:
- various commit log spelling fixes
- remove a statement from a commit log that isn't true any more with the
changes in v3
- rename a function
- merge two helpers

Diffstat:
fs/iomap/buffered-io.c | 10 -
include/linux/pagevec.h | 18 ++
include/linux/writeback.h | 12 +
mm/page-writeback.c | 344 ++++++++++++++++++++++++++--------------------
4 files changed, 231 insertions(+), 153 deletions(-)


2024-01-25 13:01:27

by Christoph Hellwig

[permalink] [raw]
Subject: [PATCH 15/19] writeback: Add for_each_writeback_folio()

From: "Matthew Wilcox (Oracle)" <[email protected]>

Wrap up the iterator with a nice bit of syntactic sugar. Now the
caller doesn't need to know about wbc->err and can just return error,
not knowing that the iterator took care of storing errors correctly.

Signed-off-by: Matthew Wilcox (Oracle) <[email protected]>
Signed-off-by: Christoph Hellwig <[email protected]>
Reviewed-by: Jan Kara <[email protected]>
Acked-by: Dave Chinner <[email protected]>
---
include/linux/writeback.h | 10 ++++++++++
mm/page-writeback.c | 8 +++-----
2 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/include/linux/writeback.h b/include/linux/writeback.h
index a091817a5dba55..2416da933440e2 100644
--- a/include/linux/writeback.h
+++ b/include/linux/writeback.h
@@ -367,6 +367,16 @@ int balance_dirty_pages_ratelimited_flags(struct address_space *mapping,

bool wb_over_bg_thresh(struct bdi_writeback *wb);

+struct folio *writeback_iter_init(struct address_space *mapping,
+ struct writeback_control *wbc);
+struct folio *writeback_iter_next(struct address_space *mapping,
+ struct writeback_control *wbc, struct folio *folio, int error);
+
+#define for_each_writeback_folio(mapping, wbc, folio, error) \
+ for (folio = writeback_iter_init(mapping, wbc); \
+ folio || ((error = wbc->err), false); \
+ folio = writeback_iter_next(mapping, wbc, folio, error))
+
typedef int (*writepage_t)(struct folio *folio, struct writeback_control *wbc,
void *data);

diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index d5815237fbec29..aca0f43021a20c 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -2458,7 +2458,7 @@ static struct folio *writeback_get_folio(struct address_space *mapping,
return folio;
}

-static struct folio *writeback_iter_init(struct address_space *mapping,
+struct folio *writeback_iter_init(struct address_space *mapping,
struct writeback_control *wbc)
{
if (wbc->range_cyclic)
@@ -2474,7 +2474,7 @@ static struct folio *writeback_iter_init(struct address_space *mapping,
return writeback_get_folio(mapping, wbc);
}

-static struct folio *writeback_iter_next(struct address_space *mapping,
+struct folio *writeback_iter_next(struct address_space *mapping,
struct writeback_control *wbc, struct folio *folio, int error)
{
unsigned long nr = folio_nr_pages(folio);
@@ -2552,9 +2552,7 @@ int write_cache_pages(struct address_space *mapping,
struct folio *folio;
int error;

- for (folio = writeback_iter_init(mapping, wbc);
- folio;
- folio = writeback_iter_next(mapping, wbc, folio, error))
+ for_each_writeback_folio(mapping, wbc, folio, error)
error = writepage(folio, wbc, data);

return wbc->err;
--
2.39.2


2024-01-25 13:02:37

by Christoph Hellwig

[permalink] [raw]
Subject: [PATCH 11/19] writeback: Use the folio_batch queue iterator

From: "Matthew Wilcox (Oracle)" <[email protected]>

Instead of keeping our own local iterator variable, use the one just
added to folio_batch.

Signed-off-by: Matthew Wilcox (Oracle) <[email protected]>
Signed-off-by: Christoph Hellwig <[email protected]>
Reviewed-by: Jan Kara <[email protected]>
Acked-by: Dave Chinner <[email protected]>
---
mm/page-writeback.c | 29 +++++++++++++++--------------
1 file changed, 15 insertions(+), 14 deletions(-)

diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index d6ac414ddce9ca..432bb42d0829d1 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -2429,13 +2429,21 @@ static bool folio_prepare_writeback(struct address_space *mapping,
return true;
}

-static void writeback_get_batch(struct address_space *mapping,
+static struct folio *writeback_get_folio(struct address_space *mapping,
struct writeback_control *wbc)
{
- folio_batch_release(&wbc->fbatch);
- cond_resched();
- filemap_get_folios_tag(mapping, &wbc->index, wbc_end(wbc),
- wbc_to_tag(wbc), &wbc->fbatch);
+ struct folio *folio;
+
+ folio = folio_batch_next(&wbc->fbatch);
+ if (!folio) {
+ folio_batch_release(&wbc->fbatch);
+ cond_resched();
+ filemap_get_folios_tag(mapping, &wbc->index, wbc_end(wbc),
+ wbc_to_tag(wbc), &wbc->fbatch);
+ folio = folio_batch_next(&wbc->fbatch);
+ }
+
+ return folio;
}

/**
@@ -2475,7 +2483,6 @@ int write_cache_pages(struct address_space *mapping,
{
int error;
pgoff_t end; /* Inclusive */
- int i = 0;

if (wbc->range_cyclic) {
wbc->index = mapping->writeback_index; /* prev offset */
@@ -2491,18 +2498,12 @@ int write_cache_pages(struct address_space *mapping,
wbc->err = 0;

for (;;) {
- struct folio *folio;
+ struct folio *folio = writeback_get_folio(mapping, wbc);
unsigned long nr;

- if (i == wbc->fbatch.nr) {
- writeback_get_batch(mapping, wbc);
- i = 0;
- }
- if (wbc->fbatch.nr == 0)
+ if (!folio)
break;

- folio = wbc->fbatch.folios[i++];
-
folio_lock(folio);
if (!folio_prepare_writeback(mapping, wbc, folio)) {
folio_unlock(folio);
--
2.39.2


2024-01-25 13:07:36

by Christoph Hellwig

[permalink] [raw]
Subject: [PATCH 14/19] writeback: Factor writeback_iter_next() out of write_cache_pages()

From: "Matthew Wilcox (Oracle)" <[email protected]>

Pull the post-processing of the writepage_t callback into a separate
function. That means changing writeback_get_next() to call
writeback_finish() when we naturally run out of folios.

Signed-off-by: Matthew Wilcox (Oracle) <[email protected]>
Signed-off-by: Christoph Hellwig <[email protected]>
Reviewed-by: Jan Kara <[email protected]>
Acked-by: Dave Chinner <[email protected]>
---
mm/page-writeback.c | 85 ++++++++++++++++++++++++---------------------
1 file changed, 45 insertions(+), 40 deletions(-)

diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index d170dab07402ce..d5815237fbec29 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -2442,8 +2442,10 @@ static struct folio *writeback_get_folio(struct address_space *mapping,
filemap_get_folios_tag(mapping, &wbc->index, wbc_end(wbc),
wbc_to_tag(wbc), &wbc->fbatch);
folio = folio_batch_next(&wbc->fbatch);
- if (!folio)
+ if (!folio) {
+ writeback_finish(mapping, wbc, 0);
return NULL;
+ }
}

folio_lock(folio);
@@ -2472,6 +2474,46 @@ static struct folio *writeback_iter_init(struct address_space *mapping,
return writeback_get_folio(mapping, wbc);
}

+static struct folio *writeback_iter_next(struct address_space *mapping,
+ struct writeback_control *wbc, struct folio *folio, int error)
+{
+ unsigned long nr = folio_nr_pages(folio);
+
+ wbc->nr_to_write -= nr;
+
+ /*
+ * Handle the legacy AOP_WRITEPAGE_ACTIVATE magic return value.
+ * Eventually all instances should just unlock the folio themselves and
+ * return 0;
+ */
+ if (error == AOP_WRITEPAGE_ACTIVATE) {
+ folio_unlock(folio);
+ error = 0;
+ }
+
+ if (error && !wbc->err)
+ wbc->err = error;
+
+ /*
+ * For integrity sync we have to keep going until we have written all
+ * the folios we tagged for writeback prior to entering the writeback
+ * loop, even if we run past wbc->nr_to_write or encounter errors.
+ * This is because the file system may still have state to clear for
+ * each folio. We'll eventually return the first error encountered.
+ *
+ * For background writeback just push done_index past this folio so that
+ * we can just restart where we left off and media errors won't choke
+ * writeout for the entire file.
+ */
+ if (wbc->sync_mode == WB_SYNC_NONE &&
+ (wbc->err || wbc->nr_to_write <= 0)) {
+ writeback_finish(mapping, wbc, folio->index + nr);
+ return NULL;
+ }
+
+ return writeback_get_folio(mapping, wbc);
+}
+
/**
* write_cache_pages - walk the list of dirty pages of the given address space and write all of them.
* @mapping: address space structure to write
@@ -2512,47 +2554,10 @@ int write_cache_pages(struct address_space *mapping,

for (folio = writeback_iter_init(mapping, wbc);
folio;
- folio = writeback_get_folio(mapping, wbc)) {
- unsigned long nr;
-
+ folio = writeback_iter_next(mapping, wbc, folio, error))
error = writepage(folio, wbc, data);
- nr = folio_nr_pages(folio);
- wbc->nr_to_write -= nr;
-
- /*
- * Handle the legacy AOP_WRITEPAGE_ACTIVATE magic return value.
- * Eventually all instances should just unlock the folio
- * themselves and return 0;
- */
- if (error == AOP_WRITEPAGE_ACTIVATE) {
- folio_unlock(folio);
- error = 0;
- }
-
- if (error && !wbc->err)
- wbc->err = error;

- /*
- * For integrity sync we have to keep going until we have
- * written all the folios we tagged for writeback prior to
- * entering this loop, even if we run past wbc->nr_to_write or
- * encounter errors. This is because the file system may still
- * have state to clear for each folio. We'll eventually return
- * the first error encountered.
- *
- * For background writeback just push done_index past this folio
- * so that we can just restart where we left off and media
- * errors won't choke writeout for the entire file.
- */
- if (wbc->sync_mode == WB_SYNC_NONE &&
- (wbc->err || wbc->nr_to_write <= 0)) {
- writeback_finish(mapping, wbc, folio->index + nr);
- return error;
- }
- }
-
- writeback_finish(mapping, wbc, 0);
- return 0;
+ return wbc->err;
}
EXPORT_SYMBOL(write_cache_pages);

--
2.39.2


2024-01-25 13:45:18

by Christoph Hellwig

[permalink] [raw]
Subject: [PATCH 03/19] writeback: rework the loop termination condition in write_cache_pages

Rework the way we deal with the cleanup after the writepage call.

First handle the magic AOP_WRITEPAGE_ACTIVATE separately from real error
returns to get it out of the way of the actual error handling path.
Then merge the code to set ret for integrity vs non-integrity writeback.
For non-integrity writeback the loop is terminated on the first error, so
ret will never be non-zero. Then use a single block to check for
non-integrity writewack to consolidate the cases where it returns for
either an error or running off the end of nr_to_write.

Signed-off-by: Christoph Hellwig <[email protected]>
Reviewed-by: Jan Kara <[email protected]>
Acked-by: Dave Chinner <[email protected]>
---
mm/page-writeback.c | 62 +++++++++++++++++++++------------------------
1 file changed, 29 insertions(+), 33 deletions(-)

diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index 0c1e9c016bc48f..259c37bc34d2a7 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -2474,43 +2474,39 @@ int write_cache_pages(struct address_space *mapping,
error = writepage(folio, wbc, data);
nr = folio_nr_pages(folio);
wbc->nr_to_write -= nr;
- if (unlikely(error)) {
- /*
- * Handle errors according to the type of
- * writeback. There's no need to continue for
- * background writeback. Just push done_index
- * past this page so media errors won't choke
- * writeout for the entire file. For integrity
- * writeback, we must process the entire dirty
- * set regardless of errors because the fs may
- * still have state to clear for each page. In
- * that case we continue processing and return
- * the first error.
- */
- if (error == AOP_WRITEPAGE_ACTIVATE) {
- folio_unlock(folio);
- error = 0;
- } else if (wbc->sync_mode != WB_SYNC_ALL) {
- ret = error;
- done_index = folio->index + nr;
- done = 1;
- break;
- }
- if (!ret)
- ret = error;
+
+ /*
+ * Handle the legacy AOP_WRITEPAGE_ACTIVATE magic return
+ * value. Eventually all instances should just unlock
+ * the folio themselves and return 0;
+ */
+ if (error == AOP_WRITEPAGE_ACTIVATE) {
+ folio_unlock(folio);
+ error = 0;
}

/*
- * We stop writing back only if we are not doing
- * integrity sync. In case of integrity sync we have to
- * keep going until we have written all the pages
- * we tagged for writeback prior to entering this loop.
+ * For integrity sync we have to keep going until we
+ * have written all the folios we tagged for writeback
+ * prior to entering this loop, even if we run past
+ * wbc->nr_to_write or encounter errors. This is
+ * because the file system may still have state to clear
+ * for each folio. We'll eventually return the first
+ * error encountered.
+ *
+ * For background writeback just push done_index past
+ * this folio so that we can just restart where we left
+ * off and media errors won't choke writeout for the
+ * entire file.
*/
- done_index = folio->index + nr;
- if (wbc->nr_to_write <= 0 &&
- wbc->sync_mode == WB_SYNC_NONE) {
- done = 1;
- break;
+ if (error && !ret)
+ ret = error;
+ if (wbc->sync_mode == WB_SYNC_NONE) {
+ if (ret || wbc->nr_to_write <= 0) {
+ done_index = folio->index + nr;
+ done = 1;
+ break;
+ }
}
}
folio_batch_release(&fbatch);
--
2.39.2


2024-01-25 15:01:50

by Christoph Hellwig

[permalink] [raw]
Subject: [PATCH 19/19] writeback: simplify writeback iteration

Based on the feedback from Jan I've tried to figure out how to
avoid the error magic in the for_each_writeback_folio. This patch
tries to implement this by switching to an open while loop over a
single writeback_iter() function:

while ((folio = writeback_iter(mapping, wbc, folio, &error))) {
...
}

the twist here is that the error value is passed by reference, so that
the iterator can restore it when breaking out of the loop.

Additionally it moves the AOP_WRITEPAGE_ACTIVATE out of the iterator
and into the callers, in preparation for eventually killing it off
with the phase out of write_cache_pages().

To me this form of the loop feels easier to follow, and it has the
added advantage that writeback_iter() can actually be nicely used in
nested loops, which should help with further iterizing the iomap
writeback code.

Signed-off-by: Christoph Hellwig <[email protected]>
---
fs/iomap/buffered-io.c | 7 +-
include/linux/writeback.h | 11 +--
mm/page-writeback.c | 174 +++++++++++++++++++++-----------------
3 files changed, 102 insertions(+), 90 deletions(-)

diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c
index 58b3661f5eac9e..1593a783176ca2 100644
--- a/fs/iomap/buffered-io.c
+++ b/fs/iomap/buffered-io.c
@@ -1985,12 +1985,13 @@ iomap_writepages(struct address_space *mapping, struct writeback_control *wbc,
struct iomap_writepage_ctx *wpc,
const struct iomap_writeback_ops *ops)
{
- struct folio *folio;
- int ret;
+ struct folio *folio = NULL;
+ int ret = 0;

wpc->ops = ops;
- for_each_writeback_folio(mapping, wbc, folio, ret)
+ while ((folio = writeback_iter(mapping, wbc, folio, &ret)))
ret = iomap_do_writepage(folio, wbc, wpc);
+
if (!wpc->ioend)
return ret;
return iomap_submit_ioend(wpc, wpc->ioend, ret);
diff --git a/include/linux/writeback.h b/include/linux/writeback.h
index 2416da933440e2..fc4605627496fc 100644
--- a/include/linux/writeback.h
+++ b/include/linux/writeback.h
@@ -367,15 +367,8 @@ int balance_dirty_pages_ratelimited_flags(struct address_space *mapping,

bool wb_over_bg_thresh(struct bdi_writeback *wb);

-struct folio *writeback_iter_init(struct address_space *mapping,
- struct writeback_control *wbc);
-struct folio *writeback_iter_next(struct address_space *mapping,
- struct writeback_control *wbc, struct folio *folio, int error);
-
-#define for_each_writeback_folio(mapping, wbc, folio, error) \
- for (folio = writeback_iter_init(mapping, wbc); \
- folio || ((error = wbc->err), false); \
- folio = writeback_iter_next(mapping, wbc, folio, error))
+struct folio *writeback_iter(struct address_space *mapping,
+ struct writeback_control *wbc, struct folio *folio, int *error);

typedef int (*writepage_t)(struct folio *folio, struct writeback_control *wbc,
void *data);
diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index 2a4b5aee5decd9..9e1cce9be63524 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -2360,29 +2360,6 @@ void tag_pages_for_writeback(struct address_space *mapping,
}
EXPORT_SYMBOL(tag_pages_for_writeback);

-static void writeback_finish(struct address_space *mapping,
- struct writeback_control *wbc, pgoff_t done_index)
-{
- folio_batch_release(&wbc->fbatch);
-
- /*
- * For range cyclic writeback we need to remember where we stopped so
- * that we can continue there next time we are called. If we hit the
- * last page and there is more work to be done, wrap back to the start
- * of the file.
- *
- * For non-cyclic writeback we always start looking up at the beginning
- * of the file if we are called again, which can only happen due to
- * -ENOMEM from the file system.
- */
- if (wbc->range_cyclic) {
- if (wbc->err || wbc->nr_to_write <= 0)
- mapping->writeback_index = done_index;
- else
- mapping->writeback_index = 0;
- }
-}
-
static xa_mark_t wbc_to_tag(struct writeback_control *wbc)
{
if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
@@ -2442,10 +2419,8 @@ static struct folio *writeback_get_folio(struct address_space *mapping,
filemap_get_folios_tag(mapping, &wbc->index, wbc_end(wbc),
wbc_to_tag(wbc), &wbc->fbatch);
folio = folio_batch_next(&wbc->fbatch);
- if (!folio) {
- writeback_finish(mapping, wbc, 0);
+ if (!folio)
return NULL;
- }
}

folio_lock(folio);
@@ -2458,60 +2433,92 @@ static struct folio *writeback_get_folio(struct address_space *mapping,
return folio;
}

-struct folio *writeback_iter_init(struct address_space *mapping,
- struct writeback_control *wbc)
-{
- if (wbc->range_cyclic)
- wbc->index = mapping->writeback_index; /* prev offset */
- else
- wbc->index = wbc->range_start >> PAGE_SHIFT;
-
- if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
- tag_pages_for_writeback(mapping, wbc->index, wbc_end(wbc));
-
- wbc->err = 0;
- folio_batch_init(&wbc->fbatch);
- return writeback_get_folio(mapping, wbc);
-}
-
-struct folio *writeback_iter_next(struct address_space *mapping,
- struct writeback_control *wbc, struct folio *folio, int error)
+/**
+ * writepage_iter - iterate folio of a mapping for writeback
+ * @mapping: address space structure to write
+ * @wbc: writeback context
+ * @folio: previously iterated folio (%NULL to start)
+ * @error: in-out pointer for writeback errors (see below)
+ *
+ * This function should be called in a while loop in the ->writepages
+ * implementation and returns the next folio for the writeback operation
+ * described by @wbc on @mapping.
+ *
+ * To start writeback @folio should be passed as NULL, for every following
+ * iteration the folio returned by this function previously should be passed.
+ * @error should contain the error from the previous writeback iteration when
+ * calling writeback_iter.
+ *
+ * Once the writeback described in @wbc has finished, this function will return
+ * %NULL and if there was an error in any iteration restore it to @error.
+ *
+ * Note: callers should not manually break out of the loop using break or goto.
+ */
+struct folio *writeback_iter(struct address_space *mapping,
+ struct writeback_control *wbc, struct folio *folio, int *error)
{
- unsigned long nr = folio_nr_pages(folio);
+ if (folio) {
+ wbc->nr_to_write -= folio_nr_pages(folio);
+ if (*error && !wbc->err)
+ wbc->err = *error;

- wbc->nr_to_write -= nr;
-
- /*
- * Handle the legacy AOP_WRITEPAGE_ACTIVATE magic return value.
- * Eventually all instances should just unlock the folio themselves and
- * return 0;
- */
- if (error == AOP_WRITEPAGE_ACTIVATE) {
- folio_unlock(folio);
- error = 0;
+ /*
+ * For integrity sync we have to keep going until we have
+ * written all the folios we tagged for writeback prior to
+ * entering the writeback loop, even if we run past
+ * wbc->nr_to_write or encounter errors.
+ *
+ * This is because the file system may still have state to clear
+ * for each folio. We'll eventually return the first error
+ * encountered.
+ *
+ * For background writeback just push done_index past this folio
+ * so that we can just restart where we left off and media
+ * errors won't choke writeout for the entire file.
+ */
+ if (wbc->sync_mode == WB_SYNC_NONE &&
+ (wbc->err || wbc->nr_to_write <= 0))
+ goto finish;
+ } else {
+ if (wbc->range_cyclic)
+ wbc->index = mapping->writeback_index; /* prev offset */
+ else
+ wbc->index = wbc->range_start >> PAGE_SHIFT;
+ if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
+ tag_pages_for_writeback(mapping, wbc->index,
+ wbc_end(wbc));
+ folio_batch_init(&wbc->fbatch);
+ wbc->err = 0;
}

- if (error && !wbc->err)
- wbc->err = error;
+ folio = writeback_get_folio(mapping, wbc);
+ if (!folio)
+ goto finish;
+ return folio;
+
+finish:
+ folio_batch_release(&wbc->fbatch);

/*
- * For integrity sync we have to keep going until we have written all
- * the folios we tagged for writeback prior to entering the writeback
- * loop, even if we run past wbc->nr_to_write or encounter errors.
- * This is because the file system may still have state to clear for
- * each folio. We'll eventually return the first error encountered.
+ * For range cyclic writeback we need to remember where we stopped so
+ * that we can continue there next time we are called. If we hit the
+ * last page and there is more work to be done, wrap back to the start
+ * of the file.
*
- * For background writeback just push done_index past this folio so that
- * we can just restart where we left off and media errors won't choke
- * writeout for the entire file.
+ * For non-cyclic writeback we always start looking up at the beginning
+ * of the file if we are called again, which can only happen due to
+ * -ENOMEM from the file system.
*/
- if (wbc->sync_mode == WB_SYNC_NONE &&
- (wbc->err || wbc->nr_to_write <= 0)) {
- writeback_finish(mapping, wbc, folio->index + nr);
- return NULL;
+ if (wbc->range_cyclic) {
+ WARN_ON_ONCE(wbc->sync_mode != WB_SYNC_NONE);
+ if (wbc->err || wbc->nr_to_write <= 0)
+ mapping->writeback_index =
+ folio->index + folio_nr_pages(folio);
+ else
+ mapping->writeback_index = 0;
}
-
- return writeback_get_folio(mapping, wbc);
+ *error = wbc->err;
+ return NULL;
}

/**
@@ -2549,13 +2556,18 @@ int write_cache_pages(struct address_space *mapping,
struct writeback_control *wbc, writepage_t writepage,
void *data)
{
- struct folio *folio;
- int error;
+ struct folio *folio = NULL;
+ int error = 0;

- for_each_writeback_folio(mapping, wbc, folio, error)
+ while ((folio = writeback_iter(mapping, wbc, folio, &error))) {
error = writepage(folio, wbc, data);
+ if (error == AOP_WRITEPAGE_ACTIVATE) {
+ folio_unlock(folio);
+ error = 0;
+ }
+ }

- return wbc->err;
+ return error;
}
EXPORT_SYMBOL(write_cache_pages);

@@ -2563,13 +2575,17 @@ static int writeback_use_writepage(struct address_space *mapping,
struct writeback_control *wbc)
{
struct blk_plug plug;
- struct folio *folio;
- int err;
+ struct folio *folio = 0;
+ int err = 0;

blk_start_plug(&plug);
- for_each_writeback_folio(mapping, wbc, folio, err) {
+ while ((folio = writeback_iter(mapping, wbc, folio, &err))) {
err = mapping->a_ops->writepage(&folio->page, wbc);
mapping_set_error(mapping, err);
+ if (err == AOP_WRITEPAGE_ACTIVATE) {
+ folio_unlock(folio);
+ err = 0;
+ }
}
blk_finish_plug(&plug);

@@ -2590,6 +2606,8 @@ int do_writepages(struct address_space *mapping, struct writeback_control *wbc)
ret = mapping->a_ops->writepages(mapping, wbc);
} else if (mapping->a_ops->writepage) {
ret = writeback_use_writepage(mapping, wbc);
+ if (!ret)
+ ret = wbc->err;
} else {
/* deal with chardevs and other special files */
ret = 0;
--
2.39.2


2024-01-25 15:43:38

by Christoph Hellwig

[permalink] [raw]
Subject: [PATCH 04/19] writeback: only update ->writeback_index for range_cyclic writeback

mapping->writeback_index is only [1] used as the starting point for
range_cyclic writeback, so there is no point in updating it for other
types of writeback.

[1] except for btrfs_defrag_file which does really odd things with
mapping->writeback_index. But btrfs doesn't use write_cache_pages at
all, so this isn't relevant here.

Signed-off-by: Christoph Hellwig <[email protected]>
Reviewed-by: Jan Kara <[email protected]>
Acked-by: Dave Chinner <[email protected]>
---
mm/page-writeback.c | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index 259c37bc34d2a7..437745a511c634 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -2403,7 +2403,6 @@ int write_cache_pages(struct address_space *mapping,
pgoff_t index;
pgoff_t end; /* Inclusive */
pgoff_t done_index;
- int range_whole = 0;
xa_mark_t tag;

folio_batch_init(&fbatch);
@@ -2413,8 +2412,6 @@ int write_cache_pages(struct address_space *mapping,
} else {
index = wbc->range_start >> PAGE_SHIFT;
end = wbc->range_end >> PAGE_SHIFT;
- if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX)
- range_whole = 1;
}
if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages) {
tag_pages_for_writeback(mapping, index, end);
@@ -2514,14 +2511,21 @@ int write_cache_pages(struct address_space *mapping,
}

/*
- * If we hit the last page and there is more work to be done: wrap
- * back the index back to the start of the file for the next
- * time we are called.
+ * For range cyclic writeback we need to remember where we stopped so
+ * that we can continue there next time we are called. If we hit the
+ * last page and there is more work to be done, wrap back to the start
+ * of the file.
+ *
+ * For non-cyclic writeback we always start looking up at the beginning
+ * of the file if we are called again, which can only happen due to
+ * -ENOMEM from the file system.
*/
- if (wbc->range_cyclic && !done)
- done_index = 0;
- if (wbc->range_cyclic || (range_whole && wbc->nr_to_write > 0))
- mapping->writeback_index = done_index;
+ if (wbc->range_cyclic) {
+ if (done)
+ mapping->writeback_index = done_index;
+ else
+ mapping->writeback_index = 0;
+ }

return ret;
}
--
2.39.2


2024-01-25 16:46:29

by Christoph Hellwig

[permalink] [raw]
Subject: [PATCH 10/19] pagevec: Add ability to iterate a queue

From: "Matthew Wilcox (Oracle)" <[email protected]>

Add a loop counter inside the folio_batch to let us iterate from 0-nr
instead of decrementing nr and treating the batch as a stack. It would
generate some very weird and suboptimal I/O patterns for page writeback
to iterate over the batch as a stack.

Signed-off-by: Matthew Wilcox (Oracle) <[email protected]>
Signed-off-by: Christoph Hellwig <[email protected]>
Reviewed-by: Jan Kara <[email protected]>
Acked-by: Dave Chinner <[email protected]>
---
include/linux/pagevec.h | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)

diff --git a/include/linux/pagevec.h b/include/linux/pagevec.h
index 87cc678adc850b..fcc06c300a72c3 100644
--- a/include/linux/pagevec.h
+++ b/include/linux/pagevec.h
@@ -27,6 +27,7 @@ struct folio;
*/
struct folio_batch {
unsigned char nr;
+ unsigned char i;
bool percpu_pvec_drained;
struct folio *folios[PAGEVEC_SIZE];
};
@@ -40,12 +41,14 @@ struct folio_batch {
static inline void folio_batch_init(struct folio_batch *fbatch)
{
fbatch->nr = 0;
+ fbatch->i = 0;
fbatch->percpu_pvec_drained = false;
}

static inline void folio_batch_reinit(struct folio_batch *fbatch)
{
fbatch->nr = 0;
+ fbatch->i = 0;
}

static inline unsigned int folio_batch_count(struct folio_batch *fbatch)
@@ -75,6 +78,21 @@ static inline unsigned folio_batch_add(struct folio_batch *fbatch,
return folio_batch_space(fbatch);
}

+/**
+ * folio_batch_next - Return the next folio to process.
+ * @fbatch: The folio batch being processed.
+ *
+ * Use this function to implement a queue of folios.
+ *
+ * Return: The next folio in the queue, or NULL if the queue is empty.
+ */
+static inline struct folio *folio_batch_next(struct folio_batch *fbatch)
+{
+ if (fbatch->i == fbatch->nr)
+ return NULL;
+ return fbatch->folios[fbatch->i++];
+}
+
void __folio_batch_release(struct folio_batch *pvec);

static inline void folio_batch_release(struct folio_batch *fbatch)
--
2.39.2


2024-01-30 10:48:19

by Jan Kara

[permalink] [raw]
Subject: Re: [PATCH 19/19] writeback: simplify writeback iteration

On Thu 25-01-24 09:57:58, Christoph Hellwig wrote:
> Based on the feedback from Jan I've tried to figure out how to
> avoid the error magic in the for_each_writeback_folio. This patch
> tries to implement this by switching to an open while loop over a
> single writeback_iter() function:
>
> while ((folio = writeback_iter(mapping, wbc, folio, &error))) {
> ...
> }
>
> the twist here is that the error value is passed by reference, so that
> the iterator can restore it when breaking out of the loop.
>
> Additionally it moves the AOP_WRITEPAGE_ACTIVATE out of the iterator
> and into the callers, in preparation for eventually killing it off
> with the phase out of write_cache_pages().
>
> To me this form of the loop feels easier to follow, and it has the
> added advantage that writeback_iter() can actually be nicely used in
> nested loops, which should help with further iterizing the iomap
> writeback code.
>
> Signed-off-by: Christoph Hellwig <[email protected]>

Looking at it now I'm thinking whether we would not be better off to
completely dump the 'error' argument of writeback_iter() /
writeback_iter_next() and just make all .writepage implementations set
wbc->err directly. But that means touching all the ~20 writepage
implementations we still have...

Couple of comments regarding this implementation below (overall I agree it
seems somewhat easier to follow the code).

> +/**
> + * writepage_iter - iterate folio of a mapping for writeback
> + * @mapping: address space structure to write
> + * @wbc: writeback context
> + * @folio: previously iterated folio (%NULL to start)
> + * @error: in-out pointer for writeback errors (see below)
> + *
> + * This function should be called in a while loop in the ->writepages
> + * implementation and returns the next folio for the writeback operation
> + * described by @wbc on @mapping.
> + *
> + * To start writeback @folio should be passed as NULL, for every following
> + * iteration the folio returned by this function previously should be passed.
> + * @error should contain the error from the previous writeback iteration when
> + * calling writeback_iter.
> + *
> + * Once the writeback described in @wbc has finished, this function will return
> + * %NULL and if there was an error in any iteration restore it to @error.
> + *
> + * Note: callers should not manually break out of the loop using break or goto.
> + */
> +struct folio *writeback_iter(struct address_space *mapping,
> + struct writeback_control *wbc, struct folio *folio, int *error)
> {
> - unsigned long nr = folio_nr_pages(folio);
> + if (folio) {
> + wbc->nr_to_write -= folio_nr_pages(folio);
> + if (*error && !wbc->err)
> + wbc->err = *error;
>
> - wbc->nr_to_write -= nr;
> -
> - /*
> - * Handle the legacy AOP_WRITEPAGE_ACTIVATE magic return value.
> - * Eventually all instances should just unlock the folio themselves and
> - * return 0;
> - */
> - if (error == AOP_WRITEPAGE_ACTIVATE) {
> - folio_unlock(folio);
> - error = 0;
> + /*
> + * For integrity sync we have to keep going until we have
> + * written all the folios we tagged for writeback prior to
> + * entering the writeback loop, even if we run past
> + * wbc->nr_to_write or encounter errors.
> + *
> + * This is because the file system may still have state to clear
> + * for each folio. We'll eventually return the first error
> + * encountered.
> + *
> + * For background writeback just push done_index past this folio
> + * so that we can just restart where we left off and media
> + * errors won't choke writeout for the entire file.
> + */
> + if (wbc->sync_mode == WB_SYNC_NONE &&
> + (wbc->err || wbc->nr_to_write <= 0))
> + goto finish;

I think it would be a bit more comprehensible if we replace the goto with:
folio_batch_release(&wbc->fbatch);
if (wbc->range_cyclic)
mapping->writeback_index =
folio->index + folio_nr_pages(folio);
*error = wbc->err;
return NULL;

> + } else {
> + if (wbc->range_cyclic)
> + wbc->index = mapping->writeback_index; /* prev offset */
> + else
> + wbc->index = wbc->range_start >> PAGE_SHIFT;
> + if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
> + tag_pages_for_writeback(mapping, wbc->index,
> + wbc_end(wbc));
> + folio_batch_init(&wbc->fbatch);
> + wbc->err = 0;
> }
>
> - if (error && !wbc->err)
> - wbc->err = error;
> + folio = writeback_get_folio(mapping, wbc);
> + if (!folio)
> + goto finish;

And here we just need to do:
if (wbc->range_cyclic)
mapping->writeback_index = 0;
*error = wbc->err;
return NULL;

> + return folio;
> +
> +finish:
> + folio_batch_release(&wbc->fbatch);
>
> /*
> - * For integrity sync we have to keep going until we have written all
> - * the folios we tagged for writeback prior to entering the writeback
> - * loop, even if we run past wbc->nr_to_write or encounter errors.
> - * This is because the file system may still have state to clear for
> - * each folio. We'll eventually return the first error encountered.
> + * For range cyclic writeback we need to remember where we stopped so
> + * that we can continue there next time we are called. If we hit the
> + * last page and there is more work to be done, wrap back to the start
> + * of the file.
> *
> - * For background writeback just push done_index past this folio so that
> - * we can just restart where we left off and media errors won't choke
> - * writeout for the entire file.
> + * For non-cyclic writeback we always start looking up at the beginning
> + * of the file if we are called again, which can only happen due to
> + * -ENOMEM from the file system.
> */
> - if (wbc->sync_mode == WB_SYNC_NONE &&
> - (wbc->err || wbc->nr_to_write <= 0)) {
> - writeback_finish(mapping, wbc, folio->index + nr);
> - return NULL;
> + if (wbc->range_cyclic) {
> + WARN_ON_ONCE(wbc->sync_mode != WB_SYNC_NONE);
> + if (wbc->err || wbc->nr_to_write <= 0)
> + mapping->writeback_index =
> + folio->index + folio_nr_pages(folio);
> + else
> + mapping->writeback_index = 0;
> }
> -
> - return writeback_get_folio(mapping, wbc);
> + *error = wbc->err;
> + return NULL;
> }
>
> /**
> @@ -2563,13 +2575,17 @@ static int writeback_use_writepage(struct address_space *mapping,
> struct writeback_control *wbc)
> {
> struct blk_plug plug;
> - struct folio *folio;
> - int err;
> + struct folio *folio = 0;
^^ NULL please


> + int err = 0;
>
> blk_start_plug(&plug);
> - for_each_writeback_folio(mapping, wbc, folio, err) {
> + while ((folio = writeback_iter(mapping, wbc, folio, &err))) {
> err = mapping->a_ops->writepage(&folio->page, wbc);
> mapping_set_error(mapping, err);
> + if (err == AOP_WRITEPAGE_ACTIVATE) {
> + folio_unlock(folio);
> + err = 0;
> + }
> }
> blk_finish_plug(&plug);
>
> @@ -2590,6 +2606,8 @@ int do_writepages(struct address_space *mapping, struct writeback_control *wbc)
> ret = mapping->a_ops->writepages(mapping, wbc);
> } else if (mapping->a_ops->writepage) {
> ret = writeback_use_writepage(mapping, wbc);
> + if (!ret)
> + ret = wbc->err;

AFAICT this should not be needed as writeback_iter() made sure wbc->err is
returned when set?

Honza
--
Jan Kara <[email protected]>
SUSE Labs, CR

2024-01-30 14:16:20

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCH 19/19] writeback: simplify writeback iteration

On Tue, Jan 30, 2024 at 11:46:05AM +0100, Jan Kara wrote:
> Looking at it now I'm thinking whether we would not be better off to
> completely dump the 'error' argument of writeback_iter() /
> writeback_iter_next() and just make all .writepage implementations set
> wbc->err directly. But that means touching all the ~20 writepage
> implementations we still have...

Heh. I actually had an earlier version that looked at wbc->err in
the ->writepages callers. But it felt a bit too ugly.

> > + */
> > + if (wbc->sync_mode == WB_SYNC_NONE &&
> > + (wbc->err || wbc->nr_to_write <= 0))
> > + goto finish;
>
> I think it would be a bit more comprehensible if we replace the goto with:
> folio_batch_release(&wbc->fbatch);
> if (wbc->range_cyclic)
> mapping->writeback_index =
> folio->index + folio_nr_pages(folio);
> *error = wbc->err;
> return NULL;

I agree that keeping the logic on when to break and when to set the
writeback_index is good, but duplicating the batch release and error
assignment seems a bit suboptimal. Let me know what you think of the
alternatіve variant below.

> > + struct folio *folio = 0;
> ^^ NULL please

Fixed.

> > ret = writeback_use_writepage(mapping, wbc);
> > + if (!ret)
> > + ret = wbc->err;
>
> AFAICT this should not be needed as writeback_iter() made sure wbc->err is
> returned when set?

Heh. That's a leftover from my above mentioned different attempt at
error handling and shouldn't have stayed in.

2024-01-30 14:22:24

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCH 19/19] writeback: simplify writeback iteration

On Tue, Jan 30, 2024 at 03:16:01PM +0100, Christoph Hellwig wrote:
> On Tue, Jan 30, 2024 at 11:46:05AM +0100, Jan Kara wrote:
> > Looking at it now I'm thinking whether we would not be better off to
> > completely dump the 'error' argument of writeback_iter() /
> > writeback_iter_next() and just make all .writepage implementations set
> > wbc->err directly. But that means touching all the ~20 writepage
> > implementations we still have...
>
> Heh. I actually had an earlier version that looked at wbc->err in
> the ->writepages callers. But it felt a bit too ugly.
>
> > > + */
> > > + if (wbc->sync_mode == WB_SYNC_NONE &&
> > > + (wbc->err || wbc->nr_to_write <= 0))
> > > + goto finish;
> >
> > I think it would be a bit more comprehensible if we replace the goto with:
> > folio_batch_release(&wbc->fbatch);
> > if (wbc->range_cyclic)
> > mapping->writeback_index =
> > folio->index + folio_nr_pages(folio);
> > *error = wbc->err;
> > return NULL;
>
> I agree that keeping the logic on when to break and when to set the
> writeback_index is good, but duplicating the batch release and error
> assignment seems a bit suboptimal. Let me know what you think of the
> alternatіve variant below.

And now for real:


diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index d8fcbac2d72310..3e4aa5bfe75819 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -2475,10 +2475,23 @@ struct folio *writeback_iter(struct address_space *mapping,
* For background writeback just push done_index past this folio
* so that we can just restart where we left off and media
* errors won't choke writeout for the entire file.
+ *
+ * For range cyclic writeback we need to remember where we
+ * stopped so that we can continue there next time we are
+ * called. If we hit the last page and there is more work
+ * to be done, wrap back to the start of the file.
+ *
+ * For non-cyclic writeback we always start looking up at the
+ * beginning of the file if we are called again, which can only
+ * happen due to -ENOMEM from the file system.
*/
if (wbc->sync_mode == WB_SYNC_NONE &&
- (wbc->err || wbc->nr_to_write <= 0))
+ (wbc->err || wbc->nr_to_write <= 0)) {
+ if (wbc->range_cyclic)
+ mapping->writeback_index =
+ folio->index + folio_nr_pages(folio);
goto finish;
+ }
} else {
if (wbc->range_cyclic)
wbc->index = mapping->writeback_index; /* prev offset */
@@ -2492,31 +2505,15 @@ struct folio *writeback_iter(struct address_space *mapping,
}

folio = writeback_get_folio(mapping, wbc);
- if (!folio)
+ if (!folio) {
+ if (wbc->range_cyclic)
+ mapping->writeback_index = 0;
goto finish;
+ }
return folio;

finish:
folio_batch_release(&wbc->fbatch);
-
- /*
- * For range cyclic writeback we need to remember where we stopped so
- * that we can continue there next time we are called. If we hit the
- * last page and there is more work to be done, wrap back to the start
- * of the file.
- *
- * For non-cyclic writeback we always start looking up at the beginning
- * of the file if we are called again, which can only happen due to
- * -ENOMEM from the file system.
- */
- if (wbc->range_cyclic) {
- WARN_ON_ONCE(wbc->sync_mode != WB_SYNC_NONE);
- if (wbc->err || wbc->nr_to_write <= 0)
- mapping->writeback_index =
- folio->index + folio_nr_pages(folio);
- else
- mapping->writeback_index = 0;
- }
*error = wbc->err;
return NULL;
}

2024-01-30 14:33:51

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCH 19/19] writeback: simplify writeback iteration

On Tue, Jan 30, 2024 at 03:22:05PM +0100, Christoph Hellwig wrote:
> And now for real:

Another slight variant of this would be to move the nr_to_write || err
check for WB_SYNC_NONE out of the branch. This adds extra tests for
the initial iteration, but unindents a huge comment, and moves it closer
to the other branch that it also describes. The downside at least to
me is that the normal non-termination path is not the straight line
through function. Thoughs?

diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index 973f57ad9ee548..ff6e73453aa8c4 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -2461,24 +2461,6 @@ struct folio *writeback_iter(struct address_space *mapping,
wbc->nr_to_write -= folio_nr_pages(folio);
if (*error && !wbc->err)
wbc->err = *error;
-
- /*
- * For integrity sync we have to keep going until we have
- * written all the folios we tagged for writeback prior to
- * entering the writeback loop, even if we run past
- * wbc->nr_to_write or encounter errors.
- *
- * This is because the file system may still have state to clear
- * for each folio. We'll eventually return the first error
- * encountered.
- *
- * For background writeback just push done_index past this folio
- * so that we can just restart where we left off and media
- * errors won't choke writeout for the entire file.
- */
- if (wbc->sync_mode == WB_SYNC_NONE &&
- (wbc->err || wbc->nr_to_write <= 0))
- goto finish;
} else {
if (wbc->range_cyclic)
wbc->index = mapping->writeback_index; /* prev offset */
@@ -2491,17 +2473,20 @@ struct folio *writeback_iter(struct address_space *mapping,
wbc->err = 0;
}

- folio = writeback_get_folio(mapping, wbc);
- if (!folio)
- goto finish;
- return folio;
-
-finish:
- folio_batch_release(&wbc->fbatch);
-
/*
+ * For integrity sync we have to keep going until we have written all
+ * the folios we tagged for writeback prior to entering the writeback
+ * loop, even if we run past wbc->nr_to_write or encounter errors.
+ *
+ * This is because the file system may still have state to clear for
+ * each folio. We'll eventually return the first error encountered.
+ *
+ * For background writeback just push done_index past this folio so that
+ * we can just restart where we left off and media errors won't choke
+ * writeout for the entire file.
+ *
* For range cyclic writeback we need to remember where we stopped so
- * that we can continue there next time we are called. If we hit the
+ * that we can continue there next time we are called. If we hit the
* last page and there is more work to be done, wrap back to the start
* of the file.
*
@@ -2509,14 +2494,21 @@ struct folio *writeback_iter(struct address_space *mapping,
* of the file if we are called again, which can only happen due to
* -ENOMEM from the file system.
*/
- if (wbc->range_cyclic) {
- WARN_ON_ONCE(wbc->sync_mode != WB_SYNC_NONE);
- if (wbc->err || wbc->nr_to_write <= 0)
+ if (wbc->sync_mode == WB_SYNC_NONE &&
+ (wbc->err || wbc->nr_to_write <= 0)) {
+ if (wbc->range_cyclic)
mapping->writeback_index =
folio->index + folio_nr_pages(folio);
- else
+ } else {
+ folio = writeback_get_folio(mapping, wbc);
+ if (folio)
+ return folio;
+
+ if (wbc->range_cyclic)
mapping->writeback_index = 0;
}
+
+ folio_batch_release(&wbc->fbatch);
*error = wbc->err;
return NULL;
}

2024-01-30 21:52:26

by Jan Kara

[permalink] [raw]
Subject: Re: [PATCH 19/19] writeback: simplify writeback iteration

On Tue 30-01-24 15:16:01, Christoph Hellwig wrote:
> On Tue, Jan 30, 2024 at 11:46:05AM +0100, Jan Kara wrote:
> > Looking at it now I'm thinking whether we would not be better off to
> > completely dump the 'error' argument of writeback_iter() /
> > writeback_iter_next() and just make all .writepage implementations set
> > wbc->err directly. But that means touching all the ~20 writepage
> > implementations we still have...
>
> Heh. I actually had an earlier version that looked at wbc->err in
> the ->writepages callers. But it felt a bit too ugly.

OK.

> > > + */
> > > + if (wbc->sync_mode == WB_SYNC_NONE &&
> > > + (wbc->err || wbc->nr_to_write <= 0))
> > > + goto finish;
> >
> > I think it would be a bit more comprehensible if we replace the goto with:
> > folio_batch_release(&wbc->fbatch);
> > if (wbc->range_cyclic)
> > mapping->writeback_index =
> > folio->index + folio_nr_pages(folio);
> > *error = wbc->err;
> > return NULL;
>
> I agree that keeping the logic on when to break and when to set the
> writeback_index is good, but duplicating the batch release and error
> assignment seems a bit suboptimal. Let me know what you think of the
> alternatіve variant below.

Well, batch release needs to be only here because if writeback_get_folio()
returns NULL, the batch has been already released by it. So what would be
duplicated is only the error assignment. But I'm fine with the version in
the following email and actually somewhat prefer it compared the yet
another variant you've sent.

Honza
--
Jan Kara <[email protected]>
SUSE Labs, CR

2024-01-31 07:15:47

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCH 19/19] writeback: simplify writeback iteration

On Tue, Jan 30, 2024 at 10:50:16PM +0100, Jan Kara wrote:
> Well, batch release needs to be only here because if writeback_get_folio()
> returns NULL, the batch has been already released by it.

Indeed.

> So what would be
> duplicated is only the error assignment. But I'm fine with the version in
> the following email and actually somewhat prefer it compared the yet
> another variant you've sent.

So how about another variant, this is closer to your original suggestion.
But I've switched around the ordered of the folio or not branches
from my original patch, and completely reworked and (IMHO) improved the
comments. it replaces patch 19 instead of being incremental to be
readable:

diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c
index 58b3661f5eac9e..1593a783176ca2 100644
--- a/fs/iomap/buffered-io.c
+++ b/fs/iomap/buffered-io.c
@@ -1985,12 +1985,13 @@ iomap_writepages(struct address_space *mapping, struct writeback_control *wbc,
struct iomap_writepage_ctx *wpc,
const struct iomap_writeback_ops *ops)
{
- struct folio *folio;
- int ret;
+ struct folio *folio = NULL;
+ int ret = 0;

wpc->ops = ops;
- for_each_writeback_folio(mapping, wbc, folio, ret)
+ while ((folio = writeback_iter(mapping, wbc, folio, &ret)))
ret = iomap_do_writepage(folio, wbc, wpc);
+
if (!wpc->ioend)
return ret;
return iomap_submit_ioend(wpc, wpc->ioend, ret);
diff --git a/include/linux/writeback.h b/include/linux/writeback.h
index 2416da933440e2..fc4605627496fc 100644
--- a/include/linux/writeback.h
+++ b/include/linux/writeback.h
@@ -367,15 +367,8 @@ int balance_dirty_pages_ratelimited_flags(struct address_space *mapping,

bool wb_over_bg_thresh(struct bdi_writeback *wb);

-struct folio *writeback_iter_init(struct address_space *mapping,
- struct writeback_control *wbc);
-struct folio *writeback_iter_next(struct address_space *mapping,
- struct writeback_control *wbc, struct folio *folio, int error);
-
-#define for_each_writeback_folio(mapping, wbc, folio, error) \
- for (folio = writeback_iter_init(mapping, wbc); \
- folio || ((error = wbc->err), false); \
- folio = writeback_iter_next(mapping, wbc, folio, error))
+struct folio *writeback_iter(struct address_space *mapping,
+ struct writeback_control *wbc, struct folio *folio, int *error);

typedef int (*writepage_t)(struct folio *folio, struct writeback_control *wbc,
void *data);
diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index 0763c4353a676a..eefcb00cb7b227 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -2360,29 +2360,6 @@ void tag_pages_for_writeback(struct address_space *mapping,
}
EXPORT_SYMBOL(tag_pages_for_writeback);

-static void writeback_finish(struct address_space *mapping,
- struct writeback_control *wbc, pgoff_t done_index)
-{
- folio_batch_release(&wbc->fbatch);
-
- /*
- * For range cyclic writeback we need to remember where we stopped so
- * that we can continue there next time we are called. If we hit the
- * last page and there is more work to be done, wrap back to the start
- * of the file.
- *
- * For non-cyclic writeback we always start looking up at the beginning
- * of the file if we are called again, which can only happen due to
- * -ENOMEM from the file system.
- */
- if (wbc->range_cyclic) {
- if (wbc->err || wbc->nr_to_write <= 0)
- mapping->writeback_index = done_index;
- else
- mapping->writeback_index = 0;
- }
-}
-
static xa_mark_t wbc_to_tag(struct writeback_control *wbc)
{
if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
@@ -2442,10 +2419,8 @@ static struct folio *writeback_get_folio(struct address_space *mapping,
filemap_get_folios_tag(mapping, &wbc->index, wbc_end(wbc),
wbc_to_tag(wbc), &wbc->fbatch);
folio = folio_batch_next(&wbc->fbatch);
- if (!folio) {
- writeback_finish(mapping, wbc, 0);
+ if (!folio)
return NULL;
- }
}

folio_lock(folio);
@@ -2458,60 +2433,107 @@ static struct folio *writeback_get_folio(struct address_space *mapping,
return folio;
}

-struct folio *writeback_iter_init(struct address_space *mapping,
- struct writeback_control *wbc)
+/**
+ * writepage_iter - iterate folio of a mapping for writeback
+ * @mapping: address space structure to write
+ * @wbc: writeback context
+ * @folio: previously iterated folio (%NULL to start)
+ * @error: in-out pointer for writeback errors (see below)
+ *
+ * This function should be called in a while loop in the ->writepages
+ * implementation and returns the next folio for the writeback operation
+ * described by @wbc on @mapping.
+ *
+ * To start writeback @folio should be passed as NULL, for every following
+ * iteration the folio returned by this function previously should be passed.
+ * @error should contain the error from the previous writeback iteration when
+ * calling writeback_iter.
+ *
+ * Once the writeback described in @wbc has finished, this function will return
+ * %NULL and if there was an error in any iteration restore it to @error.
+ *
+ * Note: callers should not manually break out of the loop using break or goto.
+ */
+struct folio *writeback_iter(struct address_space *mapping,
+ struct writeback_control *wbc, struct folio *folio, int *error)
{
- if (wbc->range_cyclic)
- wbc->index = mapping->writeback_index; /* prev offset */
- else
- wbc->index = wbc->range_start >> PAGE_SHIFT;
-
- if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
- tag_pages_for_writeback(mapping, wbc->index, wbc_end(wbc));
-
- wbc->err = 0;
- folio_batch_init(&wbc->fbatch);
- return writeback_get_folio(mapping, wbc);
-}
+ if (!folio) {
+ folio_batch_init(&wbc->fbatch);
+ wbc->err = 0;

-struct folio *writeback_iter_next(struct address_space *mapping,
- struct writeback_control *wbc, struct folio *folio, int error)
-{
- unsigned long nr = folio_nr_pages(folio);
+ /*
+ * For range cyclic writeback we remember where we stopped so
+ * that we can continue where we stopped.
+ *
+ * For non-cyclic writeback we always start at the beginning of
+ * the passed in range.
+ */
+ if (wbc->range_cyclic)
+ wbc->index = mapping->writeback_index;
+ else
+ wbc->index = wbc->range_start >> PAGE_SHIFT;

- wbc->nr_to_write -= nr;
+ /*
+ * To avoid livelocks when other processes dirty new pages, we
+ * first tag pages which should be written back and only then
+ * start writing them.
+ *
+ * For data-integrity sync we have to be careful so that we do
+ * not miss some pages (e.g., because some other process has
+ * cleared the TOWRITE tag we set). The rule we follow is that
+ * TOWRITE tag can be cleared only by the process clearing the
+ * DIRTY tag (and submitting the page for I/O).
+ */
+ if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
+ tag_pages_for_writeback(mapping, wbc->index,
+ wbc_end(wbc));
+ } else {
+ wbc->nr_to_write -= folio_nr_pages(folio);

- /*
- * Handle the legacy AOP_WRITEPAGE_ACTIVATE magic return value.
- * Eventually all instances should just unlock the folio themselves and
- * return 0;
- */
- if (error == AOP_WRITEPAGE_ACTIVATE) {
- folio_unlock(folio);
- error = 0;
+ /*
+ * For integrity writeback we have to keep going until we have
+ * written all the folios we tagged for writeback prior to
+ * entering the writeback loop, even if we run past
+ * wbc->nr_to_write or encounter errors, and just stash away
+ * the first error we encounter in wbc->err so that it can
+ * be retrieved on return.
+ *
+ * This is because the file system may still have state to clear
+ * for each folio. We'll eventually return the first error
+ * encountered.
+ */
+ if (wbc->sync_mode == WB_SYNC_ALL) {
+ if (*error && !wbc->err)
+ wbc->err = *error;
+ } else {
+ if (*error || wbc->nr_to_write <= 0)
+ goto done;
+ }
}

- if (error && !wbc->err)
- wbc->err = error;
+ folio = writeback_get_folio(mapping, wbc);
+ if (!folio) {
+ /*
+ * For range cyclic writeback not finding another folios means
+ * that we are at the end of the file. In that case go back
+ * to the start of the file for the next call.
+ */
+ if (wbc->range_cyclic)
+ mapping->writeback_index = 0;

- /*
- * For integrity sync we have to keep going until we have written all
- * the folios we tagged for writeback prior to entering the writeback
- * loop, even if we run past wbc->nr_to_write or encounter errors.
- * This is because the file system may still have state to clear for
- * each folio. We'll eventually return the first error encountered.
- *
- * For background writeback just push done_index past this folio so that
- * we can just restart where we left off and media errors won't choke
- * writeout for the entire file.
- */
- if (wbc->sync_mode == WB_SYNC_NONE &&
- (wbc->err || wbc->nr_to_write <= 0)) {
- writeback_finish(mapping, wbc, folio->index + nr);
- return NULL;
+ /*
+ * Return the first error we encountered (if there was any) to
+ * the caller now that we are done.
+ */
+ *error = wbc->err;
}
+ return folio;

- return writeback_get_folio(mapping, wbc);
+done:
+ if (wbc->range_cyclic)
+ mapping->writeback_index = folio->index + folio_nr_pages(folio);
+ folio_batch_release(&wbc->fbatch);
+ return NULL;
}

/**
@@ -2549,13 +2571,18 @@ int write_cache_pages(struct address_space *mapping,
struct writeback_control *wbc, writepage_t writepage,
void *data)
{
- struct folio *folio;
- int error;
+ struct folio *folio = NULL;
+ int error = 0;

- for_each_writeback_folio(mapping, wbc, folio, error)
+ while ((folio = writeback_iter(mapping, wbc, folio, &error))) {
error = writepage(folio, wbc, data);
+ if (error == AOP_WRITEPAGE_ACTIVATE) {
+ folio_unlock(folio);
+ error = 0;
+ }
+ }

- return wbc->err;
+ return error;
}
EXPORT_SYMBOL(write_cache_pages);

@@ -2563,13 +2590,17 @@ static int writeback_use_writepage(struct address_space *mapping,
struct writeback_control *wbc)
{
struct blk_plug plug;
- struct folio *folio;
- int err;
+ struct folio *folio = NULL;
+ int err = 0;

blk_start_plug(&plug);
- for_each_writeback_folio(mapping, wbc, folio, err) {
+ while ((folio = writeback_iter(mapping, wbc, folio, &err))) {
err = mapping->a_ops->writepage(&folio->page, wbc);
mapping_set_error(mapping, err);
+ if (err == AOP_WRITEPAGE_ACTIVATE) {
+ folio_unlock(folio);
+ err = 0;
+ }
}
blk_finish_plug(&plug);


2024-01-31 10:42:10

by Jan Kara

[permalink] [raw]
Subject: Re: [PATCH 19/19] writeback: simplify writeback iteration

On Wed 31-01-24 08:14:37, Christoph Hellwig wrote:
> On Tue, Jan 30, 2024 at 10:50:16PM +0100, Jan Kara wrote:
> > Well, batch release needs to be only here because if writeback_get_folio()
> > returns NULL, the batch has been already released by it.
>
> Indeed.
>
> > So what would be
> > duplicated is only the error assignment. But I'm fine with the version in
> > the following email and actually somewhat prefer it compared the yet
> > another variant you've sent.
>
> So how about another variant, this is closer to your original suggestion.
> But I've switched around the ordered of the folio or not branches
> from my original patch, and completely reworked and (IMHO) improved the
> comments. it replaces patch 19 instead of being incremental to be
> readable:

Yes, this looks very good to me. Feel free to add:

Reviewed-by: Jan Kara <[email protected]>

Honza

>
> diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c
> index 58b3661f5eac9e..1593a783176ca2 100644
> --- a/fs/iomap/buffered-io.c
> +++ b/fs/iomap/buffered-io.c
> @@ -1985,12 +1985,13 @@ iomap_writepages(struct address_space *mapping, struct writeback_control *wbc,
> struct iomap_writepage_ctx *wpc,
> const struct iomap_writeback_ops *ops)
> {
> - struct folio *folio;
> - int ret;
> + struct folio *folio = NULL;
> + int ret = 0;
>
> wpc->ops = ops;
> - for_each_writeback_folio(mapping, wbc, folio, ret)
> + while ((folio = writeback_iter(mapping, wbc, folio, &ret)))
> ret = iomap_do_writepage(folio, wbc, wpc);
> +
> if (!wpc->ioend)
> return ret;
> return iomap_submit_ioend(wpc, wpc->ioend, ret);
> diff --git a/include/linux/writeback.h b/include/linux/writeback.h
> index 2416da933440e2..fc4605627496fc 100644
> --- a/include/linux/writeback.h
> +++ b/include/linux/writeback.h
> @@ -367,15 +367,8 @@ int balance_dirty_pages_ratelimited_flags(struct address_space *mapping,
>
> bool wb_over_bg_thresh(struct bdi_writeback *wb);
>
> -struct folio *writeback_iter_init(struct address_space *mapping,
> - struct writeback_control *wbc);
> -struct folio *writeback_iter_next(struct address_space *mapping,
> - struct writeback_control *wbc, struct folio *folio, int error);
> -
> -#define for_each_writeback_folio(mapping, wbc, folio, error) \
> - for (folio = writeback_iter_init(mapping, wbc); \
> - folio || ((error = wbc->err), false); \
> - folio = writeback_iter_next(mapping, wbc, folio, error))
> +struct folio *writeback_iter(struct address_space *mapping,
> + struct writeback_control *wbc, struct folio *folio, int *error);
>
> typedef int (*writepage_t)(struct folio *folio, struct writeback_control *wbc,
> void *data);
> diff --git a/mm/page-writeback.c b/mm/page-writeback.c
> index 0763c4353a676a..eefcb00cb7b227 100644
> --- a/mm/page-writeback.c
> +++ b/mm/page-writeback.c
> @@ -2360,29 +2360,6 @@ void tag_pages_for_writeback(struct address_space *mapping,
> }
> EXPORT_SYMBOL(tag_pages_for_writeback);
>
> -static void writeback_finish(struct address_space *mapping,
> - struct writeback_control *wbc, pgoff_t done_index)
> -{
> - folio_batch_release(&wbc->fbatch);
> -
> - /*
> - * For range cyclic writeback we need to remember where we stopped so
> - * that we can continue there next time we are called. If we hit the
> - * last page and there is more work to be done, wrap back to the start
> - * of the file.
> - *
> - * For non-cyclic writeback we always start looking up at the beginning
> - * of the file if we are called again, which can only happen due to
> - * -ENOMEM from the file system.
> - */
> - if (wbc->range_cyclic) {
> - if (wbc->err || wbc->nr_to_write <= 0)
> - mapping->writeback_index = done_index;
> - else
> - mapping->writeback_index = 0;
> - }
> -}
> -
> static xa_mark_t wbc_to_tag(struct writeback_control *wbc)
> {
> if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
> @@ -2442,10 +2419,8 @@ static struct folio *writeback_get_folio(struct address_space *mapping,
> filemap_get_folios_tag(mapping, &wbc->index, wbc_end(wbc),
> wbc_to_tag(wbc), &wbc->fbatch);
> folio = folio_batch_next(&wbc->fbatch);
> - if (!folio) {
> - writeback_finish(mapping, wbc, 0);
> + if (!folio)
> return NULL;
> - }
> }
>
> folio_lock(folio);
> @@ -2458,60 +2433,107 @@ static struct folio *writeback_get_folio(struct address_space *mapping,
> return folio;
> }
>
> -struct folio *writeback_iter_init(struct address_space *mapping,
> - struct writeback_control *wbc)
> +/**
> + * writepage_iter - iterate folio of a mapping for writeback
> + * @mapping: address space structure to write
> + * @wbc: writeback context
> + * @folio: previously iterated folio (%NULL to start)
> + * @error: in-out pointer for writeback errors (see below)
> + *
> + * This function should be called in a while loop in the ->writepages
> + * implementation and returns the next folio for the writeback operation
> + * described by @wbc on @mapping.
> + *
> + * To start writeback @folio should be passed as NULL, for every following
> + * iteration the folio returned by this function previously should be passed.
> + * @error should contain the error from the previous writeback iteration when
> + * calling writeback_iter.
> + *
> + * Once the writeback described in @wbc has finished, this function will return
> + * %NULL and if there was an error in any iteration restore it to @error.
> + *
> + * Note: callers should not manually break out of the loop using break or goto.
> + */
> +struct folio *writeback_iter(struct address_space *mapping,
> + struct writeback_control *wbc, struct folio *folio, int *error)
> {
> - if (wbc->range_cyclic)
> - wbc->index = mapping->writeback_index; /* prev offset */
> - else
> - wbc->index = wbc->range_start >> PAGE_SHIFT;
> -
> - if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
> - tag_pages_for_writeback(mapping, wbc->index, wbc_end(wbc));
> -
> - wbc->err = 0;
> - folio_batch_init(&wbc->fbatch);
> - return writeback_get_folio(mapping, wbc);
> -}
> + if (!folio) {
> + folio_batch_init(&wbc->fbatch);
> + wbc->err = 0;
>
> -struct folio *writeback_iter_next(struct address_space *mapping,
> - struct writeback_control *wbc, struct folio *folio, int error)
> -{
> - unsigned long nr = folio_nr_pages(folio);
> + /*
> + * For range cyclic writeback we remember where we stopped so
> + * that we can continue where we stopped.
> + *
> + * For non-cyclic writeback we always start at the beginning of
> + * the passed in range.
> + */
> + if (wbc->range_cyclic)
> + wbc->index = mapping->writeback_index;
> + else
> + wbc->index = wbc->range_start >> PAGE_SHIFT;
>
> - wbc->nr_to_write -= nr;
> + /*
> + * To avoid livelocks when other processes dirty new pages, we
> + * first tag pages which should be written back and only then
> + * start writing them.
> + *
> + * For data-integrity sync we have to be careful so that we do
> + * not miss some pages (e.g., because some other process has
> + * cleared the TOWRITE tag we set). The rule we follow is that
> + * TOWRITE tag can be cleared only by the process clearing the
> + * DIRTY tag (and submitting the page for I/O).
> + */
> + if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
> + tag_pages_for_writeback(mapping, wbc->index,
> + wbc_end(wbc));
> + } else {
> + wbc->nr_to_write -= folio_nr_pages(folio);
>
> - /*
> - * Handle the legacy AOP_WRITEPAGE_ACTIVATE magic return value.
> - * Eventually all instances should just unlock the folio themselves and
> - * return 0;
> - */
> - if (error == AOP_WRITEPAGE_ACTIVATE) {
> - folio_unlock(folio);
> - error = 0;
> + /*
> + * For integrity writeback we have to keep going until we have
> + * written all the folios we tagged for writeback prior to
> + * entering the writeback loop, even if we run past
> + * wbc->nr_to_write or encounter errors, and just stash away
> + * the first error we encounter in wbc->err so that it can
> + * be retrieved on return.
> + *
> + * This is because the file system may still have state to clear
> + * for each folio. We'll eventually return the first error
> + * encountered.
> + */
> + if (wbc->sync_mode == WB_SYNC_ALL) {
> + if (*error && !wbc->err)
> + wbc->err = *error;
> + } else {
> + if (*error || wbc->nr_to_write <= 0)
> + goto done;
> + }
> }
>
> - if (error && !wbc->err)
> - wbc->err = error;
> + folio = writeback_get_folio(mapping, wbc);
> + if (!folio) {
> + /*
> + * For range cyclic writeback not finding another folios means
> + * that we are at the end of the file. In that case go back
> + * to the start of the file for the next call.
> + */
> + if (wbc->range_cyclic)
> + mapping->writeback_index = 0;
>
> - /*
> - * For integrity sync we have to keep going until we have written all
> - * the folios we tagged for writeback prior to entering the writeback
> - * loop, even if we run past wbc->nr_to_write or encounter errors.
> - * This is because the file system may still have state to clear for
> - * each folio. We'll eventually return the first error encountered.
> - *
> - * For background writeback just push done_index past this folio so that
> - * we can just restart where we left off and media errors won't choke
> - * writeout for the entire file.
> - */
> - if (wbc->sync_mode == WB_SYNC_NONE &&
> - (wbc->err || wbc->nr_to_write <= 0)) {
> - writeback_finish(mapping, wbc, folio->index + nr);
> - return NULL;
> + /*
> + * Return the first error we encountered (if there was any) to
> + * the caller now that we are done.
> + */
> + *error = wbc->err;
> }
> + return folio;
>
> - return writeback_get_folio(mapping, wbc);
> +done:
> + if (wbc->range_cyclic)
> + mapping->writeback_index = folio->index + folio_nr_pages(folio);
> + folio_batch_release(&wbc->fbatch);
> + return NULL;
> }
>
> /**
> @@ -2549,13 +2571,18 @@ int write_cache_pages(struct address_space *mapping,
> struct writeback_control *wbc, writepage_t writepage,
> void *data)
> {
> - struct folio *folio;
> - int error;
> + struct folio *folio = NULL;
> + int error = 0;
>
> - for_each_writeback_folio(mapping, wbc, folio, error)
> + while ((folio = writeback_iter(mapping, wbc, folio, &error))) {
> error = writepage(folio, wbc, data);
> + if (error == AOP_WRITEPAGE_ACTIVATE) {
> + folio_unlock(folio);
> + error = 0;
> + }
> + }
>
> - return wbc->err;
> + return error;
> }
> EXPORT_SYMBOL(write_cache_pages);
>
> @@ -2563,13 +2590,17 @@ static int writeback_use_writepage(struct address_space *mapping,
> struct writeback_control *wbc)
> {
> struct blk_plug plug;
> - struct folio *folio;
> - int err;
> + struct folio *folio = NULL;
> + int err = 0;
>
> blk_start_plug(&plug);
> - for_each_writeback_folio(mapping, wbc, folio, err) {
> + while ((folio = writeback_iter(mapping, wbc, folio, &err))) {
> err = mapping->a_ops->writepage(&folio->page, wbc);
> mapping_set_error(mapping, err);
> + if (err == AOP_WRITEPAGE_ACTIVATE) {
> + folio_unlock(folio);
> + err = 0;
> + }
> }
> blk_finish_plug(&plug);
>
>
--
Jan Kara <[email protected]>
SUSE Labs, CR

2024-01-31 10:43:45

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCH 19/19] writeback: simplify writeback iteration

On Wed, Jan 31, 2024 at 11:40:45AM +0100, Jan Kara wrote:
> Yes, this looks very good to me. Feel free to add:

Thanks! I'd also really love to hear from Brian and Matthew before
reposting. Or from anyone else interested..


2024-01-31 12:49:27

by Brian Foster

[permalink] [raw]
Subject: Re: [PATCH 19/19] writeback: simplify writeback iteration

On Wed, Jan 31, 2024 at 08:14:37AM +0100, Christoph Hellwig wrote:
> On Tue, Jan 30, 2024 at 10:50:16PM +0100, Jan Kara wrote:
> > Well, batch release needs to be only here because if writeback_get_folio()
> > returns NULL, the batch has been already released by it.
>
> Indeed.
>
> > So what would be
> > duplicated is only the error assignment. But I'm fine with the version in
> > the following email and actually somewhat prefer it compared the yet
> > another variant you've sent.
>
> So how about another variant, this is closer to your original suggestion.
> But I've switched around the ordered of the folio or not branches
> from my original patch, and completely reworked and (IMHO) improved the
> comments. it replaces patch 19 instead of being incremental to be
> readable:
>

Not a thorough review but at first glance I like it. I think the direct
function call makes things easier to follow. Just one quick thought...

..
> diff --git a/mm/page-writeback.c b/mm/page-writeback.c
> index 0763c4353a676a..eefcb00cb7b227 100644
> --- a/mm/page-writeback.c
> +++ b/mm/page-writeback.c
..
> @@ -2458,60 +2433,107 @@ static struct folio *writeback_get_folio(struct address_space *mapping,
> return folio;
> }
>
..
> +struct folio *writeback_iter(struct address_space *mapping,
> + struct writeback_control *wbc, struct folio *folio, int *error)
> {
> - if (wbc->range_cyclic)
> - wbc->index = mapping->writeback_index; /* prev offset */
> - else
> - wbc->index = wbc->range_start >> PAGE_SHIFT;
> -
> - if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
> - tag_pages_for_writeback(mapping, wbc->index, wbc_end(wbc));
> -
> - wbc->err = 0;
> - folio_batch_init(&wbc->fbatch);
> - return writeback_get_folio(mapping, wbc);
> -}
> + if (!folio) {
> + folio_batch_init(&wbc->fbatch);
> + wbc->err = 0;

The implied field initialization via !folio feels a little wonky to me
just because it's not clear from the client code that both fields must
be initialized. Even though the interface is simpler, I wonder if it's
still worth having a dumb/macro type init function that at least does
the batch and error field initialization.

Or on second thought maybe having writeback_iter() reset *error as well
on folio == NULL might be a little cleaner without changing the
interface....

Brian

>
> -struct folio *writeback_iter_next(struct address_space *mapping,
> - struct writeback_control *wbc, struct folio *folio, int error)
> -{
> - unsigned long nr = folio_nr_pages(folio);
> + /*
> + * For range cyclic writeback we remember where we stopped so
> + * that we can continue where we stopped.
> + *
> + * For non-cyclic writeback we always start at the beginning of
> + * the passed in range.
> + */
> + if (wbc->range_cyclic)
> + wbc->index = mapping->writeback_index;
> + else
> + wbc->index = wbc->range_start >> PAGE_SHIFT;
>
> - wbc->nr_to_write -= nr;
> + /*
> + * To avoid livelocks when other processes dirty new pages, we
> + * first tag pages which should be written back and only then
> + * start writing them.
> + *
> + * For data-integrity sync we have to be careful so that we do
> + * not miss some pages (e.g., because some other process has
> + * cleared the TOWRITE tag we set). The rule we follow is that
> + * TOWRITE tag can be cleared only by the process clearing the
> + * DIRTY tag (and submitting the page for I/O).
> + */
> + if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
> + tag_pages_for_writeback(mapping, wbc->index,
> + wbc_end(wbc));
> + } else {
> + wbc->nr_to_write -= folio_nr_pages(folio);
>
> - /*
> - * Handle the legacy AOP_WRITEPAGE_ACTIVATE magic return value.
> - * Eventually all instances should just unlock the folio themselves and
> - * return 0;
> - */
> - if (error == AOP_WRITEPAGE_ACTIVATE) {
> - folio_unlock(folio);
> - error = 0;
> + /*
> + * For integrity writeback we have to keep going until we have
> + * written all the folios we tagged for writeback prior to
> + * entering the writeback loop, even if we run past
> + * wbc->nr_to_write or encounter errors, and just stash away
> + * the first error we encounter in wbc->err so that it can
> + * be retrieved on return.
> + *
> + * This is because the file system may still have state to clear
> + * for each folio. We'll eventually return the first error
> + * encountered.
> + */
> + if (wbc->sync_mode == WB_SYNC_ALL) {
> + if (*error && !wbc->err)
> + wbc->err = *error;
> + } else {
> + if (*error || wbc->nr_to_write <= 0)
> + goto done;
> + }
> }
>
> - if (error && !wbc->err)
> - wbc->err = error;
> + folio = writeback_get_folio(mapping, wbc);
> + if (!folio) {
> + /*
> + * For range cyclic writeback not finding another folios means
> + * that we are at the end of the file. In that case go back
> + * to the start of the file for the next call.
> + */
> + if (wbc->range_cyclic)
> + mapping->writeback_index = 0;
>
> - /*
> - * For integrity sync we have to keep going until we have written all
> - * the folios we tagged for writeback prior to entering the writeback
> - * loop, even if we run past wbc->nr_to_write or encounter errors.
> - * This is because the file system may still have state to clear for
> - * each folio. We'll eventually return the first error encountered.
> - *
> - * For background writeback just push done_index past this folio so that
> - * we can just restart where we left off and media errors won't choke
> - * writeout for the entire file.
> - */
> - if (wbc->sync_mode == WB_SYNC_NONE &&
> - (wbc->err || wbc->nr_to_write <= 0)) {
> - writeback_finish(mapping, wbc, folio->index + nr);
> - return NULL;
> + /*
> + * Return the first error we encountered (if there was any) to
> + * the caller now that we are done.
> + */
> + *error = wbc->err;
> }
> + return folio;
>
> - return writeback_get_folio(mapping, wbc);
> +done:
> + if (wbc->range_cyclic)
> + mapping->writeback_index = folio->index + folio_nr_pages(folio);
> + folio_batch_release(&wbc->fbatch);
> + return NULL;
> }
>
> /**
> @@ -2549,13 +2571,18 @@ int write_cache_pages(struct address_space *mapping,
> struct writeback_control *wbc, writepage_t writepage,
> void *data)
> {
> - struct folio *folio;
> - int error;
> + struct folio *folio = NULL;
> + int error = 0;
>
> - for_each_writeback_folio(mapping, wbc, folio, error)
> + while ((folio = writeback_iter(mapping, wbc, folio, &error))) {
> error = writepage(folio, wbc, data);
> + if (error == AOP_WRITEPAGE_ACTIVATE) {
> + folio_unlock(folio);
> + error = 0;
> + }
> + }
>
> - return wbc->err;
> + return error;
> }
> EXPORT_SYMBOL(write_cache_pages);
>
> @@ -2563,13 +2590,17 @@ static int writeback_use_writepage(struct address_space *mapping,
> struct writeback_control *wbc)
> {
> struct blk_plug plug;
> - struct folio *folio;
> - int err;
> + struct folio *folio = NULL;
> + int err = 0;
>
> blk_start_plug(&plug);
> - for_each_writeback_folio(mapping, wbc, folio, err) {
> + while ((folio = writeback_iter(mapping, wbc, folio, &err))) {
> err = mapping->a_ops->writepage(&folio->page, wbc);
> mapping_set_error(mapping, err);
> + if (err == AOP_WRITEPAGE_ACTIVATE) {
> + folio_unlock(folio);
> + err = 0;
> + }
> }
> blk_finish_plug(&plug);
>
>


2024-01-31 13:35:04

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCH 19/19] writeback: simplify writeback iteration

On Wed, Jan 31, 2024 at 07:50:25AM -0500, Brian Foster wrote:
> The implied field initialization via !folio feels a little wonky to me
> just because it's not clear from the client code that both fields must
> be initialized. Even though the interface is simpler, I wonder if it's
> still worth having a dumb/macro type init function that at least does
> the batch and error field initialization.
>
> Or on second thought maybe having writeback_iter() reset *error as well
> on folio == NULL might be a little cleaner without changing the
> interface....

I like that second idea. An initialization helper I could live with,
but if only folio needs a defined state, that seems superflous.