rcu: Allow rcutorture to starve grace-period kthread
authorPaul E. McKenney <paulmck@kernel.org>
Thu, 2 Apr 2020 02:57:52 +0000 (19:57 -0700)
committerPaul E. McKenney <paulmck@kernel.org>
Thu, 7 May 2020 17:15:28 +0000 (10:15 -0700)
This commit provides an rcutorture.stall_gp_kthread module parameter
to allow rcutorture to starve the grace-period kthread.  This allows
testing the code that detects such starvation.

Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Documentation/admin-guide/kernel-parameters.txt
kernel/rcu/rcu.h
kernel/rcu/rcutorture.c
kernel/rcu/tree.c

index ad12b39..be94358 100644 (file)
        rcutorture.stall_cpu_irqsoff= [KNL]
                        Disable interrupts while stalling if set.
 
+       rcutorture.stall_gp_kthread= [KNL]
+                       Duration (s) of forced sleep within RCU
+                       grace-period kthread to test RCU CPU stall
+                       warnings, zero to disable.  If both stall_cpu
+                       and stall_gp_kthread are specified, the
+                       kthread is starved first, then the CPU.
+
        rcutorture.stat_interval= [KNL]
                        Time (s) between statistics printk()s.
 
index 00ddc92..cdbc5f9 100644 (file)
@@ -454,6 +454,7 @@ void do_trace_rcu_torture_read(const char *rcutorturename,
                               unsigned long secs,
                               unsigned long c_old,
                               unsigned long c);
+void rcu_gp_set_torture_wait(int duration);
 #else
 static inline void rcutorture_get_gp_data(enum rcutorture_type test_type,
                                          int *flags, unsigned long *gp_seq)
@@ -471,6 +472,7 @@ void do_trace_rcu_torture_read(const char *rcutorturename,
 #define do_trace_rcu_torture_read(rcutorturename, rhp, secs, c_old, c) \
        do { } while (0)
 #endif
+static inline void rcu_gp_set_torture_wait(int duration) { }
 #endif
 
 #if IS_ENABLED(CONFIG_RCU_TORTURE_TEST) || IS_MODULE(CONFIG_RCU_TORTURE_TEST)
index 60dc368..3d47dca 100644 (file)
@@ -115,6 +115,8 @@ torture_param(int, stall_cpu_holdoff, 10,
             "Time to wait before starting stall (s).");
 torture_param(int, stall_cpu_irqsoff, 0, "Disable interrupts while stalling.");
 torture_param(int, stall_cpu_block, 0, "Sleep while stalling.");
+torture_param(int, stall_gp_kthread, 0,
+             "Grace-period kthread stall duration (s).");
 torture_param(int, stat_interval, 60,
             "Number of seconds between stats printk()s");
 torture_param(int, stutter, 5, "Number of seconds to run/halt test");
@@ -1623,7 +1625,17 @@ static int rcu_torture_stall(void *args)
                schedule_timeout_interruptible(stall_cpu_holdoff * HZ);
                VERBOSE_TOROUT_STRING("rcu_torture_stall end holdoff");
        }
-       if (!kthread_should_stop()) {
+       if (!kthread_should_stop() && stall_gp_kthread > 0) {
+               VERBOSE_TOROUT_STRING("rcu_torture_stall begin GP stall");
+               rcu_gp_set_torture_wait(stall_gp_kthread * HZ);
+               for (idx = 0; idx < stall_gp_kthread + 2; idx++) {
+                       if (kthread_should_stop())
+                               break;
+                       schedule_timeout_uninterruptible(HZ);
+               }
+       }
+       if (!kthread_should_stop() && stall_cpu > 0) {
+               VERBOSE_TOROUT_STRING("rcu_torture_stall begin CPU stall");
                stop_at = ktime_get_seconds() + stall_cpu;
                /* RCU CPU stall is expected behavior in following code. */
                idx = cur_ops->readlock();
@@ -1642,8 +1654,8 @@ static int rcu_torture_stall(void *args)
                else if (!stall_cpu_block)
                        preempt_enable();
                cur_ops->readunlock(idx);
-               pr_alert("rcu_torture_stall end.\n");
        }
+       pr_alert("rcu_torture_stall end.\n");
        torture_shutdown_absorb("rcu_torture_stall");
        while (!kthread_should_stop())
                schedule_timeout_interruptible(10 * HZ);
@@ -1653,7 +1665,7 @@ static int rcu_torture_stall(void *args)
 /* Spawn CPU-stall kthread, if stall_cpu specified. */
 static int __init rcu_torture_stall_init(void)
 {
-       if (stall_cpu <= 0)
+       if (stall_cpu <= 0 && stall_gp_kthread <= 0)
                return 0;
        return torture_create_kthread(rcu_torture_stall, NULL, stall_task);
 }
index 156ac8d..be7dde8 100644 (file)
@@ -1486,6 +1486,31 @@ static void rcu_gp_slow(int delay)
                schedule_timeout_uninterruptible(delay);
 }
 
+static unsigned long sleep_duration;
+
+/* Allow rcutorture to stall the grace-period kthread. */
+void rcu_gp_set_torture_wait(int duration)
+{
+       if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST) && duration > 0)
+               WRITE_ONCE(sleep_duration, duration);
+}
+EXPORT_SYMBOL_GPL(rcu_gp_set_torture_wait);
+
+/* Actually implement the aforementioned wait. */
+static void rcu_gp_torture_wait(void)
+{
+       unsigned long duration;
+
+       if (!IS_ENABLED(CONFIG_RCU_TORTURE_TEST))
+               return;
+       duration = xchg(&sleep_duration, 0UL);
+       if (duration > 0) {
+               pr_alert("%s: Waiting %lu jiffies\n", __func__, duration);
+               schedule_timeout_uninterruptible(duration);
+               pr_alert("%s: Wait complete\n", __func__);
+       }
+}
+
 /*
  * Initialize a new grace period.  Return false if no grace period required.
  */
@@ -1686,6 +1711,7 @@ static void rcu_gp_fqs_loop(void)
                rcu_state.gp_state = RCU_GP_WAIT_FQS;
                ret = swait_event_idle_timeout_exclusive(
                                rcu_state.gp_wq, rcu_gp_fqs_check_wake(&gf), j);
+               rcu_gp_torture_wait();
                rcu_state.gp_state = RCU_GP_DOING_FQS;
                /* Locking provides needed memory barriers. */
                /* If grace period done, leave loop. */
@@ -1834,6 +1860,7 @@ static int __noreturn rcu_gp_kthread(void *unused)
                        swait_event_idle_exclusive(rcu_state.gp_wq,
                                         READ_ONCE(rcu_state.gp_flags) &
                                         RCU_GP_FLAG_INIT);
+                       rcu_gp_torture_wait();
                        rcu_state.gp_state = RCU_GP_DONE_GPS;
                        /* Locking provides needed memory barrier. */
                        if (rcu_gp_init())