From 13b9a606dd0a6cf9bac12894c97b9546d22a7425 Mon Sep 17 00:00:00 2001 From: Julian Sun Date: Fri, 16 Oct 2026 19:52:29 +0800 Subject: [PATCH 1/2] writeback: reset dirtied_when when switching wbs When switching a dirty inode to another wb, inode_do_switch_wbs() puts it at the newest end of the target b_dirty list instead of searching for its sorted position. This requires updating dirtied_when, which move_expired_inodes() uses to decide where to stop scanning. However, the switch updates dirtied_time_when instead. This leaves b_dirty out of order and can make the scan stop before reaching older inodes. It also resets the lazytime expiration timestamp, potentially delaying timestamp writeback. Update dirtied_when instead, preserving both the constant-time insertion and the lazytime expiration timestamp. Correct the field name in the comment and document the distinction between the two inode timestamps. Fixes: 9a6ebbdbd412 ("writeback: Avoid excessively long inode switching times") Cc: stable@vger.kernel.org Signed-off-by: Julian Sun Link: https://patch.msgid.link/20261016115230.3640770-2-sunjunchao@bytedance.com Acked-by: Tejun Heo Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/fs-writeback.c | 6 +++--- include/linux/fs.h | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index e744f9f9d43f..6984f6c10a86 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -468,12 +468,12 @@ static bool inode_do_switch_wbs(struct inode *inode, if (inode_state_read(inode) & I_DIRTY_ALL) { /* * We need to keep b_dirty list sorted by - * dirtied_time_when. However properly sorting the + * dirtied_when. However properly sorting the * inode in the list gets too expensive when switching * many inodes. So just attach inode at the end of the - * dirty list and clobber the dirtied_time_when. + * dirty list and clobber the dirtied_when. */ - inode->dirtied_time_when = jiffies; + inode->dirtied_when = jiffies; inode_io_list_move_locked(inode, new_wb, &new_wb->b_dirty); } else { diff --git a/include/linux/fs.h b/include/linux/fs.h index f9d1e05e8ae6..c5dd44314170 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -815,7 +815,9 @@ struct inode { /* 32-bit hole */ struct rw_semaphore i_rwsem; - unsigned long dirtied_when; /* jiffies of first dirtying */ + /* Timestamp for writeback list ordering and age checks (jiffies). */ + unsigned long dirtied_when; + /* Start time for deciding when lazytime updates need writeback (jiffies). */ unsigned long dirtied_time_when; struct hlist_node i_hash; From a396fe9f74ea367ae3fab2dcb70b24e83db9f0cb Mon Sep 17 00:00:00 2001 From: Julian Sun Date: Fri, 16 Oct 2026 19:52:30 +0800 Subject: [PATCH 2/2] writeback: clear old wb dirty state after inode switches Switching an inode to another wb updates the target wb's dirty-I/O state, but no longer checks whether the source wb's dirty-I/O lists became empty. The check was lost when inode_io_list_del_locked(inode, old_wb) was removed to support tracking clean attached inodes. If the last dirty inode is moved, WB_has_dirty_io remains set on the old wb and its bandwidth contribution remains in bdi->tot_write_bandwidth. This can keep an empty wb scheduled for periodic writeback and overstate the total bandwidth used to distribute writeback work. Call wb_io_lists_depopulated() on the old wb after the batch of inode switches, while its list_lock is still held. This clears the flag and removes the bandwidth contribution if its dirty-I/O lists are empty. Fixes: f3b6a6df38aa ("writeback, cgroup: keep list of inodes attached to bdi_writeback") Cc: stable@vger.kernel.org Signed-off-by: Julian Sun Link: https://patch.msgid.link/20261016115230.3640770-3-sunjunchao@bytedance.com Signed-off-by: Christian Brauner (Amutable) --- fs/fs-writeback.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index 6984f6c10a86..04fca06bd520 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -566,6 +566,7 @@ static void process_inode_switch_wbs(struct bdi_writeback *new_wb, } } + wb_io_lists_depopulated(old_wb); spin_unlock(&new_wb->list_lock); spin_unlock(&old_wb->list_lock);