* [PATCH] f2fs: fix node_cnt race between extent node destroy and writeback
@ 2026-04-03 14:40 Yongpeng Yang
2026-04-13 11:23 ` Chao Yu
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Yongpeng Yang @ 2026-04-03 14:40 UTC (permalink / raw)
To: Chao Yu, Jaegeuk Kim
Cc: linux-f2fs-devel, Yongpeng Yang, Yongpeng Yang, stable
From: Yongpeng Yang <yangyongpeng@xiaomi.com>
f2fs_destroy_extent_node() does not set FI_NO_EXTENT before clearing
extent nodes. When called from f2fs_drop_inode() with I_SYNC set,
concurrent kworker writeback can insert new extent nodes into the same
extent tree, racing with the destroy and triggering f2fs_bug_on() in
__destroy_extent_node(). The scenario is as follows:
drop inode writeback
- iput
- f2fs_drop_inode // I_SYNC set
- f2fs_destroy_extent_node
- __destroy_extent_node
- while (node_cnt) {
write_lock(&et->lock)
__free_extent_tree
write_unlock(&et->lock)
- __writeback_single_inode
- f2fs_outplace_write_data
- f2fs_update_read_extent_cache
- __update_extent_tree_range
// FI_NO_EXTENT not set,
// insert new extent node
} // node_cnt == 0, exit while
- f2fs_bug_on(node_cnt) // node_cnt > 0
Additionally, __update_extent_tree_range() only checks FI_NO_EXTENT for
EX_READ type, leaving EX_BLOCK_AGE updates completely unprotected.
This patch set FI_NO_EXTENT under et->lock in __destroy_extent_node(),
consistent with other callers (__update_extent_tree_range and
__drop_extent_tree) and check FI_NO_EXTENT for both EX_READ and
EX_BLOCK_AGE tree.
Fixes: 3fc5d5a182f6 ("f2fs: fix to shrink read extent node in batches")
Cc: stable@vger.kernel.org
Signed-off-by: Yongpeng Yang <yangyongpeng@xiaomi.com>
---
fs/f2fs/extent_cache.c | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c
index 0ed84cc065a7..87169fd29d89 100644
--- a/fs/f2fs/extent_cache.c
+++ b/fs/f2fs/extent_cache.c
@@ -119,9 +119,10 @@ static bool __may_extent_tree(struct inode *inode, enum extent_type type)
if (!__init_may_extent_tree(inode, type))
return false;
+ if (is_inode_flag_set(inode, FI_NO_EXTENT))
+ return false;
+
if (type == EX_READ) {
- if (is_inode_flag_set(inode, FI_NO_EXTENT))
- return false;
if (is_inode_flag_set(inode, FI_COMPRESSED_FILE) &&
!f2fs_sb_has_readonly(F2FS_I_SB(inode)))
return false;
@@ -644,6 +645,8 @@ static unsigned int __destroy_extent_node(struct inode *inode,
while (atomic_read(&et->node_cnt)) {
write_lock(&et->lock);
+ if (!is_inode_flag_set(inode, FI_NO_EXTENT))
+ set_inode_flag(inode, FI_NO_EXTENT);
node_cnt += __free_extent_tree(sbi, et, nr_shrink);
write_unlock(&et->lock);
}
@@ -688,12 +691,12 @@ static void __update_extent_tree_range(struct inode *inode,
write_lock(&et->lock);
- if (type == EX_READ) {
- if (is_inode_flag_set(inode, FI_NO_EXTENT)) {
- write_unlock(&et->lock);
- return;
- }
+ if (is_inode_flag_set(inode, FI_NO_EXTENT)) {
+ write_unlock(&et->lock);
+ return;
+ }
+ if (type == EX_READ) {
prev = et->largest;
dei.len = 0;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH] f2fs: fix node_cnt race between extent node destroy and writeback
2026-04-03 14:40 [PATCH] f2fs: fix node_cnt race between extent node destroy and writeback Yongpeng Yang
@ 2026-04-13 11:23 ` Chao Yu
2026-04-15 16:50 ` [f2fs-dev] " patchwork-bot+f2fs
2026-04-17 9:00 ` Chao Yu
2 siblings, 0 replies; 7+ messages in thread
From: Chao Yu @ 2026-04-13 11:23 UTC (permalink / raw)
To: Yongpeng Yang, Jaegeuk Kim; +Cc: chao, linux-f2fs-devel, Yongpeng Yang, stable
On 4/3/2026 10:40 PM, Yongpeng Yang wrote:
> From: Yongpeng Yang <yangyongpeng@xiaomi.com>
>
> f2fs_destroy_extent_node() does not set FI_NO_EXTENT before clearing
> extent nodes. When called from f2fs_drop_inode() with I_SYNC set,
> concurrent kworker writeback can insert new extent nodes into the same
> extent tree, racing with the destroy and triggering f2fs_bug_on() in
> __destroy_extent_node(). The scenario is as follows:
>
> drop inode writeback
> - iput
> - f2fs_drop_inode // I_SYNC set
> - f2fs_destroy_extent_node
> - __destroy_extent_node
> - while (node_cnt) {
> write_lock(&et->lock)
> __free_extent_tree
> write_unlock(&et->lock)
> - __writeback_single_inode
> - f2fs_outplace_write_data
> - f2fs_update_read_extent_cache
> - __update_extent_tree_range
> // FI_NO_EXTENT not set,
> // insert new extent node
> } // node_cnt == 0, exit while
> - f2fs_bug_on(node_cnt) // node_cnt > 0
>
> Additionally, __update_extent_tree_range() only checks FI_NO_EXTENT for
> EX_READ type, leaving EX_BLOCK_AGE updates completely unprotected.
>
> This patch set FI_NO_EXTENT under et->lock in __destroy_extent_node(),
> consistent with other callers (__update_extent_tree_range and
> __drop_extent_tree) and check FI_NO_EXTENT for both EX_READ and
> EX_BLOCK_AGE tree.
>
> Fixes: 3fc5d5a182f6 ("f2fs: fix to shrink read extent node in batches")
> Cc: stable@vger.kernel.org
> Signed-off-by: Yongpeng Yang <yangyongpeng@xiaomi.com>
Reviewed-by: Chao Yu <chao@kernel.org>
Thanks,
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [f2fs-dev] [PATCH] f2fs: fix node_cnt race between extent node destroy and writeback
2026-04-03 14:40 [PATCH] f2fs: fix node_cnt race between extent node destroy and writeback Yongpeng Yang
2026-04-13 11:23 ` Chao Yu
@ 2026-04-15 16:50 ` patchwork-bot+f2fs
2026-04-17 9:00 ` Chao Yu
2 siblings, 0 replies; 7+ messages in thread
From: patchwork-bot+f2fs @ 2026-04-15 16:50 UTC (permalink / raw)
To: Yongpeng Yang; +Cc: chao, jaegeuk, yangyongpeng, stable, linux-f2fs-devel
Hello:
This patch was applied to jaegeuk/f2fs.git (dev)
by Jaegeuk Kim <jaegeuk@kernel.org>:
On Fri, 3 Apr 2026 22:40:17 +0800 you wrote:
> From: Yongpeng Yang <yangyongpeng@xiaomi.com>
>
> f2fs_destroy_extent_node() does not set FI_NO_EXTENT before clearing
> extent nodes. When called from f2fs_drop_inode() with I_SYNC set,
> concurrent kworker writeback can insert new extent nodes into the same
> extent tree, racing with the destroy and triggering f2fs_bug_on() in
> __destroy_extent_node(). The scenario is as follows:
>
> [...]
Here is the summary with links:
- [f2fs-dev] f2fs: fix node_cnt race between extent node destroy and writeback
https://git.kernel.org/jaegeuk/f2fs/c/ed78aeebef05
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] f2fs: fix node_cnt race between extent node destroy and writeback
2026-04-03 14:40 [PATCH] f2fs: fix node_cnt race between extent node destroy and writeback Yongpeng Yang
2026-04-13 11:23 ` Chao Yu
2026-04-15 16:50 ` [f2fs-dev] " patchwork-bot+f2fs
@ 2026-04-17 9:00 ` Chao Yu
2026-04-17 13:26 ` [f2fs-dev] " Yongpeng Yang
2 siblings, 1 reply; 7+ messages in thread
From: Chao Yu @ 2026-04-17 9:00 UTC (permalink / raw)
To: Yongpeng Yang, Jaegeuk Kim; +Cc: chao, linux-f2fs-devel, Yongpeng Yang, stable
On 4/3/26 22:40, Yongpeng Yang wrote:
> From: Yongpeng Yang <yangyongpeng@xiaomi.com>
>
> f2fs_destroy_extent_node() does not set FI_NO_EXTENT before clearing
> extent nodes. When called from f2fs_drop_inode() with I_SYNC set,
> concurrent kworker writeback can insert new extent nodes into the same
> extent tree, racing with the destroy and triggering f2fs_bug_on() in
> __destroy_extent_node(). The scenario is as follows:
>
> drop inode writeback
> - iput
> - f2fs_drop_inode // I_SYNC set
> - f2fs_destroy_extent_node
> - __destroy_extent_node
> - while (node_cnt) {
> write_lock(&et->lock)
> __free_extent_tree
> write_unlock(&et->lock)
> - __writeback_single_inode
> - f2fs_outplace_write_data
> - f2fs_update_read_extent_cache
> - __update_extent_tree_range
> // FI_NO_EXTENT not set,
> // insert new extent node
> } // node_cnt == 0, exit while
> - f2fs_bug_on(node_cnt) // node_cnt > 0
>
> Additionally, __update_extent_tree_range() only checks FI_NO_EXTENT for
> EX_READ type, leaving EX_BLOCK_AGE updates completely unprotected.
>
> This patch set FI_NO_EXTENT under et->lock in __destroy_extent_node(),
> consistent with other callers (__update_extent_tree_range and
> __drop_extent_tree) and check FI_NO_EXTENT for both EX_READ and
> EX_BLOCK_AGE tree.
I suffered below test failure, then I bisect to this change.
generic/475 84s ... [failed, exit status 1]- output mismatch (see /share/git/fstests/results//generic/475.out.bad)
--- tests/generic/475.out 2025-01-12 21:57:40.279440664 +0800
+++ /share/git/fstests/results//generic/475.out.bad 2026-04-17 12:08:28.000000000 +0800
@@ -1,2 +1,6 @@
QA output created by 475
Silence is golden.
+mount: /mnt/scratch_f2fs: mount system call failed: Structure needs cleaning.
+ dmesg(1) may have more information after failed mount system call.
+mount failed
+(see /share/git/fstests/results//generic/475.full for details)
...
(Run 'diff -u /share/git/fstests/tests/generic/475.out /share/git/fstests/results//generic/475.out.bad' to see the entire diff)
generic/388 73s ... [failed, exit status 1]- output mismatch (see /share/git/fstests/results//generic/388.out.bad)
--- tests/generic/388.out 2025-01-12 21:57:40.275440602 +0800
+++ /share/git/fstests/results//generic/388.out.bad 2026-04-17 11:58:05.000000000 +0800
@@ -1,2 +1,6 @@
QA output created by 388
Silence is golden.
+mount: /mnt/scratch_f2fs: mount system call failed: Structure needs cleaning.
+ dmesg(1) may have more information after failed mount system call.
+cycle mount failed
+(see /share/git/fstests/results//generic/388.full for details)
...
(Run 'diff -u /share/git/fstests/tests/generic/388.out /share/git/fstests/results//generic/388.out.bad' to see the entire diff)
F2FS-fs (dm-0): sanity_check_extent_cache: inode (ino=1761) extent info [220057, 57, 6] is incorrect, run fsck to fix
I suspect we may miss any extent updates after we set FI_NO_EXTENT in
__destroy_extent_node(), result in failing in sanity_check_extent_cache().
Can we just relocate f2fs_bug_on(node_cnt) rather than complicated change?
Thoughts?
Thanks,
>
> Fixes: 3fc5d5a182f6 ("f2fs: fix to shrink read extent node in batches")
> Cc: stable@vger.kernel.org
> Signed-off-by: Yongpeng Yang <yangyongpeng@xiaomi.com>
> ---
> fs/f2fs/extent_cache.c | 17 ++++++++++-------
> 1 file changed, 10 insertions(+), 7 deletions(-)
>
> diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c
> index 0ed84cc065a7..87169fd29d89 100644
> --- a/fs/f2fs/extent_cache.c
> +++ b/fs/f2fs/extent_cache.c
> @@ -119,9 +119,10 @@ static bool __may_extent_tree(struct inode *inode, enum extent_type type)
> if (!__init_may_extent_tree(inode, type))
> return false;
>
> + if (is_inode_flag_set(inode, FI_NO_EXTENT))
> + return false;
> +
> if (type == EX_READ) {
> - if (is_inode_flag_set(inode, FI_NO_EXTENT))
> - return false;
> if (is_inode_flag_set(inode, FI_COMPRESSED_FILE) &&
> !f2fs_sb_has_readonly(F2FS_I_SB(inode)))
> return false;
> @@ -644,6 +645,8 @@ static unsigned int __destroy_extent_node(struct inode *inode,
>
> while (atomic_read(&et->node_cnt)) {
> write_lock(&et->lock);
> + if (!is_inode_flag_set(inode, FI_NO_EXTENT))
> + set_inode_flag(inode, FI_NO_EXTENT);
> node_cnt += __free_extent_tree(sbi, et, nr_shrink);
> write_unlock(&et->lock);
> }
> @@ -688,12 +691,12 @@ static void __update_extent_tree_range(struct inode *inode,
>
> write_lock(&et->lock);
>
> - if (type == EX_READ) {
> - if (is_inode_flag_set(inode, FI_NO_EXTENT)) {
> - write_unlock(&et->lock);
> - return;
> - }
> + if (is_inode_flag_set(inode, FI_NO_EXTENT)) {
> + write_unlock(&et->lock);
> + return;
> + }
>
> + if (type == EX_READ) {
> prev = et->largest;
> dei.len = 0;
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [f2fs-dev] [PATCH] f2fs: fix node_cnt race between extent node destroy and writeback
2026-04-17 9:00 ` Chao Yu
@ 2026-04-17 13:26 ` Yongpeng Yang
2026-04-18 0:51 ` Chao Yu
0 siblings, 1 reply; 7+ messages in thread
From: Yongpeng Yang @ 2026-04-17 13:26 UTC (permalink / raw)
To: Chao Yu, Jaegeuk Kim; +Cc: Yongpeng Yang, stable, linux-f2fs-devel
On 4/17/26 17:00, Chao Yu via Linux-f2fs-devel wrote:
> On 4/3/26 22:40, Yongpeng Yang wrote:
>> From: Yongpeng Yang <yangyongpeng@xiaomi.com>
>>
>> f2fs_destroy_extent_node() does not set FI_NO_EXTENT before clearing
>> extent nodes. When called from f2fs_drop_inode() with I_SYNC set,
>> concurrent kworker writeback can insert new extent nodes into the same
>> extent tree, racing with the destroy and triggering f2fs_bug_on() in
>> __destroy_extent_node(). The scenario is as follows:
>>
>> drop inode writeback
>> - iput
>> - f2fs_drop_inode // I_SYNC set
>> - f2fs_destroy_extent_node
>> - __destroy_extent_node
>> - while (node_cnt) {
>> write_lock(&et->lock)
>> __free_extent_tree
>> write_unlock(&et->lock)
>> - __writeback_single_inode
>> - f2fs_outplace_write_data
>> - f2fs_update_read_extent_cache
>> - __update_extent_tree_range
>> // FI_NO_EXTENT not set,
>> // insert new extent node
>> } // node_cnt == 0, exit while
>> - f2fs_bug_on(node_cnt) // node_cnt > 0
>>
>> Additionally, __update_extent_tree_range() only checks FI_NO_EXTENT for
>> EX_READ type, leaving EX_BLOCK_AGE updates completely unprotected.
>>
>> This patch set FI_NO_EXTENT under et->lock in __destroy_extent_node(),
>> consistent with other callers (__update_extent_tree_range and
>> __drop_extent_tree) and check FI_NO_EXTENT for both EX_READ and
>> EX_BLOCK_AGE tree.
>
> I suffered below test failure, then I bisect to this change.
>
> generic/475 84s ... [failed, exit status 1]- output mismatch (see /
> share/git/fstests/results//generic/475.out.bad)
> --- tests/generic/475.out 2025-01-12 21:57:40.279440664 +0800
> +++ /share/git/fstests/results//generic/475.out.bad 2026-04-17
> 12:08:28.000000000 +0800
> @@ -1,2 +1,6 @@
> QA output created by 475
> Silence is golden.
> +mount: /mnt/scratch_f2fs: mount system call failed: Structure needs
> cleaning.
> + dmesg(1) may have more information after failed mount system
> call.
> +mount failed
> +(see /share/git/fstests/results//generic/475.full for details)
> ...
> (Run 'diff -u /share/git/fstests/tests/generic/475.out /share/git/
> fstests/results//generic/475.out.bad' to see the entire diff)
>
>
> generic/388 73s ... [failed, exit status 1]- output mismatch (see /
> share/git/fstests/results//generic/388.out.bad)
> --- tests/generic/388.out 2025-01-12 21:57:40.275440602 +0800
> +++ /share/git/fstests/results//generic/388.out.bad 2026-04-17
> 11:58:05.000000000 +0800
> @@ -1,2 +1,6 @@
> QA output created by 388
> Silence is golden.
> +mount: /mnt/scratch_f2fs: mount system call failed: Structure needs
> cleaning.
> + dmesg(1) may have more information after failed mount system
> call.
> +cycle mount failed
> +(see /share/git/fstests/results//generic/388.full for details)
> ...
> (Run 'diff -u /share/git/fstests/tests/generic/388.out /share/git/
> fstests/results//generic/388.out.bad' to see the entire diff)
>
>
> F2FS-fs (dm-0): sanity_check_extent_cache: inode (ino=1761) extent
> info [220057, 57, 6] is incorrect, run fsck to fix
>
> I suspect we may miss any extent updates after we set FI_NO_EXTENT in
> __destroy_extent_node(), result in failing in sanity_check_extent_cache().
>
> Can we just relocate f2fs_bug_on(node_cnt) rather than complicated change?
> Thoughts?
Oh, I overlooked largest extent. How about relocate
f2fs_bug_on(node_cnt) to __destroy_extent_tree?
static void __destroy_extent_tree(struct inode *inode, enum extent_type
type)
/* free all extent info belong to this extent tree */
node_cnt = __destroy_extent_node(inode, type);
+ f2fs_bug_on(sbi, atomic_read(&et->node_cnt));
Thanks
Yongpeng,
>
> Thanks,
>
>>
>> Fixes: 3fc5d5a182f6 ("f2fs: fix to shrink read extent node in batches")
>> Cc: stable@vger.kernel.org
>> Signed-off-by: Yongpeng Yang <yangyongpeng@xiaomi.com>
>> ---
>> fs/f2fs/extent_cache.c | 17 ++++++++++-------
>> 1 file changed, 10 insertions(+), 7 deletions(-)
>>
>> diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c
>> index 0ed84cc065a7..87169fd29d89 100644
>> --- a/fs/f2fs/extent_cache.c
>> +++ b/fs/f2fs/extent_cache.c
>> @@ -119,9 +119,10 @@ static bool __may_extent_tree(struct inode
>> *inode, enum extent_type type)
>> if (!__init_may_extent_tree(inode, type))
>> return false;
>> + if (is_inode_flag_set(inode, FI_NO_EXTENT))
>> + return false;
>> +
>> if (type == EX_READ) {
>> - if (is_inode_flag_set(inode, FI_NO_EXTENT))
>> - return false;
>> if (is_inode_flag_set(inode, FI_COMPRESSED_FILE) &&
>> !f2fs_sb_has_readonly(F2FS_I_SB(inode)))
>> return false;
>> @@ -644,6 +645,8 @@ static unsigned int __destroy_extent_node(struct
>> inode *inode,
>> while (atomic_read(&et->node_cnt)) {
>> write_lock(&et->lock);
>> + if (!is_inode_flag_set(inode, FI_NO_EXTENT))
>> + set_inode_flag(inode, FI_NO_EXTENT);
>> node_cnt += __free_extent_tree(sbi, et, nr_shrink);
>> write_unlock(&et->lock);
>> }
>> @@ -688,12 +691,12 @@ static void __update_extent_tree_range(struct
>> inode *inode,
>> write_lock(&et->lock);
>> - if (type == EX_READ) {
>> - if (is_inode_flag_set(inode, FI_NO_EXTENT)) {
>> - write_unlock(&et->lock);
>> - return;
>> - }
>> + if (is_inode_flag_set(inode, FI_NO_EXTENT)) {
>> + write_unlock(&et->lock);
>> + return;
>> + }
>> + if (type == EX_READ) {
>> prev = et->largest;
>> dei.len = 0;
>>
>
>
>
> _______________________________________________
> Linux-f2fs-devel mailing list
> Linux-f2fs-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [f2fs-dev] [PATCH] f2fs: fix node_cnt race between extent node destroy and writeback
2026-04-17 13:26 ` [f2fs-dev] " Yongpeng Yang
@ 2026-04-18 0:51 ` Chao Yu
2026-04-18 16:29 ` Yongpeng Yang
0 siblings, 1 reply; 7+ messages in thread
From: Chao Yu @ 2026-04-18 0:51 UTC (permalink / raw)
To: Yongpeng Yang, Jaegeuk Kim; +Cc: chao, Yongpeng Yang, stable, linux-f2fs-devel
On 4/17/26 21:26, Yongpeng Yang wrote:
>
> On 4/17/26 17:00, Chao Yu via Linux-f2fs-devel wrote:
>> On 4/3/26 22:40, Yongpeng Yang wrote:
>>> From: Yongpeng Yang <yangyongpeng@xiaomi.com>
>>>
>>> f2fs_destroy_extent_node() does not set FI_NO_EXTENT before clearing
>>> extent nodes. When called from f2fs_drop_inode() with I_SYNC set,
>>> concurrent kworker writeback can insert new extent nodes into the same
>>> extent tree, racing with the destroy and triggering f2fs_bug_on() in
>>> __destroy_extent_node(). The scenario is as follows:
>>>
>>> drop inode writeback
>>> - iput
>>> - f2fs_drop_inode // I_SYNC set
>>> - f2fs_destroy_extent_node
>>> - __destroy_extent_node
>>> - while (node_cnt) {
>>> write_lock(&et->lock)
>>> __free_extent_tree
>>> write_unlock(&et->lock)
>>> - __writeback_single_inode
>>> - f2fs_outplace_write_data
>>> - f2fs_update_read_extent_cache
>>> - __update_extent_tree_range
>>> // FI_NO_EXTENT not set,
>>> // insert new extent node
>>> } // node_cnt == 0, exit while
>>> - f2fs_bug_on(node_cnt) // node_cnt > 0
>>>
>>> Additionally, __update_extent_tree_range() only checks FI_NO_EXTENT for
>>> EX_READ type, leaving EX_BLOCK_AGE updates completely unprotected.
>>>
>>> This patch set FI_NO_EXTENT under et->lock in __destroy_extent_node(),
>>> consistent with other callers (__update_extent_tree_range and
>>> __drop_extent_tree) and check FI_NO_EXTENT for both EX_READ and
>>> EX_BLOCK_AGE tree.
>>
>> I suffered below test failure, then I bisect to this change.
>>
>> generic/475 84s ... [failed, exit status 1]- output mismatch (see /
>> share/git/fstests/results//generic/475.out.bad)
>> --- tests/generic/475.out 2025-01-12 21:57:40.279440664 +0800
>> +++ /share/git/fstests/results//generic/475.out.bad 2026-04-17
>> 12:08:28.000000000 +0800
>> @@ -1,2 +1,6 @@
>> QA output created by 475
>> Silence is golden.
>> +mount: /mnt/scratch_f2fs: mount system call failed: Structure needs
>> cleaning.
>> + dmesg(1) may have more information after failed mount system
>> call.
>> +mount failed
>> +(see /share/git/fstests/results//generic/475.full for details)
>> ...
>> (Run 'diff -u /share/git/fstests/tests/generic/475.out /share/git/
>> fstests/results//generic/475.out.bad' to see the entire diff)
>>
>>
>> generic/388 73s ... [failed, exit status 1]- output mismatch (see /
>> share/git/fstests/results//generic/388.out.bad)
>> --- tests/generic/388.out 2025-01-12 21:57:40.275440602 +0800
>> +++ /share/git/fstests/results//generic/388.out.bad 2026-04-17
>> 11:58:05.000000000 +0800
>> @@ -1,2 +1,6 @@
>> QA output created by 388
>> Silence is golden.
>> +mount: /mnt/scratch_f2fs: mount system call failed: Structure needs
>> cleaning.
>> + dmesg(1) may have more information after failed mount system
>> call.
>> +cycle mount failed
>> +(see /share/git/fstests/results//generic/388.full for details)
>> ...
>> (Run 'diff -u /share/git/fstests/tests/generic/388.out /share/git/
>> fstests/results//generic/388.out.bad' to see the entire diff)
>>
>>
>> F2FS-fs (dm-0): sanity_check_extent_cache: inode (ino=1761) extent
>> info [220057, 57, 6] is incorrect, run fsck to fix
>>
>> I suspect we may miss any extent updates after we set FI_NO_EXTENT in
>> __destroy_extent_node(), result in failing in sanity_check_extent_cache().
>>
>> Can we just relocate f2fs_bug_on(node_cnt) rather than complicated change?
>> Thoughts?
>
> Oh, I overlooked largest extent. How about relocate
> f2fs_bug_on(node_cnt) to __destroy_extent_tree?
>
> static void __destroy_extent_tree(struct inode *inode, enum extent_type
> type)
>
> /* free all extent info belong to this extent tree */
> node_cnt = __destroy_extent_node(inode, type);
> + f2fs_bug_on(sbi, atomic_read(&et->node_cnt));
/* free all extent info belong to this extent tree */
node_cnt = __destroy_extent_node(inode, type);
/* delete extent tree entry in radix tree */
mutex_lock(&eti->extent_tree_lock);
f2fs_bug_on(sbi, atomic_read(&et->node_cnt)); <---
Oh, it has already checked node_cnt, so, maybe we can just remove the check in
__destroy_extent_node()?
Thanks,
>
> Thanks
> Yongpeng,
>
>>
>> Thanks,
>>
>>>
>>> Fixes: 3fc5d5a182f6 ("f2fs: fix to shrink read extent node in batches")
>>> Cc: stable@vger.kernel.org
>>> Signed-off-by: Yongpeng Yang <yangyongpeng@xiaomi.com>
>>> ---
>>> fs/f2fs/extent_cache.c | 17 ++++++++++-------
>>> 1 file changed, 10 insertions(+), 7 deletions(-)
>>>
>>> diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c
>>> index 0ed84cc065a7..87169fd29d89 100644
>>> --- a/fs/f2fs/extent_cache.c
>>> +++ b/fs/f2fs/extent_cache.c
>>> @@ -119,9 +119,10 @@ static bool __may_extent_tree(struct inode
>>> *inode, enum extent_type type)
>>> if (!__init_may_extent_tree(inode, type))
>>> return false;
>>> + if (is_inode_flag_set(inode, FI_NO_EXTENT))
>>> + return false;
>>> +
>>> if (type == EX_READ) {
>>> - if (is_inode_flag_set(inode, FI_NO_EXTENT))
>>> - return false;
>>> if (is_inode_flag_set(inode, FI_COMPRESSED_FILE) &&
>>> !f2fs_sb_has_readonly(F2FS_I_SB(inode)))
>>> return false;
>>> @@ -644,6 +645,8 @@ static unsigned int __destroy_extent_node(struct
>>> inode *inode,
>>> while (atomic_read(&et->node_cnt)) {
>>> write_lock(&et->lock);
>>> + if (!is_inode_flag_set(inode, FI_NO_EXTENT))
>>> + set_inode_flag(inode, FI_NO_EXTENT);
>>> node_cnt += __free_extent_tree(sbi, et, nr_shrink);
>>> write_unlock(&et->lock);
>>> }
>>> @@ -688,12 +691,12 @@ static void __update_extent_tree_range(struct
>>> inode *inode,
>>> write_lock(&et->lock);
>>> - if (type == EX_READ) {
>>> - if (is_inode_flag_set(inode, FI_NO_EXTENT)) {
>>> - write_unlock(&et->lock);
>>> - return;
>>> - }
>>> + if (is_inode_flag_set(inode, FI_NO_EXTENT)) {
>>> + write_unlock(&et->lock);
>>> + return;
>>> + }
>>> + if (type == EX_READ) {
>>> prev = et->largest;
>>> dei.len = 0;
>>>
>>
>>
>>
>> _______________________________________________
>> Linux-f2fs-devel mailing list
>> Linux-f2fs-devel@lists.sourceforge.net
>> https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [f2fs-dev] [PATCH] f2fs: fix node_cnt race between extent node destroy and writeback
2026-04-18 0:51 ` Chao Yu
@ 2026-04-18 16:29 ` Yongpeng Yang
0 siblings, 0 replies; 7+ messages in thread
From: Yongpeng Yang @ 2026-04-18 16:29 UTC (permalink / raw)
To: Chao Yu, Yongpeng Yang, Jaegeuk Kim
Cc: Yongpeng Yang, stable, linux-f2fs-devel
On 4/18/26 8:51 AM, Chao Yu via Linux-f2fs-devel wrote:
> On 4/17/26 21:26, Yongpeng Yang wrote:
>>
>> On 4/17/26 17:00, Chao Yu via Linux-f2fs-devel wrote:
>>> On 4/3/26 22:40, Yongpeng Yang wrote:
>>>> From: Yongpeng Yang <yangyongpeng@xiaomi.com>
>>>>
>>>> f2fs_destroy_extent_node() does not set FI_NO_EXTENT before clearing
>>>> extent nodes. When called from f2fs_drop_inode() with I_SYNC set,
>>>> concurrent kworker writeback can insert new extent nodes into the same
>>>> extent tree, racing with the destroy and triggering f2fs_bug_on() in
>>>> __destroy_extent_node(). The scenario is as follows:
>>>>
>>>> drop inode writeback
>>>> - iput
>>>> - f2fs_drop_inode // I_SYNC set
>>>> - f2fs_destroy_extent_node
>>>> - __destroy_extent_node
>>>> - while (node_cnt) {
>>>> write_lock(&et->lock)
>>>> __free_extent_tree
>>>> write_unlock(&et->lock)
>>>> - __writeback_single_inode
>>>> - f2fs_outplace_write_data
>>>> -
>>>> f2fs_update_read_extent_cache
>>>> -
>>>> __update_extent_tree_range
>>>> // FI_NO_EXTENT not set,
>>>> // insert new extent node
>>>> } // node_cnt == 0, exit while
>>>> - f2fs_bug_on(node_cnt) // node_cnt > 0
>>>>
>>>> Additionally, __update_extent_tree_range() only checks FI_NO_EXTENT for
>>>> EX_READ type, leaving EX_BLOCK_AGE updates completely unprotected.
>>>>
>>>> This patch set FI_NO_EXTENT under et->lock in __destroy_extent_node(),
>>>> consistent with other callers (__update_extent_tree_range and
>>>> __drop_extent_tree) and check FI_NO_EXTENT for both EX_READ and
>>>> EX_BLOCK_AGE tree.
>>>
>>> I suffered below test failure, then I bisect to this change.
>>>
>>> generic/475 84s ... [failed, exit status 1]- output mismatch
>>> (see /
>>> share/git/fstests/results//generic/475.out.bad)
>>> --- tests/generic/475.out 2025-01-12 21:57:40.279440664 +0800
>>> +++ /share/git/fstests/results//generic/475.out.bad 2026-04-17
>>> 12:08:28.000000000 +0800
>>> @@ -1,2 +1,6 @@
>>> QA output created by 475
>>> Silence is golden.
>>> +mount: /mnt/scratch_f2fs: mount system call failed: Structure
>>> needs
>>> cleaning.
>>> + dmesg(1) may have more information after failed mount
>>> system
>>> call.
>>> +mount failed
>>> +(see /share/git/fstests/results//generic/475.full for details)
>>> ...
>>> (Run 'diff -u /share/git/fstests/tests/generic/475.out /share/git/
>>> fstests/results//generic/475.out.bad' to see the entire diff)
>>>
>>>
>>> generic/388 73s ... [failed, exit status 1]- output mismatch
>>> (see /
>>> share/git/fstests/results//generic/388.out.bad)
>>> --- tests/generic/388.out 2025-01-12 21:57:40.275440602 +0800
>>> +++ /share/git/fstests/results//generic/388.out.bad 2026-04-17
>>> 11:58:05.000000000 +0800
>>> @@ -1,2 +1,6 @@
>>> QA output created by 388
>>> Silence is golden.
>>> +mount: /mnt/scratch_f2fs: mount system call failed: Structure
>>> needs
>>> cleaning.
>>> + dmesg(1) may have more information after failed mount
>>> system
>>> call.
>>> +cycle mount failed
>>> +(see /share/git/fstests/results//generic/388.full for details)
>>> ...
>>> (Run 'diff -u /share/git/fstests/tests/generic/388.out /share/git/
>>> fstests/results//generic/388.out.bad' to see the entire diff)
>>>
>>>
>>> F2FS-fs (dm-0): sanity_check_extent_cache: inode (ino=1761) extent
>>> info [220057, 57, 6] is incorrect, run fsck to fix
>>>
>>> I suspect we may miss any extent updates after we set FI_NO_EXTENT in
>>> __destroy_extent_node(), result in failing in
>>> sanity_check_extent_cache().
>>>
>>> Can we just relocate f2fs_bug_on(node_cnt) rather than complicated
>>> change?
>>> Thoughts?
>>
>> Oh, I overlooked largest extent. How about relocate
>> f2fs_bug_on(node_cnt) to __destroy_extent_tree?
>>
>> static void __destroy_extent_tree(struct inode *inode, enum extent_type
>> type)
>>
>> /* free all extent info belong to this extent tree */
>> node_cnt = __destroy_extent_node(inode, type);
>> + f2fs_bug_on(sbi, atomic_read(&et->node_cnt));
>
> /* free all extent info belong to this extent tree */
> node_cnt = __destroy_extent_node(inode, type);
>
> /* delete extent tree entry in radix tree */
> mutex_lock(&eti->extent_tree_lock);
> f2fs_bug_on(sbi, atomic_read(&et->node_cnt)); <---
>
> Oh, it has already checked node_cnt, so, maybe we can just remove the
> check in
> __destroy_extent_node()?
Yes. BTW, is it correct to remove the call to f2fs_destroy_extent_node()
in f2fs_drop_inode()? It seems this call is unnecessary, since
f2fs_evict_inode() will eventually delete all extent nodes properly.
Thanks
Yongpeng,
>
> Thanks,
>
>
>>
>> Thanks
>> Yongpeng,
>>
>>>
>>> Thanks,
>>>
>>>>
>>>> Fixes: 3fc5d5a182f6 ("f2fs: fix to shrink read extent node in batches")
>>>> Cc: stable@vger.kernel.org
>>>> Signed-off-by: Yongpeng Yang <yangyongpeng@xiaomi.com>
>>>> ---
>>>> fs/f2fs/extent_cache.c | 17 ++++++++++-------
>>>> 1 file changed, 10 insertions(+), 7 deletions(-)
>>>>
>>>> diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c
>>>> index 0ed84cc065a7..87169fd29d89 100644
>>>> --- a/fs/f2fs/extent_cache.c
>>>> +++ b/fs/f2fs/extent_cache.c
>>>> @@ -119,9 +119,10 @@ static bool __may_extent_tree(struct inode
>>>> *inode, enum extent_type type)
>>>> if (!__init_may_extent_tree(inode, type))
>>>> return false;
>>>> + if (is_inode_flag_set(inode, FI_NO_EXTENT))
>>>> + return false;
>>>> +
>>>> if (type == EX_READ) {
>>>> - if (is_inode_flag_set(inode, FI_NO_EXTENT))
>>>> - return false;
>>>> if (is_inode_flag_set(inode, FI_COMPRESSED_FILE) &&
>>>> !f2fs_sb_has_readonly(F2FS_I_SB(inode)))
>>>> return false;
>>>> @@ -644,6 +645,8 @@ static unsigned int __destroy_extent_node(struct
>>>> inode *inode,
>>>> while (atomic_read(&et->node_cnt)) {
>>>> write_lock(&et->lock);
>>>> + if (!is_inode_flag_set(inode, FI_NO_EXTENT))
>>>> + set_inode_flag(inode, FI_NO_EXTENT);
>>>> node_cnt += __free_extent_tree(sbi, et, nr_shrink);
>>>> write_unlock(&et->lock);
>>>> }
>>>> @@ -688,12 +691,12 @@ static void __update_extent_tree_range(struct
>>>> inode *inode,
>>>> write_lock(&et->lock);
>>>> - if (type == EX_READ) {
>>>> - if (is_inode_flag_set(inode, FI_NO_EXTENT)) {
>>>> - write_unlock(&et->lock);
>>>> - return;
>>>> - }
>>>> + if (is_inode_flag_set(inode, FI_NO_EXTENT)) {
>>>> + write_unlock(&et->lock);
>>>> + return;
>>>> + }
>>>> + if (type == EX_READ) {
>>>> prev = et->largest;
>>>> dei.len = 0;
>>>
>>>
>>>
>>> _______________________________________________
>>> Linux-f2fs-devel mailing list
>>> Linux-f2fs-devel@lists.sourceforge.net
>>> https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
>>
>
>
>
> _______________________________________________
> Linux-f2fs-devel mailing list
> Linux-f2fs-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-04-18 16:29 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-03 14:40 [PATCH] f2fs: fix node_cnt race between extent node destroy and writeback Yongpeng Yang
2026-04-13 11:23 ` Chao Yu
2026-04-15 16:50 ` [f2fs-dev] " patchwork-bot+f2fs
2026-04-17 9:00 ` Chao Yu
2026-04-17 13:26 ` [f2fs-dev] " Yongpeng Yang
2026-04-18 0:51 ` Chao Yu
2026-04-18 16:29 ` Yongpeng Yang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox