From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C853E4F3EAB; Wed, 30 Sep 2026 17:30:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790789410; cv=none; b=IvP3fs357vf4J1hx79Kh+3U1DVrk9EKHHphWCZRe2LpT5r7LWrlKRzQ++llf9Cqbsb+uvYGDBL8e5mK9EeasU1+5OHd6IhCiieU2Fh4FH1gD6EUV2kkd1sIL5RrU2CknymBbqYpeFYeFBYd7LNsBpMmEqNgm5eN0wGYcp2F9RAw= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790789410; c=relaxed/simple; bh=BZTlgBRP8jaZZjVzvJKjgTcI/tfob7UAEbGAPvDFEug=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KGnEhRmqJK3yQ6+PpPQf3YUwNgrWhgT6kzIGo7U/6e7TTCC2l9mrndzOSBkfeh683AfiXiSWJqq9AuzJ3M8fZnuQvKmdrxVEKEv+qkyQbnjKXe/+8w730DcOeuef3M2hS8tSfcTSr5UucRbENofPPZW3g7zDpTUs1RP7wFcPuSg= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=ABQc73r7; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="ABQc73r7" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 1BE361F00898; Wed, 30 Sep 2026 17:30:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1790789408; bh=1K/FAfjZNoyAIj1DRH84HuMvxNH/Qy2Vzpfa03Szcws=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=ABQc73r7q6tRF75mQr8yuB3RF2j9ZmeDgC2GEc5y2D5+/HHr2uE6zvZvJh3zCAgpx wOQ9P7DjDHPZvK3v+i3TIjLl4R3NxJfcf3vMVDb+dzPq0K78jSrWVKEWot7MwzeAkG uAmzlX134mhElKGwD58+rYIrv8t7wX/rnlLd74FM= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Tejun Heo , Roman Gushchin , "Patrick Lu (Anthropic)" , Jan Kara , "Christian Brauner (Amutable)" Subject: [PATCH 6.12 467/877] writeback: bound cleanup_offline_cgwb() rescans by rotating scanned inodes Date: Wed, 30 Sep 2026 17:22:58 +0200 Message-ID: <20260930152424.753780871@linuxfoundation.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260930152414.738996857@linuxfoundation.org> References: <20260930152414.738996857@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: patches@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Patrick Lu (Anthropic) commit f6988c90671e83db79df1b7b9d6fdb0e5947fd84 upstream. cleanup_offline_cgwb() prepares at most WB_MAX_INODES_PER_ISW inodes per call and is called again until the dying wb is drained, but every call walks wb->b_attached and then wb->b_dirty_time from the same end. Inodes already prepared (they stay on the list with I_WB_SWITCH set until the switch worker runs) and inodes that cannot be switched (I_FREEING, I_WILL_FREE, !SB_ACTIVE, DAX, already on the target wb) stay where they are, so each pass rescans a growing run of them under wb->list_lock and a full drain is quadratic in the number of inodes on the list. With ~17M inodes attached to one dying cgwb we saw this end in soft lockups, with CPUs reported stuck for 21-48s. Walk both lists from the oldest end and move every scanned inode to the newest end, so the next pass starts where the previous one stopped and the drain becomes linear. b_attached is unordered, so nobody sees the reorder there. b_dirty_time is ordered by dirtied_when, but the oldest unscanned inode stays at the end move_expired_inodes() picks from, sync takes the whole list regardless of order, and prepared inodes leave the list as soon as the switch work runs and get a new dirtied_time_when on the new wb anyway, so the only inodes left out of order are the ones that can never switch (DAX), and only on the dying wb. Fixes: c22d70a162d3 ("writeback, cgroup: release dying cgwbs by switching attached inodes") Cc: stable@vger.kernel.org Acked-by: Tejun Heo Acked-by: Roman Gushchin Signed-off-by: Patrick Lu (Anthropic) Link: https://patch.msgid.link/20260911-wb-cgwb-rotate-v2-1-a9ab253a1295@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) Signed-off-by: Greg Kroah-Hartman --- fs/fs-writeback.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -693,19 +693,34 @@ static bool isw_prepare_wbs_switch(struc struct inode_switch_wbs_context *isw, struct list_head *list, int *nr) { - struct inode *inode; + struct inode *inode, *tmp; + LIST_HEAD(scanned); + bool full = false; + + /* + * Walk from the oldest end and move scanned inodes to the newest + * end, so the next scan resumes at unscanned inodes instead of + * re-walking an ever-growing run of prepared and skipped ones. + * For b_dirty_time this keeps the oldest unscanned inode at the + * end move_expired_inodes() picks from; b_attached is unordered. + */ + list_for_each_entry_safe_reverse(inode, tmp, list, i_io_list) { + list_move(&inode->i_io_list, &scanned); - list_for_each_entry(inode, list, i_io_list) { if (!inode_prepare_wbs_switch(inode, new_wb)) continue; isw->inodes[*nr] = inode; (*nr)++; - if (*nr >= WB_MAX_INODES_PER_ISW - 1) - return true; + if (*nr >= WB_MAX_INODES_PER_ISW - 1) { + full = true; + break; + } } - return false; + list_splice(&scanned, list); + + return full; } /**