bcache: avoid unnecessary soft lockup in kworker update_writeback_rate()
authorColy Li <colyli@suse.de>
Sat, 28 May 2022 12:45:50 +0000 (20:45 +0800)
committerJens Axboe <axboe@kernel.dk>
Sat, 28 May 2022 12:48:26 +0000 (06:48 -0600)
The kworker routine update_writeback_rate() is schedued to update the
writeback rate in every 5 seconds by default. Before calling
__update_writeback_rate() to do real job, semaphore dc->writeback_lock
should be held by the kworker routine.

At the same time, bcache writeback thread routine bch_writeback_thread()
also needs to hold dc->writeback_lock before flushing dirty data back
into the backing device. If the dirty data set is large, it might be
very long time for bch_writeback_thread() to scan all dirty buckets and
releases dc->writeback_lock. In such case update_writeback_rate() can be
starved for long enough time so that kernel reports a soft lockup warn-
ing started like:
  watchdog: BUG: soft lockup - CPU#246 stuck for 23s! [kworker/246:31:179713]

Such soft lockup condition is unnecessary, because after the writeback
thread finishes its job and releases dc->writeback_lock, the kworker
update_writeback_rate() may continue to work and everything is fine
indeed.

This patch avoids the unnecessary soft lockup by the following method,
- Add new member to struct cached_dev
  - dc->rate_update_retry (0 by default)
- In update_writeback_rate() call down_read_trylock(&dc->writeback_lock)
  firstly, if it fails then lock contention happens.
- If dc->rate_update_retry <= BCH_WBRATE_UPDATE_MAX_SKIPS (15), doesn't
  acquire the lock and reschedules the kworker for next try.
- If dc->rate_update_retry > BCH_WBRATE_UPDATE_MAX_SKIPS, no retry
  anymore and call down_read(&dc->writeback_lock) to wait for the lock.

By the above method, at worst case update_writeback_rate() may retry for
1+ minutes before blocking on dc->writeback_lock by calling down_read().
For a 4TB cache device with 1TB dirty data, 90%+ of the unnecessary soft
lockup warning message can be avoided.

When retrying to acquire dc->writeback_lock in update_writeback_rate(),
of course the writeback rate cannot be updated. It is fair, because when
the kworker is blocked on the lock contention of dc->writeback_lock, the
writeback rate cannot be updated neither.

This change follows Jens Axboe's suggestion to a more clear and simple
version.

Signed-off-by: Coly Li <colyli@suse.de>
Link: https://lore.kernel.org/r/20220528124550.32834-2-colyli@suse.de
Signed-off-by: Jens Axboe <axboe@kernel.dk>
drivers/md/bcache/bcache.h
drivers/md/bcache/writeback.c

index 9ed9c95..2acda9c 100644 (file)
@@ -395,6 +395,13 @@ struct cached_dev {
        atomic_t                io_errors;
        unsigned int            error_limit;
        unsigned int            offline_seconds;
+
+       /*
+        * Retry to update writeback_rate if contention happens for
+        * down_read(dc->writeback_lock) in update_writeback_rate()
+        */
+#define BCH_WBRATE_UPDATE_MAX_SKIPS    15
+       unsigned int            rate_update_retry;
 };
 
 enum alloc_reserve {
index d138a2d..3f0ff3a 100644 (file)
@@ -235,19 +235,27 @@ static void update_writeback_rate(struct work_struct *work)
                return;
        }
 
-       if (atomic_read(&dc->has_dirty) && dc->writeback_percent) {
-               /*
-                * If the whole cache set is idle, set_at_max_writeback_rate()
-                * will set writeback rate to a max number. Then it is
-                * unncessary to update writeback rate for an idle cache set
-                * in maximum writeback rate number(s).
-                */
-               if (!set_at_max_writeback_rate(c, dc)) {
-                       down_read(&dc->writeback_lock);
+       /*
+        * If the whole cache set is idle, set_at_max_writeback_rate()
+        * will set writeback rate to a max number. Then it is
+        * unncessary to update writeback rate for an idle cache set
+        * in maximum writeback rate number(s).
+        */
+       if (atomic_read(&dc->has_dirty) && dc->writeback_percent &&
+           !set_at_max_writeback_rate(c, dc)) {
+               do {
+                       if (!down_read_trylock((&dc->writeback_lock))) {
+                               dc->rate_update_retry++;
+                               if (dc->rate_update_retry <=
+                                   BCH_WBRATE_UPDATE_MAX_SKIPS)
+                                       break;
+                               down_read(&dc->writeback_lock);
+                               dc->rate_update_retry = 0;
+                       }
                        __update_writeback_rate(dc);
                        update_gc_after_writeback(c);
                        up_read(&dc->writeback_lock);
-               }
+               } while (0);
        }
 
 
@@ -1006,6 +1014,9 @@ void bch_cached_dev_writeback_init(struct cached_dev *dc)
        dc->writeback_rate_fp_term_high = 1000;
        dc->writeback_rate_i_term_inverse = 10000;
 
+       /* For dc->writeback_lock contention in update_writeback_rate() */
+       dc->rate_update_retry = 0;
+
        WARN_ON(test_and_clear_bit(BCACHE_DEV_WB_RUNNING, &dc->disk.flags));
        INIT_DELAYED_WORK(&dc->writeback_rate_update, update_writeback_rate);
 }