On Sat, Mar 27, 2021 at 05:57:06PM +0800, Chao Yu wrote:
> In this patch, we will add two new mount options: "gc_merge" and
> "nogc_merge", when background_gc is on, "gc_merge" option can be
> set to let background GC thread to handle foreground GC requests,
> it can eliminate the sluggish issue caused by slow foreground GC
> operation when GC is triggered from a process with limited I/O
> and CPU resources.
> 
> Original idea is from Xiang.
> 
> Signed-off-by: Gao Xiang <xi...@kernel.org>
> Signed-off-by: Chao Yu <yuch...@huawei.com>

Ah, that was a quite old commit many years ago due to priority inversion
issue ;-) I vaguely remembered some potential wakeup race condition which
was addressed in the internal branch...Yet I have no idea about those now
LOL.

Thanks for redoing this and sending it out to the upstream... :-)

Thanks,
Gao Xiang

> ---
>  Documentation/filesystems/f2fs.rst |  6 ++++++
>  fs/f2fs/f2fs.h                     |  1 +
>  fs/f2fs/gc.c                       | 26 ++++++++++++++++++++++----
>  fs/f2fs/gc.h                       |  6 ++++++
>  fs/f2fs/segment.c                  | 15 +++++++++++++--
>  fs/f2fs/super.c                    | 19 +++++++++++++++++--
>  6 files changed, 65 insertions(+), 8 deletions(-)
> 
> diff --git a/Documentation/filesystems/f2fs.rst 
> b/Documentation/filesystems/f2fs.rst
> index 35ed01a5fbc9..63c0c49b726d 100644
> --- a/Documentation/filesystems/f2fs.rst
> +++ b/Documentation/filesystems/f2fs.rst
> @@ -110,6 +110,12 @@ background_gc=%s  Turn on/off cleaning operations, 
> namely garbage
>                        on synchronous garbage collection running in 
> background.
>                        Default value for this option is on. So garbage
>                        collection is on by default.
> +gc_merge              When background_gc is on, this option can be enabled to
> +                      let background GC thread to handle foreground GC 
> requests,
> +                      it can eliminate the sluggish issue caused by slow 
> foreground
> +                      GC operation when GC is triggered from a process with 
> limited
> +                      I/O and CPU resources.
> +nogc_merge            Disable GC merge feature.
>  disable_roll_forward  Disable the roll-forward recovery routine
>  norecovery            Disable the roll-forward recovery routine, mounted 
> read-
>                        only (i.e., -o ro,disable_roll_forward)
> diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
> index fe380bcf8d4d..87d734f5589d 100644
> --- a/fs/f2fs/f2fs.h
> +++ b/fs/f2fs/f2fs.h
> @@ -97,6 +97,7 @@ extern const char *f2fs_fault_name[FAULT_MAX];
>  #define F2FS_MOUNT_NORECOVERY                0x04000000
>  #define F2FS_MOUNT_ATGC                      0x08000000
>  #define F2FS_MOUNT_MERGE_CHECKPOINT  0x10000000
> +#define      F2FS_MOUNT_GC_MERGE             0x20000000
>  
>  #define F2FS_OPTION(sbi)     ((sbi)->mount_opt)
>  #define clear_opt(sbi, option)       (F2FS_OPTION(sbi).opt &= 
> ~F2FS_MOUNT_##option)
> diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c
> index a2ca483f9855..5c48825fd12d 100644
> --- a/fs/f2fs/gc.c
> +++ b/fs/f2fs/gc.c
> @@ -31,19 +31,24 @@ static int gc_thread_func(void *data)
>       struct f2fs_sb_info *sbi = data;
>       struct f2fs_gc_kthread *gc_th = sbi->gc_thread;
>       wait_queue_head_t *wq = &sbi->gc_thread->gc_wait_queue_head;
> +     wait_queue_head_t *fggc_wq = &sbi->gc_thread->fggc_wq;
>       unsigned int wait_ms;
>  
>       wait_ms = gc_th->min_sleep_time;
>  
>       set_freezable();
>       do {
> -             bool sync_mode;
> +             bool sync_mode, foreground = false;
>  
>               wait_event_interruptible_timeout(*wq,
>                               kthread_should_stop() || freezing(current) ||
> +                             waitqueue_active(fggc_wq) ||
>                               gc_th->gc_wake,
>                               msecs_to_jiffies(wait_ms));
>  
> +             if (test_opt(sbi, GC_MERGE) && waitqueue_active(fggc_wq))
> +                     foreground = true;
> +
>               /* give it a try one time */
>               if (gc_th->gc_wake)
>                       gc_th->gc_wake = 0;
> @@ -90,7 +95,10 @@ static int gc_thread_func(void *data)
>                       goto do_gc;
>               }
>  
> -             if (!down_write_trylock(&sbi->gc_lock)) {
> +             if (foreground) {
> +                     down_write(&sbi->gc_lock);
> +                     goto do_gc;
> +             } else if (!down_write_trylock(&sbi->gc_lock)) {
>                       stat_other_skip_bggc_count(sbi);
>                       goto next;
>               }
> @@ -107,14 +115,22 @@ static int gc_thread_func(void *data)
>               else
>                       increase_sleep_time(gc_th, &wait_ms);
>  do_gc:
> -             stat_inc_bggc_count(sbi->stat_info);
> +             if (!foreground)
> +                     stat_inc_bggc_count(sbi->stat_info);
>  
>               sync_mode = F2FS_OPTION(sbi).bggc_mode == BGGC_MODE_SYNC;
>  
> +             /* foreground GC was been triggered via f2fs_balance_fs() */
> +             if (foreground)
> +                     sync_mode = false;
> +
>               /* if return value is not zero, no victim was selected */
> -             if (f2fs_gc(sbi, sync_mode, true, false, NULL_SEGNO))
> +             if (f2fs_gc(sbi, sync_mode, !foreground, false, NULL_SEGNO))
>                       wait_ms = gc_th->no_gc_sleep_time;
>  
> +             if (foreground)
> +                     wake_up_all(&gc_th->fggc_wq);
> +
>               trace_f2fs_background_gc(sbi->sb, wait_ms,
>                               prefree_segments(sbi), free_segments(sbi));
>  
> @@ -148,6 +164,7 @@ int f2fs_start_gc_thread(struct f2fs_sb_info *sbi)
>  
>       sbi->gc_thread = gc_th;
>       init_waitqueue_head(&sbi->gc_thread->gc_wait_queue_head);
> +     init_waitqueue_head(&sbi->gc_thread->fggc_wq);
>       sbi->gc_thread->f2fs_gc_task = kthread_run(gc_thread_func, sbi,
>                       "f2fs_gc-%u:%u", MAJOR(dev), MINOR(dev));
>       if (IS_ERR(gc_th->f2fs_gc_task)) {
> @@ -165,6 +182,7 @@ void f2fs_stop_gc_thread(struct f2fs_sb_info *sbi)
>       if (!gc_th)
>               return;
>       kthread_stop(gc_th->f2fs_gc_task);
> +     wake_up_all(&gc_th->fggc_wq);
>       kfree(gc_th);
>       sbi->gc_thread = NULL;
>  }
> diff --git a/fs/f2fs/gc.h b/fs/f2fs/gc.h
> index 0c8dae12dc51..3fe145e8e594 100644
> --- a/fs/f2fs/gc.h
> +++ b/fs/f2fs/gc.h
> @@ -42,6 +42,12 @@ struct f2fs_gc_kthread {
>  
>       /* for changing gc mode */
>       unsigned int gc_wake;
> +
> +     /* for GC_MERGE mount option */
> +     wait_queue_head_t fggc_wq;              /*
> +                                              * caller of f2fs_balance_fs()
> +                                              * will wait on this wait queue.
> +                                              */
>  };
>  
>  struct gc_inode_list {
> diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c
> index 24ad45f5e335..31ccea1378fa 100644
> --- a/fs/f2fs/segment.c
> +++ b/fs/f2fs/segment.c
> @@ -503,8 +503,19 @@ void f2fs_balance_fs(struct f2fs_sb_info *sbi, bool need)
>        * dir/node pages without enough free segments.
>        */
>       if (has_not_enough_free_secs(sbi, 0, 0)) {
> -             down_write(&sbi->gc_lock);
> -             f2fs_gc(sbi, false, false, false, NULL_SEGNO);
> +             if (test_opt(sbi, GC_MERGE) && sbi->gc_thread &&
> +                                     sbi->gc_thread->f2fs_gc_task) {
> +                     DEFINE_WAIT(wait);
> +
> +                     prepare_to_wait(&sbi->gc_thread->fggc_wq, &wait,
> +                                             TASK_UNINTERRUPTIBLE);
> +                     wake_up(&sbi->gc_thread->gc_wait_queue_head);
> +                     io_schedule();
> +                     finish_wait(&sbi->gc_thread->fggc_wq, &wait);
> +             } else {
> +                     down_write(&sbi->gc_lock);
> +                     f2fs_gc(sbi, false, false, false, NULL_SEGNO);
> +             }
>       }
>  }
>  
> diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
> index b48281642e98..954b1fe97d67 100644
> --- a/fs/f2fs/super.c
> +++ b/fs/f2fs/super.c
> @@ -151,6 +151,8 @@ enum {
>       Opt_compress_chksum,
>       Opt_compress_mode,
>       Opt_atgc,
> +     Opt_gc_merge,
> +     Opt_nogc_merge,
>       Opt_err,
>  };
>  
> @@ -223,6 +225,8 @@ static match_table_t f2fs_tokens = {
>       {Opt_compress_chksum, "compress_chksum"},
>       {Opt_compress_mode, "compress_mode=%s"},
>       {Opt_atgc, "atgc"},
> +     {Opt_gc_merge, "gc_merge"},
> +     {Opt_nogc_merge, "nogc_merge"},
>       {Opt_err, NULL},
>  };
>  
> @@ -1073,6 +1077,12 @@ static int parse_options(struct super_block *sb, char 
> *options, bool is_remount)
>               case Opt_atgc:
>                       set_opt(sbi, ATGC);
>                       break;
> +             case Opt_gc_merge:
> +                     set_opt(sbi, GC_MERGE);
> +                     break;
> +             case Opt_nogc_merge:
> +                     clear_opt(sbi, GC_MERGE);
> +                     break;
>               default:
>                       f2fs_err(sbi, "Unrecognized mount option \"%s\" or 
> missing value",
>                                p);
> @@ -1675,6 +1685,9 @@ static int f2fs_show_options(struct seq_file *seq, 
> struct dentry *root)
>       else if (F2FS_OPTION(sbi).bggc_mode == BGGC_MODE_OFF)
>               seq_printf(seq, ",background_gc=%s", "off");
>  
> +     if (test_opt(sbi, GC_MERGE))
> +             seq_puts(seq, ",gc_merge");
> +
>       if (test_opt(sbi, DISABLE_ROLL_FORWARD))
>               seq_puts(seq, ",disable_roll_forward");
>       if (test_opt(sbi, NORECOVERY))
> @@ -2038,7 +2051,8 @@ static int f2fs_remount(struct super_block *sb, int 
> *flags, char *data)
>        * option. Also sync the filesystem.
>        */
>       if ((*flags & SB_RDONLY) ||
> -                     F2FS_OPTION(sbi).bggc_mode == BGGC_MODE_OFF) {
> +                     (F2FS_OPTION(sbi).bggc_mode == BGGC_MODE_OFF &&
> +                     !test_opt(sbi, GC_MERGE))) {
>               if (sbi->gc_thread) {
>                       f2fs_stop_gc_thread(sbi);
>                       need_restart_gc = true;
> @@ -4012,7 +4026,8 @@ static int f2fs_fill_super(struct super_block *sb, void 
> *data, int silent)
>        * If filesystem is not mounted as read-only then
>        * do start the gc_thread.
>        */
> -     if (F2FS_OPTION(sbi).bggc_mode != BGGC_MODE_OFF && !f2fs_readonly(sb)) {
> +     if ((F2FS_OPTION(sbi).bggc_mode != BGGC_MODE_OFF ||
> +             test_opt(sbi, GC_MERGE)) && !f2fs_readonly(sb)) {
>               /* After POR, we can run background GC thread.*/
>               err = f2fs_start_gc_thread(sbi);
>               if (err)
> -- 
> 2.29.2
> 

Reply via email to