mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Johannes Weiner <hannes@cmpxchg.org>
To: Yosry Ahmed <yosry@kernel.org>
Cc: Kaitao Cheng <kaitao.cheng@linux.dev>,
	Dave Chinner <david@fromorbit.com>, Qi Zheng <qi.zheng@linux.dev>,
	Muchun Song <muchun.song@linux.dev>,
	Roman Gushchin <roman.gushchin@linux.dev>,
	Nhat Pham <nphamcs@gmail.com>,
	Chengming Zhou <chengming.zhou@linux.dev>,
	Andrew Morton <akpm@linux-foundation.org>,
	linux-mm@kvack.org, linux-kernel@vger.kernel.org,
	Kaitao Cheng <chengkaitao@kylinos.cn>
Subject: Re: [PATCH] mm/zswap: fix double decrement of the LRU node count on writeback
Date: Wed, 28 Oct 2026 17:10:21 -0400	[thread overview]
Message-ID: <auJkvb-Lq0M5Enkz@cmpxchg.org> (raw)
In-Reply-To: <CAO9r8zP15iW3ECW2Bs0rfyZrifeisDunLa6qJC43M1RcE16wWQ@mail.gmail.com>

On Wed, Oct 28, 2026 at 12:22:43PM -0700, Yosry Ahmed wrote:
> On Wed, Oct 28, 2026 at 4:36 AM Kaitao Cheng <kaitao.cheng@linux.dev> wrote:
> >
> > From: Kaitao Cheng <chengkaitao@kylinos.cn>
> >
> > Successful writeback calls zswap_entry_free(), which removes the entry
> > from the LRU and decrements both the sublist and node item counts.
> > shrink_memcg_cb() then returns LRU_REMOVED_RETRY, causing the LRU walker
> > to decrement the node count again.
> 
> I wonder if we have a valid use case for adding LRU_REMOVED_COUNTED or
> LRU_DELETED instead to simply avoid the decrement.

It bugs me that it's the walker, not the callback, that adjusts the
count. That composes poorly with the normal list manipulation API.

How about pushing the accounting into the isolation callback, which
can then simply use __list_lru_del()?

The tradeoff is that more things need to be passed into the isolation
callback, but that seems reasonable. It does make the change noisy,
however, and we might want something like Kaitao's change as a
backportable fix first regardless.

Thoughts?

---

diff --git a/drivers/android/binder/page_range.rs b/drivers/android/binder/page_range.rs
index b0c797523bc2..157256e52dd0 100644
--- a/drivers/android/binder/page_range.rs
+++ b/drivers/android/binder/page_range.rs
@@ -24,7 +24,7 @@
 use kernel::{
     bindings,
     error::Result,
-    ffi::{c_ulong, c_void},
+    ffi::{c_int, c_ulong, c_void},
     mm::{virt, Mm, MmWithUser},
     new_mutex, new_spinlock,
     page::{Page, PAGE_SHIFT, PAGE_SIZE},
@@ -684,8 +684,10 @@ fn drop(self: Pin<&mut Self>) {
 /// Called by the shrinker.
 #[no_mangle]
 unsafe extern "C" fn rust_shrink_free_page(
+    lru: *mut bindings::list_lru,
+    l: *mut bindings::list_lru_one,
     item: *mut bindings::list_head,
-    lru: *mut bindings::list_lru_one,
+    nid: c_int,
     _cb_arg: *mut c_void,
 ) -> bindings::lru_status {
     // Fields that should survive after unlocking the lru lock.
@@ -728,7 +730,7 @@ fn drop(self: Pin<&mut Self>) {
         };
 
         // SAFETY: The item is in this lru list, so it's okay to remove it.
-        unsafe { bindings::list_lru_isolate(lru, item) };
+        unsafe { bindings::__list_lru_del(lru, l, item, nid) };
 
         // SAFETY: Both pointers are in bounds of the same allocation.
         page_index = unsafe { info.offset_from(inner.pages) } as usize;
@@ -746,7 +748,7 @@ fn drop(self: Pin<&mut Self>) {
     }
 
     // SAFETY: The lru lock is locked when this method is called.
-    unsafe { bindings::spin_unlock(&raw mut (*lru).lock) };
+    unsafe { bindings::spin_unlock(&raw mut (*l).lock) };
 
     if let Some(vma) = check_vma(&vma_read, range_ptr) {
         let user_page_addr = vma_addr + (page_index << PAGE_SHIFT);
diff --git a/drivers/gpu/drm/ttm/ttm_pool.c b/drivers/gpu/drm/ttm/ttm_pool.c
index 1bf37023fed6..2e858c05e30b 100644
--- a/drivers/gpu/drm/ttm/ttm_pool.c
+++ b/drivers/gpu/drm/ttm/ttm_pool.c
@@ -345,13 +345,14 @@ static void ttm_pool_type_give(struct ttm_pool_type *pt, struct page *p)
 	mod_lruvec_page_state(p, NR_GPU_RECLAIM, num_pages);
 }
 
-static enum lru_status take_one_from_lru(struct list_head *item,
-					 struct list_lru_one *list,
+static enum lru_status take_one_from_lru(struct list_lru *lru,
+					 struct list_lru_one *l,
+					 struct list_head *item, int nid,
 					 void *cb_arg)
 {
 	struct page **out_page = cb_arg;
 	struct page *p = container_of(item, struct page, lru);
-	list_lru_isolate(list, item);
+	__list_lru_del(lru, l, item, nid);
 
 	*out_page = p;
 	return LRU_REMOVED;
@@ -387,13 +388,14 @@ static void ttm_pool_type_init(struct ttm_pool_type *pt, struct ttm_pool *pool,
 	spin_unlock(&shrinker_lock);
 }
 
-static enum lru_status pool_move_to_dispose_list(struct list_head *item,
-						 struct list_lru_one *list,
+static enum lru_status pool_move_to_dispose_list(struct list_lru *lru,
+						 struct list_lru_one *l,
+						 struct list_head *item, int nid,
 						 void *cb_arg)
 {
 	struct list_head *dispose = cb_arg;
 
-	list_lru_isolate_move(list, item, dispose);
+	__list_lru_move(lru, l, item, nid, dispose);
 
 	return LRU_REMOVED;
 }
diff --git a/fs/dcache.c b/fs/dcache.c
index 37d021d22a7d..ce8063c2f5a6 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -556,24 +556,26 @@ static void d_shrink_add(struct dentry *dentry, struct list_head *list)
  * LRU lists entirely, while shrink_move moves it to the indicated
  * private list.
  */
-static void d_lru_isolate(struct list_lru_one *lru, struct dentry *dentry)
+static void d_lru_isolate(struct list_lru *lru, struct list_lru_one *l,
+			  struct dentry *dentry, int nid)
 {
 	D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST);
 	dentry->d_flags &= ~DCACHE_LRU_LIST;
 	this_cpu_dec(nr_dentry_unused);
 	if (d_is_negative(dentry))
 		this_cpu_dec(nr_dentry_negative);
-	list_lru_isolate(lru, &dentry->d_lru);
+	__list_lru_del(lru, l, &dentry->d_lru, nid);
 }
 
-static void d_lru_shrink_move(struct list_lru_one *lru, struct dentry *dentry,
+static void d_lru_shrink_move(struct list_lru *lru, struct list_lru_one *l,
+			      struct dentry *dentry, int nid,
 			      struct list_head *list)
 {
 	D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST);
 	dentry->d_flags |= DCACHE_SHRINK_LIST;
 	if (d_is_negative(dentry))
 		this_cpu_dec(nr_dentry_negative);
-	list_lru_isolate_move(lru, &dentry->d_lru, list);
+	__list_lru_move(lru, l, &dentry->d_lru, nid, list);
 }
 
 static void ___d_drop(struct dentry *dentry)
@@ -1308,8 +1310,8 @@ void shrink_dentry_list(struct list_head *list)
 }
 EXPORT_SYMBOL(shrink_dentry_list);
 
-static enum lru_status dentry_lru_isolate(struct list_head *item,
-		struct list_lru_one *lru, void *arg)
+static enum lru_status dentry_lru_isolate(struct list_lru *lru,
+		struct list_lru_one *l, struct list_head *item, int nid, void *arg)
 {
 	struct list_head *freeable = arg;
 	struct dentry	*dentry = container_of(item, struct dentry, d_lru);
@@ -1329,7 +1331,7 @@ static enum lru_status dentry_lru_isolate(struct list_head *item,
 	 * another pass through the LRU.
 	 */
 	if (dentry->d_lockref.count) {
-		d_lru_isolate(lru, dentry);
+		d_lru_isolate(lru, l, dentry, nid);
 		spin_unlock(&dentry->d_lock);
 		return LRU_REMOVED;
 	}
@@ -1360,7 +1362,7 @@ static enum lru_status dentry_lru_isolate(struct list_head *item,
 		return LRU_ROTATE;
 	}
 
-	d_lru_shrink_move(lru, dentry, freeable);
+	d_lru_shrink_move(lru, l, dentry, nid, freeable);
 	spin_unlock(&dentry->d_lock);
 
 	return LRU_REMOVED;
@@ -1389,8 +1391,8 @@ long prune_dcache_sb(struct super_block *sb, struct shrink_control *sc)
 	return freed;
 }
 
-static enum lru_status dentry_lru_isolate_shrink(struct list_head *item,
-		struct list_lru_one *lru, void *arg)
+static enum lru_status dentry_lru_isolate_shrink(struct list_lru *lru,
+		struct list_lru_one *l, struct list_head *item, int nid, void *arg)
 {
 	struct list_head *freeable = arg;
 	struct dentry	*dentry = container_of(item, struct dentry, d_lru);
@@ -1403,7 +1405,7 @@ static enum lru_status dentry_lru_isolate_shrink(struct list_head *item,
 	if (!spin_trylock(&dentry->d_lock))
 		return LRU_SKIP;
 
-	d_lru_shrink_move(lru, dentry, freeable);
+	d_lru_shrink_move(lru, l, dentry, nid, freeable);
 	spin_unlock(&dentry->d_lock);
 
 	return LRU_REMOVED;
diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c
index b2984fb7b35c..c8afe8236805 100644
--- a/fs/gfs2/quota.c
+++ b/fs/gfs2/quota.c
@@ -148,8 +148,8 @@ static void gfs2_qd_list_dispose(struct list_head *list)
 }
 
 
-static enum lru_status gfs2_qd_isolate(struct list_head *item,
-		struct list_lru_one *lru, void *arg)
+static enum lru_status gfs2_qd_isolate(struct list_lru *lru,
+		struct list_lru_one *l, struct list_head *item, int nid, void *arg)
 {
 	struct list_head *dispose = arg;
 	struct gfs2_quota_data *qd =
@@ -162,7 +162,7 @@ static enum lru_status gfs2_qd_isolate(struct list_head *item,
 	status = LRU_SKIP;
 	if (qd->qd_lockref.count == 0) {
 		lockref_mark_dead(&qd->qd_lockref);
-		list_lru_isolate_move(lru, &qd->qd_lru, dispose);
+		__list_lru_move(lru, l, &qd->qd_lru, nid, dispose);
 		status = LRU_REMOVED;
 	}
 
diff --git a/fs/inode.c b/fs/inode.c
index 1cb6293b237a..675f4a8b9d95 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -929,8 +929,8 @@ EXPORT_SYMBOL_GPL(evict_inodes);
  * LRU does not have strict ordering. Hence we don't want to reclaim inodes
  * with this flag set because they are the inodes that are out of order.
  */
-static enum lru_status inode_lru_isolate(struct list_head *item,
-		struct list_lru_one *lru, void *arg)
+static enum lru_status inode_lru_isolate(struct list_lru *lru,
+		struct list_lru_one *l, struct list_head *item, int nid, void *arg)
 {
 	struct list_head *freeable = arg;
 	struct inode	*inode = container_of(item, struct inode, i_lru);
@@ -951,7 +951,7 @@ static enum lru_status inode_lru_isolate(struct list_head *item,
 	if (icount_read(inode) ||
 	    (inode_state_read(inode) & ~I_REFERENCED) ||
 	    !mapping_shrinkable(&inode->i_data)) {
-		list_lru_isolate(lru, &inode->i_lru);
+		__list_lru_del(lru, l, &inode->i_lru, nid);
 		spin_unlock(&inode->i_lock);
 		this_cpu_dec(nr_unused);
 		return LRU_REMOVED;
@@ -974,7 +974,7 @@ static enum lru_status inode_lru_isolate(struct list_head *item,
 
 		inode_pin_lru_isolating(inode);
 		spin_unlock(&inode->i_lock);
-		spin_unlock(&lru->lock);
+		spin_unlock(&l->lock);
 		reap = invalidate_mapping_pages(&inode->i_data, 0, -1);
 		if (current_is_kswapd())
 			__count_vm_events(KSWAPD_INODESTEAL, reap);
@@ -987,7 +987,7 @@ static enum lru_status inode_lru_isolate(struct list_head *item,
 
 	WARN_ON(inode_state_read(inode) & I_NEW);
 	inode_state_set(inode, I_FREEING);
-	list_lru_isolate_move(lru, &inode->i_lru, freeable);
+	__list_lru_move(lru, l, &inode->i_lru, nid, freeable);
 	spin_unlock(&inode->i_lock);
 
 	this_cpu_dec(nr_unused);
diff --git a/fs/nfs/nfs42xattr.c b/fs/nfs/nfs42xattr.c
index 37d79400e5f4..17eb14b40830 100644
--- a/fs/nfs/nfs42xattr.c
+++ b/fs/nfs/nfs42xattr.c
@@ -801,8 +801,8 @@ static struct shrinker *nfs4_xattr_entry_shrinker;
 static struct shrinker *nfs4_xattr_large_entry_shrinker;
 
 static enum lru_status
-cache_lru_isolate(struct list_head *item,
-	struct list_lru_one *lru, void *arg)
+cache_lru_isolate(struct list_lru *lru, struct list_lru_one *l,
+	struct list_head *item, int nid, void *arg)
 {
 	struct list_head *dispose = arg;
 	struct inode *inode;
@@ -827,7 +827,7 @@ cache_lru_isolate(struct list_head *item,
 	cache->inode = NULL;
 	NFS_I(inode)->xattr_cache = NULL;
 	NFS_I(inode)->cache_validity &= ~NFS_INO_INVALID_XATTR;
-	list_lru_isolate(lru, &cache->lru);
+	__list_lru_del(lru, l, &cache->lru, nid);
 
 	spin_unlock(&inode->i_lock);
 
@@ -866,8 +866,8 @@ nfs4_xattr_cache_count(struct shrinker *shrink, struct shrink_control *sc)
 }
 
 static enum lru_status
-entry_lru_isolate(struct list_head *item,
-	struct list_lru_one *lru, void *arg)
+entry_lru_isolate(struct list_lru *lru, struct list_lru_one *l,
+	struct list_head *item, int nid, void *arg)
 {
 	struct list_head *dispose = arg;
 	struct nfs4_xattr_bucket *bucket;
@@ -896,7 +896,7 @@ entry_lru_isolate(struct list_head *item,
 
 		hlist_del_init(&entry->hnode);
 		atomic_long_dec(&cache->nent);
-		list_lru_isolate(lru, &entry->lru);
+		__list_lru_del(lru, l, &entry->lru, nid);
 
 		spin_unlock(&bucket->lock);
 	} else {
@@ -907,7 +907,7 @@ entry_lru_isolate(struct list_head *item,
 		kref_get(&entry->ref);
 
 		cache->listxattr = NULL;
-		list_lru_isolate(lru, &entry->lru);
+		__list_lru_del(lru, l, &entry->lru, nid);
 
 		spin_unlock(&cache->listxattr_lock);
 	}
diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
index 17a94e6fcb15..6b9165e5c3c5 100644
--- a/fs/nfsd/filecache.c
+++ b/fs/nfsd/filecache.c
@@ -509,8 +509,10 @@ void nfsd_file_net_dispose(struct nfsd_net *nn)
 
 /**
  * nfsd_file_lru_cb - Examine an entry on the LRU list
- * @item: LRU entry to examine
  * @lru: controlling LRU
+ * @l: locked sublist of @lru that holds @item
+ * @item: LRU entry to examine
+ * @nid: node id of @l
  * @arg: dispose list
  *
  * Return values:
@@ -519,8 +521,8 @@ void nfsd_file_net_dispose(struct nfsd_net *nn)
  *   %LRU_SKIP: @item cannot be evicted
  */
 static enum lru_status
-nfsd_file_lru_cb(struct list_head *item, struct list_lru_one *lru,
-		 void *arg)
+nfsd_file_lru_cb(struct list_lru *lru, struct list_lru_one *l,
+		 struct list_head *item, int nid, void *arg)
 {
 	struct list_head *head = arg;
 	struct nfsd_file *nf = list_entry(item, struct nfsd_file, nf_lru);
@@ -554,7 +556,7 @@ nfsd_file_lru_cb(struct list_head *item, struct list_lru_one *lru,
 
 	/* Refcount went to zero. Unhash it and queue it to the dispose list */
 	nfsd_file_unhash(nf);
-	list_lru_isolate(lru, &nf->nf_lru);
+	__list_lru_del(lru, l, &nf->nf_lru, nid);
 	list_add(&nf->nf_gc, head);
 	this_cpu_inc(nfsd_file_evictions);
 	trace_nfsd_file_gc_disposed(nf);
@@ -562,8 +564,8 @@ nfsd_file_lru_cb(struct list_head *item, struct list_lru_one *lru,
 }
 
 static enum lru_status
-nfsd_file_gc_cb(struct list_head *item, struct list_lru_one *lru,
-		 void *arg)
+nfsd_file_gc_cb(struct list_lru *lru, struct list_lru_one *l,
+		 struct list_head *item, int nid, void *arg)
 {
 	struct nfsd_file *nf = list_entry(item, struct nfsd_file, nf_lru);
 
@@ -576,7 +578,7 @@ nfsd_file_gc_cb(struct list_head *item, struct list_lru_one *lru,
 		trace_nfsd_file_gc_aged(nf);
 		return LRU_ROTATE;
 	}
-	return nfsd_file_lru_cb(item, lru, arg);
+	return nfsd_file_lru_cb(lru, l, item, nid, arg);
 }
 
 static void
diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
index 6c93b4f5629c..436aca8f6619 100644
--- a/fs/xfs/xfs_buf.c
+++ b/fs/xfs/xfs_buf.c
@@ -1467,8 +1467,10 @@ __xfs_buf_mark_corrupt(
  */
 static enum lru_status
 xfs_buftarg_drain_rele(
+	struct list_lru		*lru,
+	struct list_lru_one	*l,
 	struct list_head	*item,
-	struct list_lru_one	*lru,
+	int			nid,
 	void			*arg)
 
 {
@@ -1485,7 +1487,7 @@ xfs_buftarg_drain_rele(
 	}
 
 	xfs_buf_kill(bp);
-	list_lru_isolate_move(lru, item, dispose);
+	__list_lru_move(lru, l, item, nid, dispose);
 	spin_unlock(&bp->b_lockref.lock);
 	return LRU_REMOVED;
 }
@@ -1560,8 +1562,10 @@ xfs_buftarg_drain(
 
 static enum lru_status
 xfs_buftarg_isolate(
+	struct list_lru		*lru,
+	struct list_lru_one	*l,
 	struct list_head	*item,
-	struct list_lru_one	*lru,
+	int			nid,
 	void			*arg)
 {
 	struct xfs_buf		*bp = container_of(item, struct xfs_buf, b_lru);
@@ -1581,7 +1585,7 @@ xfs_buftarg_isolate(
 	 * yet.
 	 */
 	if (bp->b_lockref.count > 0) {
-		list_lru_isolate(lru, &bp->b_lru);
+		__list_lru_del(lru, l, &bp->b_lru, nid);
 		spin_unlock(&bp->b_lockref.lock);
 		return LRU_REMOVED;
 	}
@@ -1597,7 +1601,7 @@ xfs_buftarg_isolate(
 	}
 
 	xfs_buf_kill(bp);
-	list_lru_isolate_move(lru, item, dispose);
+	__list_lru_move(lru, l, item, nid, dispose);
 	spin_unlock(&bp->b_lockref.lock);
 	return LRU_REMOVED;
 }
diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c
index dd5011f578a6..3933dd222509 100644
--- a/fs/xfs/xfs_qm.c
+++ b/fs/xfs/xfs_qm.c
@@ -411,10 +411,12 @@ struct xfs_qm_isolate {
 
 static enum lru_status
 xfs_qm_dquot_isolate(
+	struct list_lru		*lru,
+	struct list_lru_one	*l,
 	struct list_head	*item,
-	struct list_lru_one	*lru,
+	int			nid,
 	void			*arg)
-		__releases(&lru->lock) __acquires(&lru->lock)
+		__releases(&l->lock) __acquires(&l->lock)
 {
 	struct xfs_dquot	*dqp = container_of(item,
 						struct xfs_dquot, q_lru);
@@ -450,7 +452,7 @@ xfs_qm_dquot_isolate(
 		XFS_STATS_INC(dqp->q_mount, xs_qm_dqwants);
 
 		trace_xfs_dqreclaim_want(dqp);
-		list_lru_isolate(lru, &dqp->q_lru);
+		__list_lru_del(lru, l, &dqp->q_lru, nid);
 		XFS_STATS_DEC(dqp->q_mount, xs_qm_dquot_unused);
 		return LRU_REMOVED;
 	}
@@ -473,7 +475,7 @@ xfs_qm_dquot_isolate(
 	lockref_mark_dead(&dqp->q_lockref);
 	spin_unlock(&dqp->q_lockref.lock);
 
-	list_lru_isolate_move(lru, &dqp->q_lru, &isol->dispose);
+	__list_lru_move(lru, l, &dqp->q_lru, nid, &isol->dispose);
 	XFS_STATS_DEC(dqp->q_mount, xs_qm_dquot_unused);
 	trace_xfs_dqreclaim_done(dqp);
 	XFS_STATS_INC(dqp->q_mount, xs_qm_dqreclaims);
diff --git a/include/linux/list_lru.h b/include/linux/list_lru.h
index a450fffe1550..f6d94474a4c7 100644
--- a/include/linux/list_lru.h
+++ b/include/linux/list_lru.h
@@ -150,6 +150,15 @@ bool __list_lru_add(struct list_lru *lru, struct list_lru_one *l,
 bool __list_lru_del(struct list_lru *lru, struct list_lru_one *l,
 		struct list_head *item, int nid);
 
+/* Take @item off the locked sublist @l and put it on a private list @head */
+static inline void __list_lru_move(struct list_lru *lru, struct list_lru_one *l,
+				   struct list_head *item, int nid,
+				   struct list_head *head)
+{
+	__list_lru_del(lru, l, item, nid);
+	list_add(item, head);
+}
+
 /**
  * list_lru_add: add an element to the lru list's tail
  * @lru: the lru pointer
@@ -271,12 +280,9 @@ static inline unsigned long list_lru_count(struct list_lru *lru)
 	return count;
 }
 
-void list_lru_isolate(struct list_lru_one *list, struct list_head *item);
-void list_lru_isolate_move(struct list_lru_one *list, struct list_head *item,
-			   struct list_head *head);
-
-typedef enum lru_status (*list_lru_walk_cb)(struct list_head *item,
-		struct list_lru_one *list, void *cb_arg);
+typedef enum lru_status (*list_lru_walk_cb)(struct list_lru *lru,
+		struct list_lru_one *l, struct list_head *item, int nid,
+		void *cb_arg);
 
 /**
  * list_lru_walk_one: walk a @lru, isolating and disposing freeable items.
@@ -289,11 +295,14 @@ typedef enum lru_status (*list_lru_walk_cb)(struct list_head *item,
  * @nr_to_walk: how many items to scan.
  *
  * This function will scan all elements in a particular @lru, calling the
- * @isolate callback for each of those items, along with the current list
- * spinlock and a caller-provided opaque. The @isolate callback can choose to
+ * @isolate callback for each of those items, along with the locked sublist
+ * and a caller-provided opaque. The @isolate callback can choose to
  * drop the lock internally, but *must* return with the lock held. The callback
  * will return an enum lru_status telling the @lru infrastructure what to
- * do with the object being scanned.
+ * do with the object being scanned. A callback that takes the item off
+ * the lru does so with __list_lru_del() or __list_lru_move(), or with
+ * list_lru_del() once it has dropped the lock, and returns LRU_REMOVED
+ * or LRU_REMOVED_RETRY.
  *
  * Please note that @nr_to_walk does not mean how many objects will be freed,
  * just how many objects will be scanned.
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index bd44d58e8684..87722e1a7eee 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4638,8 +4638,9 @@ static bool thp_underused(struct folio *folio)
 	return false;
 }
 
-static enum lru_status deferred_split_isolate(struct list_head *item,
-					      struct list_lru_one *lru,
+static enum lru_status deferred_split_isolate(struct list_lru *lru,
+					      struct list_lru_one *l,
+					      struct list_head *item, int nid,
 					      void *cb_arg)
 {
 	struct folio *folio = container_of(item, struct folio, _deferred_list);
@@ -4649,7 +4650,7 @@ static enum lru_status deferred_split_isolate(struct list_head *item,
 	if (!folio_try_get(folio))
 		return LRU_SKIP;
 
-	list_lru_isolate_move(lru, item, freeable);
+	__list_lru_move(lru, l, item, nid, freeable);
 	return LRU_REMOVED;
 }
 
diff --git a/mm/list_lru.c b/mm/list_lru.c
index d1d256822ccc..9469117aa424 100644
--- a/mm/list_lru.c
+++ b/mm/list_lru.c
@@ -218,6 +218,7 @@ bool __list_lru_del(struct list_lru *lru, struct list_lru_one *l,
 	}
 	return false;
 }
+EXPORT_SYMBOL_GPL(__list_lru_del);
 
 /* The caller must ensure the memcg lifetime. */
 bool list_lru_add(struct list_lru *lru, struct list_head *item, int nid,
@@ -291,21 +292,6 @@ bool list_lru_del_obj(struct list_lru *lru, struct list_head *item)
 }
 EXPORT_SYMBOL_GPL(list_lru_del_obj);
 
-void list_lru_isolate(struct list_lru_one *list, struct list_head *item)
-{
-	list_del_init(item);
-	list->nr_items--;
-}
-EXPORT_SYMBOL_GPL(list_lru_isolate);
-
-void list_lru_isolate_move(struct list_lru_one *list, struct list_head *item,
-			   struct list_head *head)
-{
-	list_move(item, head);
-	list->nr_items--;
-}
-EXPORT_SYMBOL_GPL(list_lru_isolate_move);
-
 unsigned long list_lru_count_one(struct list_lru *lru,
 				 int nid, struct mem_cgroup *memcg)
 {
@@ -338,7 +324,6 @@ __list_lru_walk_one(struct list_lru *lru, int nid, struct mem_cgroup *memcg,
 		    list_lru_walk_cb isolate, void *cb_arg,
 		    unsigned long *nr_to_walk, bool irq_off)
 {
-	struct list_lru_node *nlru = &lru->node[nid];
 	struct list_lru_one *l = NULL;
 	struct list_head *item, *n;
 	unsigned long isolated = 0;
@@ -359,7 +344,7 @@ __list_lru_walk_one(struct list_lru *lru, int nid, struct mem_cgroup *memcg,
 			break;
 		--*nr_to_walk;
 
-		ret = isolate(item, l, cb_arg);
+		ret = isolate(lru, l, item, nid, cb_arg);
 		switch (ret) {
 		/*
 		 * LRU_RETRY, LRU_REMOVED_RETRY and LRU_STOP will drop the lru
@@ -371,7 +356,6 @@ __list_lru_walk_one(struct list_lru *lru, int nid, struct mem_cgroup *memcg,
 			fallthrough;
 		case LRU_REMOVED:
 			isolated++;
-			atomic_long_dec(&nlru->nr_items);
 			if (ret == LRU_REMOVED_RETRY)
 				goto restart;
 			break;
diff --git a/mm/shmem.c b/mm/shmem.c
index 3c0c7edd2020..e4c1d9dfcfa2 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -852,8 +852,9 @@ struct shmem_unused_huge_scan {
 	struct shrink_control *sc;
 };
 
-static enum lru_status shmem_unused_huge_isolate(struct list_head *item,
-						 struct list_lru_one *lru,
+static enum lru_status shmem_unused_huge_isolate(struct list_lru *lru,
+						 struct list_lru_one *l,
+						 struct list_head *item, int nid,
 						 void *arg)
 {
 	struct shmem_unused_huge_scan *scan = arg;
@@ -874,14 +875,14 @@ static enum lru_status shmem_unused_huge_isolate(struct list_head *item,
 	inode = igrab(&info->vfs_inode);
 	/* inode is about to be evicted */
 	if (!inode) {
-		list_lru_isolate(lru, item);
+		__list_lru_del(lru, l, item, nid);
 		objcg = shmem_get_and_clear_objcg(info);
 		spin_unlock(&info->lock);
 		obj_cgroup_put(objcg);
 		return LRU_REMOVED;
 	}
 
-	list_lru_isolate(lru, item);
+	__list_lru_del(lru, l, item, nid);
 	objcg = shmem_get_and_clear_objcg(info);
 	set_shmem_unused_huge_isolated(info);
 	list_add_tail(&info->shrinklist, &scan->list);
diff --git a/mm/workingset.c b/mm/workingset.c
index 1504f91cdca5..c5d5690bda9f 100644
--- a/mm/workingset.c
+++ b/mm/workingset.c
@@ -706,9 +706,10 @@ static unsigned long count_shadow_nodes(struct shrinker *shrinker,
 	return nodes - max_nodes;
 }
 
-static enum lru_status shadow_lru_isolate(struct list_head *item,
-					  struct list_lru_one *lru,
-					  void *arg) __must_hold(lru->lock)
+static enum lru_status shadow_lru_isolate(struct list_lru *lru,
+					  struct list_lru_one *l,
+					  struct list_head *item, int nid,
+					  void *arg) __must_hold(l->lock)
 {
 	struct xa_node *node = container_of(item, struct xa_node, private_list);
 	struct address_space *mapping;
@@ -717,20 +718,20 @@ static enum lru_status shadow_lru_isolate(struct list_head *item,
 	/*
 	 * Page cache insertions and deletions synchronously maintain
 	 * the shadow node LRU under the i_pages lock and the
-	 * &lru->lock. Because the page cache tree is emptied before
-	 * the inode can be destroyed, holding the &lru->lock pins any
+	 * &l->lock. Because the page cache tree is emptied before
+	 * the inode can be destroyed, holding the &l->lock pins any
 	 * address_space that has nodes on the LRU.
 	 *
 	 * We can then safely transition to the i_pages lock to
 	 * pin only the address_space of the particular node we want
-	 * to reclaim, take the node off-LRU, and drop the &lru->lock.
+	 * to reclaim, take the node off-LRU, and drop the &l->lock.
 	 */
 
 	mapping = container_of(node->array, struct address_space, i_pages);
 
 	/* Coming from the list, invert the lock order */
 	if (!xa_trylock(&mapping->i_pages)) {
-		spin_unlock_irq(&lru->lock);
+		spin_unlock_irq(&l->lock);
 		ret = LRU_RETRY;
 		goto out;
 	}
@@ -739,16 +740,16 @@ static enum lru_status shadow_lru_isolate(struct list_head *item,
 	if (mapping->host != NULL) {
 		if (!spin_trylock(&mapping->host->i_lock)) {
 			xa_unlock(&mapping->i_pages);
-			spin_unlock_irq(&lru->lock);
+			spin_unlock_irq(&l->lock);
 			ret = LRU_RETRY;
 			goto out;
 		}
 	}
 
-	list_lru_isolate(lru, item);
+	__list_lru_del(lru, l, item, nid);
 	__dec_node_page_state(virt_to_page(node), WORKINGSET_NODES);
 
-	spin_unlock(&lru->lock);
+	spin_unlock(&l->lock);
 
 	/*
 	 * The nodes should only contain one or more shadow entries,
diff --git a/mm/zswap.c b/mm/zswap.c
index ad01c691a263..72eae0745b62 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -1177,8 +1177,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
  *    can expect from writeback. We scale down the number of objects available
  *    for reclaim by this ratio.
  */
-static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_one *l,
-				       void *arg)
+static enum lru_status shrink_memcg_cb(struct list_lru *lru, struct list_lru_one *l,
+				       struct list_head *item, int nid, void *arg)
 {
 	struct zswap_entry *entry = container_of(item, struct zswap_entry, lru);
 	bool *encountered_page_in_swapcache = (bool *)arg;
-- 
2.56.0

  reply	other threads:[~2026-10-28 21:10 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-28 11:36 Kaitao Cheng
2026-10-28 18:47 ` Nhat Pham
2026-10-29  4:15   ` Kaitao Cheng
2026-10-28 19:22 ` Yosry Ahmed
2026-10-28 21:10   ` Johannes Weiner [this message]
2026-10-28 21:17     ` Yosry Ahmed
2026-10-29  4:23     ` Kaitao Cheng

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=auJkvb-Lq0M5Enkz@cmpxchg.org \
    --to=hannes@cmpxchg.org \
    --cc=akpm@linux-foundation.org \
    --cc=chengkaitao@kylinos.cn \
    --cc=chengming.zhou@linux.dev \
    --cc=david@fromorbit.com \
    --cc=kaitao.cheng@linux.dev \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=muchun.song@linux.dev \
    --cc=nphamcs@gmail.com \
    --cc=qi.zheng@linux.dev \
    --cc=roman.gushchin@linux.dev \
    --cc=yosry@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®