public inbox for stable@vger.kernel.org
 help / color / mirror / Atom feed
* [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