sched/fair: Limit sched_cfs_period_timer() loop to avoid hard lockup [Linux 3.16.72]

This Linux kernel change "sched/fair: Limit sched_cfs_period_timer() loop to avoid hard lockup" is included in the Linux 3.16.72 release. This change is authored by Phil Auld <pauld [at] redhat.com> on Tue Mar 19 09:00:05 2019 -0400. The commit for this change in Linux stable tree is 1fc39f2 (patch) which is from upstream commit 2e8e192. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream 2e8e192.

sched/fair: Limit sched_cfs_period_timer() loop to avoid hard lockup

commit 2e8e19226398db8265a8e675fcc0118b9e80c9e8 upstream.

With extremely short cfs_period_us setting on a parent task group with a large
number of children the for loop in sched_cfs_period_timer() can run until the
watchdog fires. There is no guarantee that the call to hrtimer_forward_now()
will ever return 0.  The large number of children can make
do_sched_cfs_period_timer() take longer than the period.

 NMI watchdog: Watchdog detected hard LOCKUP on cpu 24
 RIP: 0010:tg_nop+0x0/0x10
  <IRQ>
  walk_tg_tree_from+0x29/0xb0
  unthrottle_cfs_rq+0xe0/0x1a0
  distribute_cfs_runtime+0xd3/0xf0
  sched_cfs_period_timer+0xcb/0x160
  ? sched_cfs_slack_timer+0xd0/0xd0
  __hrtimer_run_queues+0xfb/0x270
  hrtimer_interrupt+0x122/0x270
  smp_apic_timer_interrupt+0x6a/0x140
  apic_timer_interrupt+0xf/0x20
  </IRQ>

To prevent this we add protection to the loop that detects when the loop has run
too many times and scales the period and quota up, proportionally, so that the timer
can complete before then next period expires.  This preserves the relative runtime
quota while preventing the hard lockup.

A warning is issued reporting this state and the new values.

Signed-off-by: Phil Auld <[email protected]>
Signed-off-by: Peter Zijlstra (Intel) <[email protected]>
Cc: Anton Blanchard <[email protected]>
Cc: Ben Segall <[email protected]>
Cc: Linus Torvalds <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Link: https://lkml.kernel.org/r/[email protected]
Signed-off-by: Ingo Molnar <[email protected]>
[bwh: Backported to 3.16: adjust context]
Signed-off-by: Ben Hutchings <[email protected]>

There are 25 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 kernel/sched/fair.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 08b6858..7dfaf3c 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -3704,6 +3704,8 @@ static enum hrtimer_restart sched_cfs_slack_timer(struct hrtimer *timer)
    return HRTIMER_NORESTART;
 }

+extern const u64 max_cfs_quota_period;
+
 static enum hrtimer_restart sched_cfs_period_timer(struct hrtimer *timer)
 {
    struct cfs_bandwidth *cfs_b =
@@ -3711,6 +3713,7 @@ static enum hrtimer_restart sched_cfs_period_timer(struct hrtimer *timer)
    ktime_t now;
    int overrun;
    int idle = 0;
+   int count = 0;

    raw_spin_lock(&cfs_b->lock);
    for (;;) {
@@ -3720,6 +3723,28 @@ static enum hrtimer_restart sched_cfs_period_timer(struct hrtimer *timer)
        if (!overrun)
            break;

+       if (++count > 3) {
+           u64 new, old = ktime_to_ns(cfs_b->period);
+
+           new = (old * 147) / 128; /* ~115% */
+           new = min(new, max_cfs_quota_period);
+
+           cfs_b->period = ns_to_ktime(new);
+
+           /* since max is 1s, this is limited to 1e9^2, which fits in u64 */
+           cfs_b->quota *= new;
+           cfs_b->quota = div64_u64(cfs_b->quota, old);
+
+           pr_warn_ratelimited(
+   "cfs_period_timer[cpu%d]: period too short, scaling up (new cfs_period_us %lld, cfs_quota_us = %lld)\n",
+               smp_processor_id(),
+               div_u64(new, NSEC_PER_USEC),
+               div_u64(cfs_b->quota, NSEC_PER_USEC));
+
+           /* reset count so we don't come right back in here */
+           count = 0;
+       }
+
        idle = do_sched_cfs_period_timer(cfs_b, overrun);
    }
    raw_spin_unlock(&cfs_b->lock);

Leave a Reply

Your email address will not be published. Required fields are marked *