mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Daeho Jeong <daeho43@gmail.com>
To: linux-kernel@vger.kernel.org,
	linux-f2fs-devel@lists.sourceforge.net, kernel-team@android.com
Cc: Daeho Jeong <daehojeong@google.com>, stable@vger.kernel.org
Subject: [PATCH] f2fs: write a checkpoint in fsync after the warm node log is moved
Date: Sun, 15 Nov 2026 13:35:40 -0800	[thread overview]
Message-ID: <20261115213540.2100311-1-daeho43@gmail.com> (raw)

From: Daeho Jeong <daehojeong@google.com>

Roll-forward recovery finds fsynced node blocks by following the chain in
the warm node log. It starts from the next free block of the log recorded
in the last checkpoint, and each node block has the address of the next
one in its footer. f2fs_allocate_data_block() fills the footer after it
moves the log to a new segment, so the chain is kept when a segment is
full.

f2fs_allocate_segment_for_resize() moves the log out of the range to be
removed in the middle of a segment. No node block is allocated there, so
the last node block in the old segment still points to the next block of
the old segment, and nothing points to the new one. The node blocks
written after that cannot be found by recovery until the next checkpoint.

For example, with F2FS_IOC_RESIZE_FS:

  - fsync(A) writes a node block to segment X, which is in the range.
  - resize moves the warm node log from segment X to segment Y.
  - fsync(B) writes a node block to segment Y and returns 0.
  - after a sudden power-off, recovery follows the chain in segment X,
    recovers A and stops there. B is lost.

Checkpoint is blocked during the first GC pass of resize, so this lasts
until the pass is done. f2fs_ioc_reserve_dev_alias() moves the logs in
the same way.

Set SBI_NEED_CP when the warm node log is moved, so that fsync writes a
checkpoint instead of relying on the chain. The next checkpoint clears
the flag and starts a new chain from the new segment. Such an fsync waits
for the first GC pass of resize, since checkpoint is blocked until then.

An fsync which has already passed need_do_checkpoint() can still write
its node blocks after the log is moved. So check the flag again after
the node blocks are written, and write a checkpoint in that case, too.

Test: four tasks create and fsync files while F2FS_IOC_RESIZE_FS shrinks
the filesystem, and the machine is rebooted by sysrq-b 1.5 seconds after
the log is moved. Without this patch, all the files whose fsync returned
after the move were lost (454 and 437 files in two runs). With this
patch, no fsync returned in that window and no file with a returned
fsync was lost. The second check was tested with a debug patch which
delays an fsync between need_do_checkpoint() and its node writes.

Fixes: b4b10061ef98 ("f2fs: refactor resize_fs to avoid meta updates in progress")
Cc: stable@vger.kernel.org
Signed-off-by: Daeho Jeong <daehojeong@google.com>
---
 fs/f2fs/file.c    |  8 ++++++++
 fs/f2fs/segment.c | 10 +++++++++-
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index 05061254c3b2..61ed0ad5d29d 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -466,6 +466,14 @@ static int f2fs_do_sync_file(struct file *file, loff_t start, loff_t end,
 		goto sync_nodes;
 	}
 
+	/*
+	 * The warm node log can be moved after need_do_checkpoint() above.
+	 * Then the node blocks written here are out of the chain, so write
+	 * a checkpoint.
+	 */
+	if (unlikely(is_sbi_flag_set(sbi, SBI_NEED_CP)))
+		goto go_write;
+
 	/*
 	 * If it's atomic_write, it's just fine to keep write ordering. So
 	 * here we don't need to wait for node write completion, since we use
diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c
index 31ac93a93021..e77a01376288 100644
--- a/fs/f2fs/segment.c
+++ b/fs/f2fs/segment.c
@@ -3488,9 +3488,17 @@ int f2fs_allocate_segment_for_resize(struct f2fs_sb_info *sbi, int type,
 unlock:
 	up_write(&SIT_I(sbi)->sentry_lock);
 
-	if (segno != curseg->segno)
+	if (segno != curseg->segno) {
 		f2fs_notice(sbi, "For resize: curseg of type %d: %u ==> %u",
 			    type, segno, curseg->segno);
+		/*
+		 * The chain of fsynced node blocks is cut here, so roll-forward
+		 * recovery cannot find what is fsynced from now on. Let fsync
+		 * write a checkpoint until the next checkpoint is done.
+		 */
+		if (type == CURSEG_WARM_NODE)
+			set_sbi_flag(sbi, SBI_NEED_CP);
+	}
 
 	mutex_unlock(&curseg->curseg_mutex);
 	f2fs_up_read(&SM_I(sbi)->curseg_lock);
-- 
2.56.0.611.g1030f0b940-goog


                 reply	other threads:[~2026-11-15 21:35 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261115213540.2100311-1-daeho43@gmail.com \
    --to=daeho43@gmail.com \
    --cc=daehojeong@google.com \
    --cc=kernel-team@android.com \
    --cc=linux-f2fs-devel@lists.sourceforge.net \
    --cc=linux-kernel@vger.kernel.org \
    --cc=stable@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®