mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] f2fs: cache: count the temporary pins apart from the regular references
@ 2026-10-05 19:21 Daeho Jeong
  0 siblings, 0 replies; only message in thread
From: Daeho Jeong @ 2026-10-05 19:21 UTC (permalink / raw)
  To: linux-kernel, linux-f2fs-devel, kernel-team; +Cc: Daeho Jeong

From: Daeho Jeong <daehojeong@google.com>

f2fs_unlock_cache() and f2fs_end_cache_writeback() take a temporary
reference on the entry around clear_and_wake_up_bit(), so that the entry
is not freed before wake_up_bit() returns. f2fs_destroy_cache() waits
for the LOCKED and WRITEBACK bits and then expects the refcount to be 1,
but the bit waits can return as soon as the bit is cleared, before the
I/O completion drops the temporary reference. When the completion runs
in process context and is preempted there (e.g. dm-flakey in
generic/311), umount hits:

  kernel BUG at fs/f2fs/cache.c:567!
  f2fs_destroy_cache+0x260/0x268
  f2fs_put_super+0x1fc/0x428

Count the temporary references in units of F2FS_CACHE_PIN_BIAS, like
GUP_PIN_COUNTING_BIAS for folios, and check only the regular references
in f2fs_destroy_cache(). A leaked regular reference is still caught
there, without waiting for the completion.

The pinned entry is freed by whichever of f2fs_cache_put() and
f2fs_cache_unpin() drops the refcount to zero, as before. The shrinker
still skips an entry while it is pinned, since the refcount is then
neither 1 nor below 3.

Fixes: 6e392158cf54 ("f2fs: cache: pin cached block in f2fs_end_cache_writeback()")
Fixes: 61ba87b33e87 ("f2fs: cache: pin cached block in f2fs_unlock_cache()")
Signed-off-by: Daeho Jeong <daehojeong@google.com>
---
 fs/f2fs/cache.c | 40 ++++++++++++++++++++++++++++++++++------
 1 file changed, 34 insertions(+), 6 deletions(-)

diff --git a/fs/f2fs/cache.c b/fs/f2fs/cache.c
index 04b5408cbc5..a75578cd4e3 100644
--- a/fs/f2fs/cache.c
+++ b/fs/f2fs/cache.c
@@ -18,7 +18,7 @@
 #include <trace/events/f2fs.h>
 #include "segment.h"
 
-static bool f2fs_cache_put(struct f2fs_cached_block *entry);
+static void f2fs_free_cache(struct f2fs_cached_block *entry);
 
 void f2fs_cache_wait_writeback_cond(struct f2fs_cached_block *entry,
 					enum page_type type)
@@ -111,6 +111,27 @@ void f2fs_start_cache_writeback(struct f2fs_cached_block *entry)
 				F2FS_CACHE_TAG_WRITEBACK);
 }
 
+/*
+ * The temporary references taken around clear_and_wake_up_bit() are counted
+ * in units of F2FS_CACHE_PIN_BIAS, like GUP_PIN_COUNTING_BIAS for folios, so
+ * that they can be told apart from the regular references.
+ */
+#define F2FS_CACHE_PIN_BIAS	(1 << 16)
+
+static void f2fs_cache_pin(struct f2fs_cached_block *entry)
+{
+	atomic_add(F2FS_CACHE_PIN_BIAS, &entry->refcount);
+}
+
+static void f2fs_cache_unpin(struct f2fs_cached_block *entry)
+{
+	int ref = atomic_sub_return(F2FS_CACHE_PIN_BIAS, &entry->refcount);
+
+	WARN_ON_ONCE(ref < 0);
+	if (!ref)
+		f2fs_free_cache(entry);
+}
+
 void f2fs_end_cache_writeback(struct f2fs_cached_block *entry)
 {
 	/*
@@ -125,9 +146,9 @@ void f2fs_end_cache_writeback(struct f2fs_cached_block *entry)
 	 * But here we must make sure that the entry is not freed and
 	 * reused before clear_and_wake_up_bit().
 	 */
-	f2fs_cache_get(entry);
+	f2fs_cache_pin(entry);
 	clear_and_wake_up_bit(F2FS_BLOCK_WRITEBACK, &entry->state);
-	f2fs_cache_put(entry);
+	f2fs_cache_unpin(entry);
 }
 
 static int f2fs_cache_refcount(struct f2fs_cached_block *entry)
@@ -135,6 +156,12 @@ static int f2fs_cache_refcount(struct f2fs_cached_block *entry)
 	return atomic_read(&entry->refcount);
 }
 
+/* the number of regular references, excluding the temporary ones */
+static int f2fs_cache_users(struct f2fs_cached_block *entry)
+{
+	return f2fs_cache_refcount(entry) & (F2FS_CACHE_PIN_BIAS - 1);
+}
+
 static void f2fs_do_free_cache(struct f2fs_cached_block *entry)
 {
 	kfree(entry->data);
@@ -326,9 +353,9 @@ void f2fs_unlock_cache(struct f2fs_cached_block *entry)
 	 * Pin the entry here to make sure it is not freed before wake_up_bit()
 	 * completes.
 	 */
-	f2fs_cache_get(entry);
+	f2fs_cache_pin(entry);
 	clear_and_wake_up_bit(F2FS_BLOCK_LOCKED, &entry->state);
-	f2fs_cache_put(entry);
+	f2fs_cache_unpin(entry);
 }
 
 bool f2fs_put_cache(struct f2fs_cached_block *entry, bool unlock)
@@ -564,7 +591,8 @@ void f2fs_destroy_cache(struct f2fs_cached_block_list *cache)
 	f2fs_bug_on(cache->sbi, f2fs_cache_test_dirty(entry));
 	f2fs_bug_on(cache->sbi, f2fs_cache_test_writeback(entry));
 	f2fs_bug_on(cache->sbi, !list_empty(&entry->list));
-	f2fs_bug_on(cache->sbi, f2fs_cache_refcount(entry) != 1);
+	/* the I/O completion may not have unpinned the entry yet */
+	f2fs_bug_on(cache->sbi, f2fs_cache_users(entry) != 1);
 	f2fs_put_cache(entry, true);
 	goto next;
 }
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-05 19:21 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-05 19:21 [PATCH] f2fs: cache: count the temporary pins apart from the regular references Daeho Jeong

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®