* [f2fs-dev] [PATCH] f2fs: quota: fix quota flush failure during filesystem freeze
@ 2026-09-08 3:48 Jianan Huang
2026-09-09 11:42 ` Chao Yu via Linux-f2fs-devel
0 siblings, 1 reply; 5+ messages in thread
From: Jianan Huang @ 2026-09-08 3:48 UTC (permalink / raw)
To: linux-f2fs-devel, chao, jaegeuk; +Cc: Jianan Huang
Quota flush retries can be exhausted during filesystem freeze.
freeze_super() and the quota checkpoint may race as below.
freeze_super() f2fs_ckpt
- down_write(&sb->s_umount)
- sb->s_writers.frozen = SB_FREEZE_PAGEFAULT
- sync_filesystem()
- f2fs_sync_fs()
- f2fs_issue_checkpoint()
- queue CP_SYNC -----------------> - block_operations()
- down_read_trylock(s_umount)
: fails; freeze holds write lock
- quota flush retries exhausted
- set CP_QUOTA_NEED_FSCK_FLAG
During the PAGEFAULT sync pass, freeze_super() holds s_umount for
write, but umount_lock_holder is not set until f2fs_freeze(). Thus
quota writeback tries to acquire s_umount again and can exhaust retries.
With checkpoint merge enabled, f2fs_issue_checkpoint() may dispatch
CP_SYNC to f2fs_ckpt. That thread also cannot acquire the freeze lock.
Split the F2FS-internal sync implementation from the super-operation
callback. For the VFS PAGEFAULT freeze sync, record current as holder
around the internal sync. This lets quota writeback use the existing
lock and makes CP_SYNC run directly in the freeze caller.
Convert F2FS-internal callers to the internal helper so they cannot be
mistaken for the freeze owner while the filesystem is frozen.
Fixes: eb85c2410d6f ("f2fs: quota: fix to avoid warning in dquot_writeback_dquots()")
Signed-off-by: Jianan Huang <jnhuang95@gmail.com>
---
fs/f2fs/f2fs.h | 2 +-
fs/f2fs/file.c | 8 ++++----
fs/f2fs/namei.c | 16 ++++++++--------
fs/f2fs/segment.c | 4 ++--
fs/f2fs/super.c | 25 ++++++++++++++++++++++---
5 files changed, 37 insertions(+), 18 deletions(-)
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 9940a6cecf1a..169792a08a82 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -3977,7 +3977,7 @@ void f2fs_quota_off_umount(struct super_block *sb);
void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag);
void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error);
int f2fs_commit_super(struct f2fs_sb_info *sbi, bool recover);
-int f2fs_sync_fs(struct super_block *sb, int sync);
+int __f2fs_sync_fs(struct super_block *sb, int sync);
int f2fs_sanity_check_ckpt(struct f2fs_sb_info *sbi);
/*
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index edc352569e87..83546a053656 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -408,7 +408,7 @@ static int f2fs_do_sync_file(struct file *file, loff_t start, loff_t end,
if (cp_reason) {
/* all the dirty node pages should be flushed for POR */
- ret = f2fs_sync_fs(inode->i_sb, 1);
+ ret = __f2fs_sync_fs(inode->i_sb, 1);
/*
* We've secured consistency through sync_fs. Following pino
@@ -2548,7 +2548,7 @@ int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag,
break;
case F2FS_GOING_DOWN_METASYNC:
/* do checkpoint only */
- ret = f2fs_sync_fs(sb, 1);
+ ret = __f2fs_sync_fs(sb, 1);
if (ret) {
if (ret == -EIO)
ret = 0;
@@ -2568,7 +2568,7 @@ int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag,
set_sbi_flag(sbi, SBI_CP_DISABLED_QUICK);
set_sbi_flag(sbi, SBI_IS_DIRTY);
/* do checkpoint only */
- ret = f2fs_sync_fs(sb, 1);
+ ret = __f2fs_sync_fs(sb, 1);
if (ret == -EIO)
ret = 0;
goto out;
@@ -2988,7 +2988,7 @@ static int f2fs_ioc_write_checkpoint(struct file *filp)
if (ret)
return ret;
- ret = f2fs_sync_fs(sbi->sb, 1);
+ ret = __f2fs_sync_fs(sbi->sb, 1);
mnt_drop_write_file(filp);
return ret;
diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index ff86ee07290d..5f6f5db9e849 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -403,7 +403,7 @@ static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir,
d_instantiate_new(dentry, inode);
if (IS_DIRSYNC(dir)) {
- err = f2fs_sync_fs(sbi->sb, 1);
+ err = __f2fs_sync_fs(sbi->sb, 1);
if (err)
return err;
}
@@ -459,7 +459,7 @@ static int f2fs_link(struct dentry *old_dentry, struct inode *dir,
d_instantiate(dentry, inode);
if (IS_DIRSYNC(dir)) {
- err = f2fs_sync_fs(sbi->sb, 1);
+ err = __f2fs_sync_fs(sbi->sb, 1);
if (err)
return err;
}
@@ -641,7 +641,7 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry)
d_invalidate(dentry);
if (IS_DIRSYNC(dir))
- err = f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1);
+ err = __f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1);
out:
trace_f2fs_unlink_exit(d_inode(dentry), err);
return err;
@@ -729,7 +729,7 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir,
ret = filemap_write_and_wait_range(inode->i_mapping, 0,
disk_link.len - 1);
if (!ret && IS_DIRSYNC(dir))
- err = f2fs_sync_fs(sbi->sb, 1);
+ err = __f2fs_sync_fs(sbi->sb, 1);
f2fs_balance_fs(sbi, true);
out:
@@ -787,7 +787,7 @@ static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
d_instantiate_new(dentry, inode);
if (IS_DIRSYNC(dir)) {
- err = f2fs_sync_fs(sbi->sb, 1);
+ err = __f2fs_sync_fs(sbi->sb, 1);
if (err)
return ERR_PTR(err);
}
@@ -845,7 +845,7 @@ static int f2fs_mknod(struct mnt_idmap *idmap, struct inode *dir,
d_instantiate_new(dentry, inode);
if (IS_DIRSYNC(dir)) {
- err = f2fs_sync_fs(sbi->sb, 1);
+ err = __f2fs_sync_fs(sbi->sb, 1);
if (err)
return err;
}
@@ -1148,7 +1148,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
f2fs_unlock_op(sbi, &lc);
if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) {
- err = f2fs_sync_fs(sbi->sb, 1);
+ err = __f2fs_sync_fs(sbi->sb, 1);
if (err)
return err;
}
@@ -1318,7 +1318,7 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
f2fs_unlock_op(sbi, &lc);
if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) {
- err = f2fs_sync_fs(sbi->sb, 1);
+ err = __f2fs_sync_fs(sbi->sb, 1);
if (err)
return err;
}
diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c
index 63b712d3d599..9a09f2525b70 100644
--- a/fs/f2fs/segment.c
+++ b/fs/f2fs/segment.c
@@ -550,7 +550,7 @@ void f2fs_balance_fs_bg(struct f2fs_sb_info *sbi, bool from_bg)
mutex_unlock(&sbi->flush_lock);
}
stat_inc_cp_call_count(sbi, BACKGROUND);
- f2fs_sync_fs(sbi->sb, 1);
+ __f2fs_sync_fs(sbi->sb, 1);
}
static int __submit_flush_wait(struct f2fs_sb_info *sbi,
@@ -3478,7 +3478,7 @@ int f2fs_allocate_pinning_section(struct f2fs_sb_info *sbi)
true, ZONED_PIN_SEC_REQUIRED_COUNT, true);
if (err)
return err;
- err = f2fs_sync_fs(sbi->sb, 1);
+ err = __f2fs_sync_fs(sbi->sb, 1);
if (!err) {
gc_required = false;
goto retry;
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index 1314b6ccced9..a82065146849 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -2131,7 +2131,7 @@ static void f2fs_put_super(struct super_block *sb)
}
}
-int f2fs_sync_fs(struct super_block *sb, int sync)
+int __f2fs_sync_fs(struct super_block *sb, int sync)
{
struct f2fs_sb_info *sbi = F2FS_SB(sb);
int err = 0;
@@ -2154,6 +2154,25 @@ int f2fs_sync_fs(struct super_block *sb, int sync)
return err;
}
+static int f2fs_sync_fs(struct super_block *sb, int sync)
+{
+ struct f2fs_sb_info *sbi = F2FS_SB(sb);
+ bool freeze_sync = sync &&
+ sb->s_writers.frozen == SB_FREEZE_PAGEFAULT;
+ int err;
+
+ /* freeze_super() holds s_umount for write during this sync pass. */
+ if (freeze_sync)
+ sbi->umount_lock_holder = current;
+
+ err = __f2fs_sync_fs(sb, sync);
+
+ if (freeze_sync)
+ sbi->umount_lock_holder = NULL;
+
+ return err;
+}
+
static int f2fs_freeze(struct super_block *sb)
{
struct f2fs_sb_info *sbi = F2FS_SB(sb);
@@ -2807,7 +2826,7 @@ static int f2fs_enable_checkpoint(struct f2fs_sb_info *sbi)
set_sbi_flag(sbi, SBI_IS_DIRTY);
f2fs_up_write_trace(&sbi->gc_lock, &lc);
- ret = f2fs_sync_fs(sbi->sb, 1);
+ ret = __f2fs_sync_fs(sbi->sb, 1);
if (ret)
f2fs_err(sbi, "%s sync_fs failed, ret: %d", __func__, ret);
@@ -2993,7 +3012,7 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb)
set_sbi_flag(sbi, SBI_IS_DIRTY);
set_sbi_flag(sbi, SBI_IS_CLOSE);
- err = f2fs_sync_fs(sb, 1);
+ err = __f2fs_sync_fs(sb, 1);
if (err)
goto restore_gc;
clear_sbi_flag(sbi, SBI_IS_CLOSE);
--
2.43.0
_______________________________________________
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [f2fs-dev] [PATCH] f2fs: quota: fix quota flush failure during filesystem freeze 2026-09-08 3:48 [f2fs-dev] [PATCH] f2fs: quota: fix quota flush failure during filesystem freeze Jianan Huang @ 2026-09-09 11:42 ` Chao Yu via Linux-f2fs-devel 2026-09-15 11:39 ` Jianan Huang 0 siblings, 1 reply; 5+ messages in thread From: Chao Yu via Linux-f2fs-devel @ 2026-09-09 11:42 UTC (permalink / raw) To: Jianan Huang, linux-f2fs-devel, jaegeuk On 9/8/26 11:48, Jianan Huang wrote: > Quota flush retries can be exhausted during filesystem freeze. > > freeze_super() and the quota checkpoint may race as below. > > freeze_super() f2fs_ckpt > - down_write(&sb->s_umount) > - sb->s_writers.frozen = SB_FREEZE_PAGEFAULT > - sync_filesystem() > - f2fs_sync_fs() > - f2fs_issue_checkpoint() > - queue CP_SYNC -----------------> - block_operations() > - down_read_trylock(s_umount) > : fails; freeze holds write lock > - quota flush retries exhausted > - set CP_QUOTA_NEED_FSCK_FLAG > > During the PAGEFAULT sync pass, freeze_super() holds s_umount for > write, but umount_lock_holder is not set until f2fs_freeze(). Thus > quota writeback tries to acquire s_umount again and can exhaust retries. > > With checkpoint merge enabled, f2fs_issue_checkpoint() may dispatch > CP_SYNC to f2fs_ckpt. That thread also cannot acquire the freeze lock. > > Split the F2FS-internal sync implementation from the super-operation > callback. For the VFS PAGEFAULT freeze sync, record current as holder > around the internal sync. This lets quota writeback use the existing > lock and makes CP_SYNC run directly in the freeze caller. > > Convert F2FS-internal callers to the internal helper so they cannot be > mistaken for the freeze owner while the filesystem is frozen. > > Fixes: eb85c2410d6f ("f2fs: quota: fix to avoid warning in dquot_writeback_dquots()") > Signed-off-by: Jianan Huang <jnhuang95@gmail.com> > --- > fs/f2fs/f2fs.h | 2 +- > fs/f2fs/file.c | 8 ++++---- > fs/f2fs/namei.c | 16 ++++++++-------- > fs/f2fs/segment.c | 4 ++-- > fs/f2fs/super.c | 25 ++++++++++++++++++++++--- > 5 files changed, 37 insertions(+), 18 deletions(-) > > diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h > index 9940a6cecf1a..169792a08a82 100644 > --- a/fs/f2fs/f2fs.h > +++ b/fs/f2fs/f2fs.h > @@ -3977,7 +3977,7 @@ void f2fs_quota_off_umount(struct super_block *sb); > void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag); > void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error); > int f2fs_commit_super(struct f2fs_sb_info *sbi, bool recover); > -int f2fs_sync_fs(struct super_block *sb, int sync); > +int __f2fs_sync_fs(struct super_block *sb, int sync); > int f2fs_sanity_check_ckpt(struct f2fs_sb_info *sbi); > > /* > diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c > index edc352569e87..83546a053656 100644 > --- a/fs/f2fs/file.c > +++ b/fs/f2fs/file.c > @@ -408,7 +408,7 @@ static int f2fs_do_sync_file(struct file *file, loff_t start, loff_t end, > > if (cp_reason) { > /* all the dirty node pages should be flushed for POR */ > - ret = f2fs_sync_fs(inode->i_sb, 1); > + ret = __f2fs_sync_fs(inode->i_sb, 1); > > /* > * We've secured consistency through sync_fs. Following pino > @@ -2548,7 +2548,7 @@ int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag, > break; > case F2FS_GOING_DOWN_METASYNC: > /* do checkpoint only */ > - ret = f2fs_sync_fs(sb, 1); > + ret = __f2fs_sync_fs(sb, 1); > if (ret) { > if (ret == -EIO) > ret = 0; > @@ -2568,7 +2568,7 @@ int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag, > set_sbi_flag(sbi, SBI_CP_DISABLED_QUICK); > set_sbi_flag(sbi, SBI_IS_DIRTY); > /* do checkpoint only */ > - ret = f2fs_sync_fs(sb, 1); > + ret = __f2fs_sync_fs(sb, 1); > if (ret == -EIO) > ret = 0; > goto out; > @@ -2988,7 +2988,7 @@ static int f2fs_ioc_write_checkpoint(struct file *filp) > if (ret) > return ret; > > - ret = f2fs_sync_fs(sbi->sb, 1); > + ret = __f2fs_sync_fs(sbi->sb, 1); > > mnt_drop_write_file(filp); > return ret; > diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c > index ff86ee07290d..5f6f5db9e849 100644 > --- a/fs/f2fs/namei.c > +++ b/fs/f2fs/namei.c > @@ -403,7 +403,7 @@ static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, > d_instantiate_new(dentry, inode); > > if (IS_DIRSYNC(dir)) { > - err = f2fs_sync_fs(sbi->sb, 1); > + err = __f2fs_sync_fs(sbi->sb, 1); > if (err) > return err; > } > @@ -459,7 +459,7 @@ static int f2fs_link(struct dentry *old_dentry, struct inode *dir, > d_instantiate(dentry, inode); > > if (IS_DIRSYNC(dir)) { > - err = f2fs_sync_fs(sbi->sb, 1); > + err = __f2fs_sync_fs(sbi->sb, 1); > if (err) > return err; > } > @@ -641,7 +641,7 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) > d_invalidate(dentry); > > if (IS_DIRSYNC(dir)) > - err = f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1); > + err = __f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1); > out: > trace_f2fs_unlink_exit(d_inode(dentry), err); > return err; > @@ -729,7 +729,7 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, > ret = filemap_write_and_wait_range(inode->i_mapping, 0, > disk_link.len - 1); > if (!ret && IS_DIRSYNC(dir)) > - err = f2fs_sync_fs(sbi->sb, 1); > + err = __f2fs_sync_fs(sbi->sb, 1); > > f2fs_balance_fs(sbi, true); > out: > @@ -787,7 +787,7 @@ static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, > d_instantiate_new(dentry, inode); > > if (IS_DIRSYNC(dir)) { > - err = f2fs_sync_fs(sbi->sb, 1); > + err = __f2fs_sync_fs(sbi->sb, 1); > if (err) > return ERR_PTR(err); > } > @@ -845,7 +845,7 @@ static int f2fs_mknod(struct mnt_idmap *idmap, struct inode *dir, > d_instantiate_new(dentry, inode); > > if (IS_DIRSYNC(dir)) { > - err = f2fs_sync_fs(sbi->sb, 1); > + err = __f2fs_sync_fs(sbi->sb, 1); > if (err) > return err; > } > @@ -1148,7 +1148,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, > f2fs_unlock_op(sbi, &lc); > > if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { > - err = f2fs_sync_fs(sbi->sb, 1); > + err = __f2fs_sync_fs(sbi->sb, 1); > if (err) > return err; > } > @@ -1318,7 +1318,7 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, > f2fs_unlock_op(sbi, &lc); > > if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { > - err = f2fs_sync_fs(sbi->sb, 1); > + err = __f2fs_sync_fs(sbi->sb, 1); > if (err) > return err; > } > diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c > index 63b712d3d599..9a09f2525b70 100644 > --- a/fs/f2fs/segment.c > +++ b/fs/f2fs/segment.c > @@ -550,7 +550,7 @@ void f2fs_balance_fs_bg(struct f2fs_sb_info *sbi, bool from_bg) > mutex_unlock(&sbi->flush_lock); > } > stat_inc_cp_call_count(sbi, BACKGROUND); > - f2fs_sync_fs(sbi->sb, 1); > + __f2fs_sync_fs(sbi->sb, 1); > } > > static int __submit_flush_wait(struct f2fs_sb_info *sbi, > @@ -3478,7 +3478,7 @@ int f2fs_allocate_pinning_section(struct f2fs_sb_info *sbi) > true, ZONED_PIN_SEC_REQUIRED_COUNT, true); > if (err) > return err; > - err = f2fs_sync_fs(sbi->sb, 1); > + err = __f2fs_sync_fs(sbi->sb, 1); > if (!err) { > gc_required = false; > goto retry; > diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c > index 1314b6ccced9..a82065146849 100644 > --- a/fs/f2fs/super.c > +++ b/fs/f2fs/super.c > @@ -2131,7 +2131,7 @@ static void f2fs_put_super(struct super_block *sb) > } > } > > -int f2fs_sync_fs(struct super_block *sb, int sync) > +int __f2fs_sync_fs(struct super_block *sb, int sync) > { > struct f2fs_sb_info *sbi = F2FS_SB(sb); > int err = 0; > @@ -2154,6 +2154,25 @@ int f2fs_sync_fs(struct super_block *sb, int sync) > return err; > } > > +static int f2fs_sync_fs(struct super_block *sb, int sync) > +{ > + struct f2fs_sb_info *sbi = F2FS_SB(sb); > + bool freeze_sync = sync && > + sb->s_writers.frozen == SB_FREEZE_PAGEFAULT; How about this to make sure freeze_sync will only be true in freeze_super()? bool freeze_sync = sync && rwsem_is_locked(&sb->s_umount) && sb->s_writers.frozen == SB_FREEZE_PAGEFAULT; And it's not needed to rename f2fs_sync_fs() to __f2fs_sync_fs()? Thanks, > + int err; > + > + /* freeze_super() holds s_umount for write during this sync pass. */ > + if (freeze_sync) > + sbi->umount_lock_holder = current; > + > + err = __f2fs_sync_fs(sb, sync); > + > + if (freeze_sync) > + sbi->umount_lock_holder = NULL; > + > + return err; > +} > + > static int f2fs_freeze(struct super_block *sb) > { > struct f2fs_sb_info *sbi = F2FS_SB(sb); > @@ -2807,7 +2826,7 @@ static int f2fs_enable_checkpoint(struct f2fs_sb_info *sbi) > set_sbi_flag(sbi, SBI_IS_DIRTY); > f2fs_up_write_trace(&sbi->gc_lock, &lc); > > - ret = f2fs_sync_fs(sbi->sb, 1); > + ret = __f2fs_sync_fs(sbi->sb, 1); > if (ret) > f2fs_err(sbi, "%s sync_fs failed, ret: %d", __func__, ret); > > @@ -2993,7 +3012,7 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) > > set_sbi_flag(sbi, SBI_IS_DIRTY); > set_sbi_flag(sbi, SBI_IS_CLOSE); > - err = f2fs_sync_fs(sb, 1); > + err = __f2fs_sync_fs(sb, 1); > if (err) > goto restore_gc; > clear_sbi_flag(sbi, SBI_IS_CLOSE); _______________________________________________ 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] 5+ messages in thread
* Re: [f2fs-dev] [PATCH] f2fs: quota: fix quota flush failure during filesystem freeze 2026-09-09 11:42 ` Chao Yu via Linux-f2fs-devel @ 2026-09-15 11:39 ` Jianan Huang 2026-09-16 1:59 ` Chao Yu via Linux-f2fs-devel 0 siblings, 1 reply; 5+ messages in thread From: Jianan Huang @ 2026-09-15 11:39 UTC (permalink / raw) To: Chao Yu; +Cc: jaegeuk, linux-f2fs-devel Chao Yu <chao@kernel.org> 于2026年9月9日周三 19:43写道: > > On 9/8/26 11:48, Jianan Huang wrote: > > Quota flush retries can be exhausted during filesystem freeze. > > > > freeze_super() and the quota checkpoint may race as below. > > > > freeze_super() f2fs_ckpt > > - down_write(&sb->s_umount) > > - sb->s_writers.frozen = SB_FREEZE_PAGEFAULT > > - sync_filesystem() > > - f2fs_sync_fs() > > - f2fs_issue_checkpoint() > > - queue CP_SYNC -----------------> - block_operations() > > - down_read_trylock(s_umount) > > : fails; freeze holds write lock > > - quota flush retries exhausted > > - set CP_QUOTA_NEED_FSCK_FLAG > > > > During the PAGEFAULT sync pass, freeze_super() holds s_umount for > > write, but umount_lock_holder is not set until f2fs_freeze(). Thus > > quota writeback tries to acquire s_umount again and can exhaust retries. > > > > With checkpoint merge enabled, f2fs_issue_checkpoint() may dispatch > > CP_SYNC to f2fs_ckpt. That thread also cannot acquire the freeze lock. > > > > Split the F2FS-internal sync implementation from the super-operation > > callback. For the VFS PAGEFAULT freeze sync, record current as holder > > around the internal sync. This lets quota writeback use the existing > > lock and makes CP_SYNC run directly in the freeze caller. > > > > Convert F2FS-internal callers to the internal helper so they cannot be > > mistaken for the freeze owner while the filesystem is frozen. > > > > Fixes: eb85c2410d6f ("f2fs: quota: fix to avoid warning in dquot_writeback_dquots()") > > Signed-off-by: Jianan Huang <jnhuang95@gmail.com> > > --- > > fs/f2fs/f2fs.h | 2 +- > > fs/f2fs/file.c | 8 ++++---- > > fs/f2fs/namei.c | 16 ++++++++-------- > > fs/f2fs/segment.c | 4 ++-- > > fs/f2fs/super.c | 25 ++++++++++++++++++++++--- > > 5 files changed, 37 insertions(+), 18 deletions(-) > > > > diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h > > index 9940a6cecf1a..169792a08a82 100644 > > --- a/fs/f2fs/f2fs.h > > +++ b/fs/f2fs/f2fs.h > > @@ -3977,7 +3977,7 @@ void f2fs_quota_off_umount(struct super_block *sb); > > void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag); > > void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error); > > int f2fs_commit_super(struct f2fs_sb_info *sbi, bool recover); > > -int f2fs_sync_fs(struct super_block *sb, int sync); > > +int __f2fs_sync_fs(struct super_block *sb, int sync); > > int f2fs_sanity_check_ckpt(struct f2fs_sb_info *sbi); > > > > /* > > diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c > > index edc352569e87..83546a053656 100644 > > --- a/fs/f2fs/file.c > > +++ b/fs/f2fs/file.c > > @@ -408,7 +408,7 @@ static int f2fs_do_sync_file(struct file *file, loff_t start, loff_t end, > > > > if (cp_reason) { > > /* all the dirty node pages should be flushed for POR */ > > - ret = f2fs_sync_fs(inode->i_sb, 1); > > + ret = __f2fs_sync_fs(inode->i_sb, 1); > > > > /* > > * We've secured consistency through sync_fs. Following pino > > @@ -2548,7 +2548,7 @@ int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag, > > break; > > case F2FS_GOING_DOWN_METASYNC: > > /* do checkpoint only */ > > - ret = f2fs_sync_fs(sb, 1); > > + ret = __f2fs_sync_fs(sb, 1); > > if (ret) { > > if (ret == -EIO) > > ret = 0; > > @@ -2568,7 +2568,7 @@ int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag, > > set_sbi_flag(sbi, SBI_CP_DISABLED_QUICK); > > set_sbi_flag(sbi, SBI_IS_DIRTY); > > /* do checkpoint only */ > > - ret = f2fs_sync_fs(sb, 1); > > + ret = __f2fs_sync_fs(sb, 1); > > if (ret == -EIO) > > ret = 0; > > goto out; > > @@ -2988,7 +2988,7 @@ static int f2fs_ioc_write_checkpoint(struct file *filp) > > if (ret) > > return ret; > > > > - ret = f2fs_sync_fs(sbi->sb, 1); > > + ret = __f2fs_sync_fs(sbi->sb, 1); > > > > mnt_drop_write_file(filp); > > return ret; > > diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c > > index ff86ee07290d..5f6f5db9e849 100644 > > --- a/fs/f2fs/namei.c > > +++ b/fs/f2fs/namei.c > > @@ -403,7 +403,7 @@ static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, > > d_instantiate_new(dentry, inode); > > > > if (IS_DIRSYNC(dir)) { > > - err = f2fs_sync_fs(sbi->sb, 1); > > + err = __f2fs_sync_fs(sbi->sb, 1); > > if (err) > > return err; > > } > > @@ -459,7 +459,7 @@ static int f2fs_link(struct dentry *old_dentry, struct inode *dir, > > d_instantiate(dentry, inode); > > > > if (IS_DIRSYNC(dir)) { > > - err = f2fs_sync_fs(sbi->sb, 1); > > + err = __f2fs_sync_fs(sbi->sb, 1); > > if (err) > > return err; > > } > > @@ -641,7 +641,7 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) > > d_invalidate(dentry); > > > > if (IS_DIRSYNC(dir)) > > - err = f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1); > > + err = __f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1); > > out: > > trace_f2fs_unlink_exit(d_inode(dentry), err); > > return err; > > @@ -729,7 +729,7 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, > > ret = filemap_write_and_wait_range(inode->i_mapping, 0, > > disk_link.len - 1); > > if (!ret && IS_DIRSYNC(dir)) > > - err = f2fs_sync_fs(sbi->sb, 1); > > + err = __f2fs_sync_fs(sbi->sb, 1); > > > > f2fs_balance_fs(sbi, true); > > out: > > @@ -787,7 +787,7 @@ static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, > > d_instantiate_new(dentry, inode); > > > > if (IS_DIRSYNC(dir)) { > > - err = f2fs_sync_fs(sbi->sb, 1); > > + err = __f2fs_sync_fs(sbi->sb, 1); > > if (err) > > return ERR_PTR(err); > > } > > @@ -845,7 +845,7 @@ static int f2fs_mknod(struct mnt_idmap *idmap, struct inode *dir, > > d_instantiate_new(dentry, inode); > > > > if (IS_DIRSYNC(dir)) { > > - err = f2fs_sync_fs(sbi->sb, 1); > > + err = __f2fs_sync_fs(sbi->sb, 1); > > if (err) > > return err; > > } > > @@ -1148,7 +1148,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, > > f2fs_unlock_op(sbi, &lc); > > > > if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { > > - err = f2fs_sync_fs(sbi->sb, 1); > > + err = __f2fs_sync_fs(sbi->sb, 1); > > if (err) > > return err; > > } > > @@ -1318,7 +1318,7 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, > > f2fs_unlock_op(sbi, &lc); > > > > if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { > > - err = f2fs_sync_fs(sbi->sb, 1); > > + err = __f2fs_sync_fs(sbi->sb, 1); > > if (err) > > return err; > > } > > diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c > > index 63b712d3d599..9a09f2525b70 100644 > > --- a/fs/f2fs/segment.c > > +++ b/fs/f2fs/segment.c > > @@ -550,7 +550,7 @@ void f2fs_balance_fs_bg(struct f2fs_sb_info *sbi, bool from_bg) > > mutex_unlock(&sbi->flush_lock); > > } > > stat_inc_cp_call_count(sbi, BACKGROUND); > > - f2fs_sync_fs(sbi->sb, 1); > > + __f2fs_sync_fs(sbi->sb, 1); > > } > > > > static int __submit_flush_wait(struct f2fs_sb_info *sbi, > > @@ -3478,7 +3478,7 @@ int f2fs_allocate_pinning_section(struct f2fs_sb_info *sbi) > > true, ZONED_PIN_SEC_REQUIRED_COUNT, true); > > if (err) > > return err; > > - err = f2fs_sync_fs(sbi->sb, 1); > > + err = __f2fs_sync_fs(sbi->sb, 1); > > if (!err) { > > gc_required = false; > > goto retry; > > diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c > > index 1314b6ccced9..a82065146849 100644 > > --- a/fs/f2fs/super.c > > +++ b/fs/f2fs/super.c > > @@ -2131,7 +2131,7 @@ static void f2fs_put_super(struct super_block *sb) > > } > > } > > > > -int f2fs_sync_fs(struct super_block *sb, int sync) > > +int __f2fs_sync_fs(struct super_block *sb, int sync) > > { > > struct f2fs_sb_info *sbi = F2FS_SB(sb); > > int err = 0; > > @@ -2154,6 +2154,25 @@ int f2fs_sync_fs(struct super_block *sb, int sync) > > return err; > > } > > > > +static int f2fs_sync_fs(struct super_block *sb, int sync) > > +{ > > + struct f2fs_sb_info *sbi = F2FS_SB(sb); > > + bool freeze_sync = sync && > > + sb->s_writers.frozen == SB_FREEZE_PAGEFAULT; > > How about this to make sure freeze_sync will only be true in freeze_super()? > > bool freeze_sync = sync && rwsem_is_locked(&sb->s_umount) && > sb->s_writers.frozen == SB_FREEZE_PAGEFAULT; > > And it's not needed to rename f2fs_sync_fs() to __f2fs_sync_fs()? > Sorry for the late reply. rwsem_is_locked() doesn't tell us whether current owns the lock, so I'm concerned about the following race: Freezer A Directory fsync B holds s_umount write lock stage = SB_FREEZE_PAGEFAULT holder = current_A locks gc_lock holder = current_B waits for gc_lock holder != current_A s_umount read trylock fails retry limit -> QUOTA_SKIP_FLUSH sets QUOTA_NEED_FLUSH commits CP with quota_need_fsck clears IS_DIRTY unlocks gc_lock holder = NULL stage = SB_FREEZE_FS locks gc_lock !IS_DIRTY && CP_SYNC -> returns before quota flush unlocks gc_lock holder = NULL completes f2fs_freeze() unlocks s_umount FIFREEZE returns Could we use a separate f2fs_sync_fs_super() wrapper for the VFS callback and keep freeze holder management there, leaving f2fs_sync_fs() for internal callers? Thanks, > Thanks, > > > + int err; > > + > > + /* freeze_super() holds s_umount for write during this sync pass. */ > > + if (freeze_sync) > > + sbi->umount_lock_holder = current; > > + > > + err = __f2fs_sync_fs(sb, sync); > > + > > + if (freeze_sync) > > + sbi->umount_lock_holder = NULL; > > + > > + return err; > > +} > > + > > static int f2fs_freeze(struct super_block *sb) > > { > > struct f2fs_sb_info *sbi = F2FS_SB(sb); > > @@ -2807,7 +2826,7 @@ static int f2fs_enable_checkpoint(struct f2fs_sb_info *sbi) > > set_sbi_flag(sbi, SBI_IS_DIRTY); > > f2fs_up_write_trace(&sbi->gc_lock, &lc); > > > > - ret = f2fs_sync_fs(sbi->sb, 1); > > + ret = __f2fs_sync_fs(sbi->sb, 1); > > if (ret) > > f2fs_err(sbi, "%s sync_fs failed, ret: %d", __func__, ret); > > > > @@ -2993,7 +3012,7 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) > > > > set_sbi_flag(sbi, SBI_IS_DIRTY); > > set_sbi_flag(sbi, SBI_IS_CLOSE); > > - err = f2fs_sync_fs(sb, 1); > > + err = __f2fs_sync_fs(sb, 1); > > if (err) > > goto restore_gc; > > clear_sbi_flag(sbi, SBI_IS_CLOSE); > _______________________________________________ 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] 5+ messages in thread
* Re: [f2fs-dev] [PATCH] f2fs: quota: fix quota flush failure during filesystem freeze 2026-09-15 11:39 ` Jianan Huang @ 2026-09-16 1:59 ` Chao Yu via Linux-f2fs-devel 2026-09-16 3:25 ` Jianan Huang 0 siblings, 1 reply; 5+ messages in thread From: Chao Yu via Linux-f2fs-devel @ 2026-09-16 1:59 UTC (permalink / raw) To: Jianan Huang; +Cc: jaegeuk, linux-f2fs-devel On 9/15/26 19:39, Jianan Huang wrote: > Chao Yu <chao@kernel.org> 于2026年9月9日周三 19:43写道: >> >> On 9/8/26 11:48, Jianan Huang wrote: >>> Quota flush retries can be exhausted during filesystem freeze. >>> >>> freeze_super() and the quota checkpoint may race as below. >>> >>> freeze_super() f2fs_ckpt >>> - down_write(&sb->s_umount) >>> - sb->s_writers.frozen = SB_FREEZE_PAGEFAULT >>> - sync_filesystem() >>> - f2fs_sync_fs() >>> - f2fs_issue_checkpoint() >>> - queue CP_SYNC -----------------> - block_operations() >>> - down_read_trylock(s_umount) >>> : fails; freeze holds write lock >>> - quota flush retries exhausted >>> - set CP_QUOTA_NEED_FSCK_FLAG >>> >>> During the PAGEFAULT sync pass, freeze_super() holds s_umount for >>> write, but umount_lock_holder is not set until f2fs_freeze(). Thus >>> quota writeback tries to acquire s_umount again and can exhaust retries. >>> >>> With checkpoint merge enabled, f2fs_issue_checkpoint() may dispatch >>> CP_SYNC to f2fs_ckpt. That thread also cannot acquire the freeze lock. >>> >>> Split the F2FS-internal sync implementation from the super-operation >>> callback. For the VFS PAGEFAULT freeze sync, record current as holder >>> around the internal sync. This lets quota writeback use the existing >>> lock and makes CP_SYNC run directly in the freeze caller. >>> >>> Convert F2FS-internal callers to the internal helper so they cannot be >>> mistaken for the freeze owner while the filesystem is frozen. >>> >>> Fixes: eb85c2410d6f ("f2fs: quota: fix to avoid warning in dquot_writeback_dquots()") >>> Signed-off-by: Jianan Huang <jnhuang95@gmail.com> >>> --- >>> fs/f2fs/f2fs.h | 2 +- >>> fs/f2fs/file.c | 8 ++++---- >>> fs/f2fs/namei.c | 16 ++++++++-------- >>> fs/f2fs/segment.c | 4 ++-- >>> fs/f2fs/super.c | 25 ++++++++++++++++++++++--- >>> 5 files changed, 37 insertions(+), 18 deletions(-) >>> >>> diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h >>> index 9940a6cecf1a..169792a08a82 100644 >>> --- a/fs/f2fs/f2fs.h >>> +++ b/fs/f2fs/f2fs.h >>> @@ -3977,7 +3977,7 @@ void f2fs_quota_off_umount(struct super_block *sb); >>> void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag); >>> void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error); >>> int f2fs_commit_super(struct f2fs_sb_info *sbi, bool recover); >>> -int f2fs_sync_fs(struct super_block *sb, int sync); >>> +int __f2fs_sync_fs(struct super_block *sb, int sync); >>> int f2fs_sanity_check_ckpt(struct f2fs_sb_info *sbi); >>> >>> /* >>> diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c >>> index edc352569e87..83546a053656 100644 >>> --- a/fs/f2fs/file.c >>> +++ b/fs/f2fs/file.c >>> @@ -408,7 +408,7 @@ static int f2fs_do_sync_file(struct file *file, loff_t start, loff_t end, >>> >>> if (cp_reason) { >>> /* all the dirty node pages should be flushed for POR */ >>> - ret = f2fs_sync_fs(inode->i_sb, 1); >>> + ret = __f2fs_sync_fs(inode->i_sb, 1); >>> >>> /* >>> * We've secured consistency through sync_fs. Following pino >>> @@ -2548,7 +2548,7 @@ int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag, >>> break; >>> case F2FS_GOING_DOWN_METASYNC: >>> /* do checkpoint only */ >>> - ret = f2fs_sync_fs(sb, 1); >>> + ret = __f2fs_sync_fs(sb, 1); >>> if (ret) { >>> if (ret == -EIO) >>> ret = 0; >>> @@ -2568,7 +2568,7 @@ int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag, >>> set_sbi_flag(sbi, SBI_CP_DISABLED_QUICK); >>> set_sbi_flag(sbi, SBI_IS_DIRTY); >>> /* do checkpoint only */ >>> - ret = f2fs_sync_fs(sb, 1); >>> + ret = __f2fs_sync_fs(sb, 1); >>> if (ret == -EIO) >>> ret = 0; >>> goto out; >>> @@ -2988,7 +2988,7 @@ static int f2fs_ioc_write_checkpoint(struct file *filp) >>> if (ret) >>> return ret; >>> >>> - ret = f2fs_sync_fs(sbi->sb, 1); >>> + ret = __f2fs_sync_fs(sbi->sb, 1); >>> >>> mnt_drop_write_file(filp); >>> return ret; >>> diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c >>> index ff86ee07290d..5f6f5db9e849 100644 >>> --- a/fs/f2fs/namei.c >>> +++ b/fs/f2fs/namei.c >>> @@ -403,7 +403,7 @@ static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, >>> d_instantiate_new(dentry, inode); >>> >>> if (IS_DIRSYNC(dir)) { >>> - err = f2fs_sync_fs(sbi->sb, 1); >>> + err = __f2fs_sync_fs(sbi->sb, 1); >>> if (err) >>> return err; >>> } >>> @@ -459,7 +459,7 @@ static int f2fs_link(struct dentry *old_dentry, struct inode *dir, >>> d_instantiate(dentry, inode); >>> >>> if (IS_DIRSYNC(dir)) { >>> - err = f2fs_sync_fs(sbi->sb, 1); >>> + err = __f2fs_sync_fs(sbi->sb, 1); >>> if (err) >>> return err; >>> } >>> @@ -641,7 +641,7 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) >>> d_invalidate(dentry); >>> >>> if (IS_DIRSYNC(dir)) >>> - err = f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1); >>> + err = __f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1); >>> out: >>> trace_f2fs_unlink_exit(d_inode(dentry), err); >>> return err; >>> @@ -729,7 +729,7 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, >>> ret = filemap_write_and_wait_range(inode->i_mapping, 0, >>> disk_link.len - 1); >>> if (!ret && IS_DIRSYNC(dir)) >>> - err = f2fs_sync_fs(sbi->sb, 1); >>> + err = __f2fs_sync_fs(sbi->sb, 1); >>> >>> f2fs_balance_fs(sbi, true); >>> out: >>> @@ -787,7 +787,7 @@ static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, >>> d_instantiate_new(dentry, inode); >>> >>> if (IS_DIRSYNC(dir)) { >>> - err = f2fs_sync_fs(sbi->sb, 1); >>> + err = __f2fs_sync_fs(sbi->sb, 1); >>> if (err) >>> return ERR_PTR(err); >>> } >>> @@ -845,7 +845,7 @@ static int f2fs_mknod(struct mnt_idmap *idmap, struct inode *dir, >>> d_instantiate_new(dentry, inode); >>> >>> if (IS_DIRSYNC(dir)) { >>> - err = f2fs_sync_fs(sbi->sb, 1); >>> + err = __f2fs_sync_fs(sbi->sb, 1); >>> if (err) >>> return err; >>> } >>> @@ -1148,7 +1148,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, >>> f2fs_unlock_op(sbi, &lc); >>> >>> if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { >>> - err = f2fs_sync_fs(sbi->sb, 1); >>> + err = __f2fs_sync_fs(sbi->sb, 1); >>> if (err) >>> return err; >>> } >>> @@ -1318,7 +1318,7 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, >>> f2fs_unlock_op(sbi, &lc); >>> >>> if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { >>> - err = f2fs_sync_fs(sbi->sb, 1); >>> + err = __f2fs_sync_fs(sbi->sb, 1); >>> if (err) >>> return err; >>> } >>> diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c >>> index 63b712d3d599..9a09f2525b70 100644 >>> --- a/fs/f2fs/segment.c >>> +++ b/fs/f2fs/segment.c >>> @@ -550,7 +550,7 @@ void f2fs_balance_fs_bg(struct f2fs_sb_info *sbi, bool from_bg) >>> mutex_unlock(&sbi->flush_lock); >>> } >>> stat_inc_cp_call_count(sbi, BACKGROUND); >>> - f2fs_sync_fs(sbi->sb, 1); >>> + __f2fs_sync_fs(sbi->sb, 1); >>> } >>> >>> static int __submit_flush_wait(struct f2fs_sb_info *sbi, >>> @@ -3478,7 +3478,7 @@ int f2fs_allocate_pinning_section(struct f2fs_sb_info *sbi) >>> true, ZONED_PIN_SEC_REQUIRED_COUNT, true); >>> if (err) >>> return err; >>> - err = f2fs_sync_fs(sbi->sb, 1); >>> + err = __f2fs_sync_fs(sbi->sb, 1); >>> if (!err) { >>> gc_required = false; >>> goto retry; >>> diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c >>> index 1314b6ccced9..a82065146849 100644 >>> --- a/fs/f2fs/super.c >>> +++ b/fs/f2fs/super.c >>> @@ -2131,7 +2131,7 @@ static void f2fs_put_super(struct super_block *sb) >>> } >>> } >>> >>> -int f2fs_sync_fs(struct super_block *sb, int sync) >>> +int __f2fs_sync_fs(struct super_block *sb, int sync) >>> { >>> struct f2fs_sb_info *sbi = F2FS_SB(sb); >>> int err = 0; >>> @@ -2154,6 +2154,25 @@ int f2fs_sync_fs(struct super_block *sb, int sync) >>> return err; >>> } >>> >>> +static int f2fs_sync_fs(struct super_block *sb, int sync) >>> +{ >>> + struct f2fs_sb_info *sbi = F2FS_SB(sb); >>> + bool freeze_sync = sync && >>> + sb->s_writers.frozen == SB_FREEZE_PAGEFAULT; >> >> How about this to make sure freeze_sync will only be true in freeze_super()? >> >> bool freeze_sync = sync && rwsem_is_locked(&sb->s_umount) && >> sb->s_writers.frozen == SB_FREEZE_PAGEFAULT; >> >> And it's not needed to rename f2fs_sync_fs() to __f2fs_sync_fs()? >> > > Sorry for the late reply. rwsem_is_locked() doesn't tell us whether > current owns the lock, so I'm concerned about the following race: > > Freezer A Directory fsync B > holds s_umount write lock > stage = SB_FREEZE_PAGEFAULT > holder = current_A > locks gc_lock > holder = current_B > waits for gc_lock Oh, right, fsync won't call sb_start_*(). One more question, if we set holder under gc_lock, will it resolve this race case? Thanks, > holder != current_A > s_umount read trylock fails > retry limit -> QUOTA_SKIP_FLUSH > sets QUOTA_NEED_FLUSH > commits CP with quota_need_fsck > clears IS_DIRTY > unlocks gc_lock > holder = NULL > stage = SB_FREEZE_FS > locks gc_lock > !IS_DIRTY && CP_SYNC > -> returns before quota flush > unlocks gc_lock > holder = NULL > completes f2fs_freeze() > unlocks s_umount > FIFREEZE returns > > Could we use a separate f2fs_sync_fs_super() wrapper for the VFS callback > and keep freeze holder management there, leaving f2fs_sync_fs() for > internal callers? > > Thanks, > >> Thanks, >> >>> + int err; >>> + >>> + /* freeze_super() holds s_umount for write during this sync pass. */ >>> + if (freeze_sync) >>> + sbi->umount_lock_holder = current; >>> + >>> + err = __f2fs_sync_fs(sb, sync); >>> + >>> + if (freeze_sync) >>> + sbi->umount_lock_holder = NULL; >>> + >>> + return err; >>> +} >>> + >>> static int f2fs_freeze(struct super_block *sb) >>> { >>> struct f2fs_sb_info *sbi = F2FS_SB(sb); >>> @@ -2807,7 +2826,7 @@ static int f2fs_enable_checkpoint(struct f2fs_sb_info *sbi) >>> set_sbi_flag(sbi, SBI_IS_DIRTY); >>> f2fs_up_write_trace(&sbi->gc_lock, &lc); >>> >>> - ret = f2fs_sync_fs(sbi->sb, 1); >>> + ret = __f2fs_sync_fs(sbi->sb, 1); >>> if (ret) >>> f2fs_err(sbi, "%s sync_fs failed, ret: %d", __func__, ret); >>> >>> @@ -2993,7 +3012,7 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) >>> >>> set_sbi_flag(sbi, SBI_IS_DIRTY); >>> set_sbi_flag(sbi, SBI_IS_CLOSE); >>> - err = f2fs_sync_fs(sb, 1); >>> + err = __f2fs_sync_fs(sb, 1); >>> if (err) >>> goto restore_gc; >>> clear_sbi_flag(sbi, SBI_IS_CLOSE); >> _______________________________________________ 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] 5+ messages in thread
* Re: [f2fs-dev] [PATCH] f2fs: quota: fix quota flush failure during filesystem freeze 2026-09-16 1:59 ` Chao Yu via Linux-f2fs-devel @ 2026-09-16 3:25 ` Jianan Huang 0 siblings, 0 replies; 5+ messages in thread From: Jianan Huang @ 2026-09-16 3:25 UTC (permalink / raw) To: Chao Yu; +Cc: jaegeuk, linux-f2fs-devel Chao Yu <chao@kernel.org> 于2026年9月16日周三 09:59写道: > > On 9/15/26 19:39, Jianan Huang wrote: > > Chao Yu <chao@kernel.org> 于2026年9月9日周三 19:43写道: > >> > >> On 9/8/26 11:48, Jianan Huang wrote: > >>> Quota flush retries can be exhausted during filesystem freeze. > >>> > >>> freeze_super() and the quota checkpoint may race as below. > >>> > >>> freeze_super() f2fs_ckpt > >>> - down_write(&sb->s_umount) > >>> - sb->s_writers.frozen = SB_FREEZE_PAGEFAULT > >>> - sync_filesystem() > >>> - f2fs_sync_fs() > >>> - f2fs_issue_checkpoint() > >>> - queue CP_SYNC -----------------> - block_operations() > >>> - down_read_trylock(s_umount) > >>> : fails; freeze holds write lock > >>> - quota flush retries exhausted > >>> - set CP_QUOTA_NEED_FSCK_FLAG > >>> > >>> During the PAGEFAULT sync pass, freeze_super() holds s_umount for > >>> write, but umount_lock_holder is not set until f2fs_freeze(). Thus > >>> quota writeback tries to acquire s_umount again and can exhaust retries. > >>> > >>> With checkpoint merge enabled, f2fs_issue_checkpoint() may dispatch > >>> CP_SYNC to f2fs_ckpt. That thread also cannot acquire the freeze lock. > >>> > >>> Split the F2FS-internal sync implementation from the super-operation > >>> callback. For the VFS PAGEFAULT freeze sync, record current as holder > >>> around the internal sync. This lets quota writeback use the existing > >>> lock and makes CP_SYNC run directly in the freeze caller. > >>> > >>> Convert F2FS-internal callers to the internal helper so they cannot be > >>> mistaken for the freeze owner while the filesystem is frozen. > >>> > >>> Fixes: eb85c2410d6f ("f2fs: quota: fix to avoid warning in dquot_writeback_dquots()") > >>> Signed-off-by: Jianan Huang <jnhuang95@gmail.com> > >>> --- > >>> fs/f2fs/f2fs.h | 2 +- > >>> fs/f2fs/file.c | 8 ++++---- > >>> fs/f2fs/namei.c | 16 ++++++++-------- > >>> fs/f2fs/segment.c | 4 ++-- > >>> fs/f2fs/super.c | 25 ++++++++++++++++++++++--- > >>> 5 files changed, 37 insertions(+), 18 deletions(-) > >>> > >>> diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h > >>> index 9940a6cecf1a..169792a08a82 100644 > >>> --- a/fs/f2fs/f2fs.h > >>> +++ b/fs/f2fs/f2fs.h > >>> @@ -3977,7 +3977,7 @@ void f2fs_quota_off_umount(struct super_block *sb); > >>> void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag); > >>> void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error); > >>> int f2fs_commit_super(struct f2fs_sb_info *sbi, bool recover); > >>> -int f2fs_sync_fs(struct super_block *sb, int sync); > >>> +int __f2fs_sync_fs(struct super_block *sb, int sync); > >>> int f2fs_sanity_check_ckpt(struct f2fs_sb_info *sbi); > >>> > >>> /* > >>> diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c > >>> index edc352569e87..83546a053656 100644 > >>> --- a/fs/f2fs/file.c > >>> +++ b/fs/f2fs/file.c > >>> @@ -408,7 +408,7 @@ static int f2fs_do_sync_file(struct file *file, loff_t start, loff_t end, > >>> > >>> if (cp_reason) { > >>> /* all the dirty node pages should be flushed for POR */ > >>> - ret = f2fs_sync_fs(inode->i_sb, 1); > >>> + ret = __f2fs_sync_fs(inode->i_sb, 1); > >>> > >>> /* > >>> * We've secured consistency through sync_fs. Following pino > >>> @@ -2548,7 +2548,7 @@ int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag, > >>> break; > >>> case F2FS_GOING_DOWN_METASYNC: > >>> /* do checkpoint only */ > >>> - ret = f2fs_sync_fs(sb, 1); > >>> + ret = __f2fs_sync_fs(sb, 1); > >>> if (ret) { > >>> if (ret == -EIO) > >>> ret = 0; > >>> @@ -2568,7 +2568,7 @@ int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag, > >>> set_sbi_flag(sbi, SBI_CP_DISABLED_QUICK); > >>> set_sbi_flag(sbi, SBI_IS_DIRTY); > >>> /* do checkpoint only */ > >>> - ret = f2fs_sync_fs(sb, 1); > >>> + ret = __f2fs_sync_fs(sb, 1); > >>> if (ret == -EIO) > >>> ret = 0; > >>> goto out; > >>> @@ -2988,7 +2988,7 @@ static int f2fs_ioc_write_checkpoint(struct file *filp) > >>> if (ret) > >>> return ret; > >>> > >>> - ret = f2fs_sync_fs(sbi->sb, 1); > >>> + ret = __f2fs_sync_fs(sbi->sb, 1); > >>> > >>> mnt_drop_write_file(filp); > >>> return ret; > >>> diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c > >>> index ff86ee07290d..5f6f5db9e849 100644 > >>> --- a/fs/f2fs/namei.c > >>> +++ b/fs/f2fs/namei.c > >>> @@ -403,7 +403,7 @@ static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, > >>> d_instantiate_new(dentry, inode); > >>> > >>> if (IS_DIRSYNC(dir)) { > >>> - err = f2fs_sync_fs(sbi->sb, 1); > >>> + err = __f2fs_sync_fs(sbi->sb, 1); > >>> if (err) > >>> return err; > >>> } > >>> @@ -459,7 +459,7 @@ static int f2fs_link(struct dentry *old_dentry, struct inode *dir, > >>> d_instantiate(dentry, inode); > >>> > >>> if (IS_DIRSYNC(dir)) { > >>> - err = f2fs_sync_fs(sbi->sb, 1); > >>> + err = __f2fs_sync_fs(sbi->sb, 1); > >>> if (err) > >>> return err; > >>> } > >>> @@ -641,7 +641,7 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) > >>> d_invalidate(dentry); > >>> > >>> if (IS_DIRSYNC(dir)) > >>> - err = f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1); > >>> + err = __f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1); > >>> out: > >>> trace_f2fs_unlink_exit(d_inode(dentry), err); > >>> return err; > >>> @@ -729,7 +729,7 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, > >>> ret = filemap_write_and_wait_range(inode->i_mapping, 0, > >>> disk_link.len - 1); > >>> if (!ret && IS_DIRSYNC(dir)) > >>> - err = f2fs_sync_fs(sbi->sb, 1); > >>> + err = __f2fs_sync_fs(sbi->sb, 1); > >>> > >>> f2fs_balance_fs(sbi, true); > >>> out: > >>> @@ -787,7 +787,7 @@ static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, > >>> d_instantiate_new(dentry, inode); > >>> > >>> if (IS_DIRSYNC(dir)) { > >>> - err = f2fs_sync_fs(sbi->sb, 1); > >>> + err = __f2fs_sync_fs(sbi->sb, 1); > >>> if (err) > >>> return ERR_PTR(err); > >>> } > >>> @@ -845,7 +845,7 @@ static int f2fs_mknod(struct mnt_idmap *idmap, struct inode *dir, > >>> d_instantiate_new(dentry, inode); > >>> > >>> if (IS_DIRSYNC(dir)) { > >>> - err = f2fs_sync_fs(sbi->sb, 1); > >>> + err = __f2fs_sync_fs(sbi->sb, 1); > >>> if (err) > >>> return err; > >>> } > >>> @@ -1148,7 +1148,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, > >>> f2fs_unlock_op(sbi, &lc); > >>> > >>> if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { > >>> - err = f2fs_sync_fs(sbi->sb, 1); > >>> + err = __f2fs_sync_fs(sbi->sb, 1); > >>> if (err) > >>> return err; > >>> } > >>> @@ -1318,7 +1318,7 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, > >>> f2fs_unlock_op(sbi, &lc); > >>> > >>> if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { > >>> - err = f2fs_sync_fs(sbi->sb, 1); > >>> + err = __f2fs_sync_fs(sbi->sb, 1); > >>> if (err) > >>> return err; > >>> } > >>> diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c > >>> index 63b712d3d599..9a09f2525b70 100644 > >>> --- a/fs/f2fs/segment.c > >>> +++ b/fs/f2fs/segment.c > >>> @@ -550,7 +550,7 @@ void f2fs_balance_fs_bg(struct f2fs_sb_info *sbi, bool from_bg) > >>> mutex_unlock(&sbi->flush_lock); > >>> } > >>> stat_inc_cp_call_count(sbi, BACKGROUND); > >>> - f2fs_sync_fs(sbi->sb, 1); > >>> + __f2fs_sync_fs(sbi->sb, 1); > >>> } > >>> > >>> static int __submit_flush_wait(struct f2fs_sb_info *sbi, > >>> @@ -3478,7 +3478,7 @@ int f2fs_allocate_pinning_section(struct f2fs_sb_info *sbi) > >>> true, ZONED_PIN_SEC_REQUIRED_COUNT, true); > >>> if (err) > >>> return err; > >>> - err = f2fs_sync_fs(sbi->sb, 1); > >>> + err = __f2fs_sync_fs(sbi->sb, 1); > >>> if (!err) { > >>> gc_required = false; > >>> goto retry; > >>> diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c > >>> index 1314b6ccced9..a82065146849 100644 > >>> --- a/fs/f2fs/super.c > >>> +++ b/fs/f2fs/super.c > >>> @@ -2131,7 +2131,7 @@ static void f2fs_put_super(struct super_block *sb) > >>> } > >>> } > >>> > >>> -int f2fs_sync_fs(struct super_block *sb, int sync) > >>> +int __f2fs_sync_fs(struct super_block *sb, int sync) > >>> { > >>> struct f2fs_sb_info *sbi = F2FS_SB(sb); > >>> int err = 0; > >>> @@ -2154,6 +2154,25 @@ int f2fs_sync_fs(struct super_block *sb, int sync) > >>> return err; > >>> } > >>> > >>> +static int f2fs_sync_fs(struct super_block *sb, int sync) > >>> +{ > >>> + struct f2fs_sb_info *sbi = F2FS_SB(sb); > >>> + bool freeze_sync = sync && > >>> + sb->s_writers.frozen == SB_FREEZE_PAGEFAULT; > >> > >> How about this to make sure freeze_sync will only be true in freeze_super()? > >> > >> bool freeze_sync = sync && rwsem_is_locked(&sb->s_umount) && > >> sb->s_writers.frozen == SB_FREEZE_PAGEFAULT; > >> > >> And it's not needed to rename f2fs_sync_fs() to __f2fs_sync_fs()? > >> > > > > Sorry for the late reply. rwsem_is_locked() doesn't tell us whether > > current owns the lock, so I'm concerned about the following race: > > > > Freezer A Directory fsync B > > holds s_umount write lock > > stage = SB_FREEZE_PAGEFAULT > > holder = current_A > > locks gc_lock > > holder = current_B > > waits for gc_lock > > Oh, right, fsync won't call sb_start_*(). > > One more question, if we set holder under gc_lock, will it resolve this race case? > Setting holder under gc_lock prevents the overwrite race, but a later checkpoint, even from a direct fsync, may outlive the freezer's s_umount write lock: Freezer A Fsync task / checkpoint worker B holds s_umount for write finishes its own checkpoint acquires gc_lock sees SB_FREEZE_PAGEFAULT sets holder = current B computes need_lock = false freeze may fails on dirty check without waiting for B releases s_umount runs quota sync without s_umount So I think we still need to identify whether the current task actually holds s_umount. Thanks, > Thanks, > > > holder != current_A > > s_umount read trylock fails > > retry limit -> QUOTA_SKIP_FLUSH > > sets QUOTA_NEED_FLUSH > > commits CP with quota_need_fsck > > clears IS_DIRTY > > unlocks gc_lock > > holder = NULL > > stage = SB_FREEZE_FS > > locks gc_lock > > !IS_DIRTY && CP_SYNC > > -> returns before quota flush > > unlocks gc_lock > > holder = NULL > > completes f2fs_freeze() > > unlocks s_umount > > FIFREEZE returns > > > > Could we use a separate f2fs_sync_fs_super() wrapper for the VFS callback > > and keep freeze holder management there, leaving f2fs_sync_fs() for > > internal callers? > > > > Thanks, > > > >> Thanks, > >> > >>> + int err; > >>> + > >>> + /* freeze_super() holds s_umount for write during this sync pass. */ > >>> + if (freeze_sync) > >>> + sbi->umount_lock_holder = current; > >>> + > >>> + err = __f2fs_sync_fs(sb, sync); > >>> + > >>> + if (freeze_sync) > >>> + sbi->umount_lock_holder = NULL; > >>> + > >>> + return err; > >>> +} > >>> + > >>> static int f2fs_freeze(struct super_block *sb) > >>> { > >>> struct f2fs_sb_info *sbi = F2FS_SB(sb); > >>> @@ -2807,7 +2826,7 @@ static int f2fs_enable_checkpoint(struct f2fs_sb_info *sbi) > >>> set_sbi_flag(sbi, SBI_IS_DIRTY); > >>> f2fs_up_write_trace(&sbi->gc_lock, &lc); > >>> > >>> - ret = f2fs_sync_fs(sbi->sb, 1); > >>> + ret = __f2fs_sync_fs(sbi->sb, 1); > >>> if (ret) > >>> f2fs_err(sbi, "%s sync_fs failed, ret: %d", __func__, ret); > >>> > >>> @@ -2993,7 +3012,7 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) > >>> > >>> set_sbi_flag(sbi, SBI_IS_DIRTY); > >>> set_sbi_flag(sbi, SBI_IS_CLOSE); > >>> - err = f2fs_sync_fs(sb, 1); > >>> + err = __f2fs_sync_fs(sb, 1); > >>> if (err) > >>> goto restore_gc; > >>> clear_sbi_flag(sbi, SBI_IS_CLOSE); > >> > _______________________________________________ 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] 5+ messages in thread
end of thread, other threads:[~2026-09-16 3:25 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-08 3:48 [f2fs-dev] [PATCH] f2fs: quota: fix quota flush failure during filesystem freeze Jianan Huang 2026-09-09 11:42 ` Chao Yu via Linux-f2fs-devel 2026-09-15 11:39 ` Jianan Huang 2026-09-16 1:59 ` Chao Yu via Linux-f2fs-devel 2026-09-16 3:25 ` Jianan Huang
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.