rcutorture: Add SRCU deadlock scenarios
authorPaul E. McKenney <paulmck@kernel.org>
Sat, 14 Jan 2023 05:34:27 +0000 (21:34 -0800)
committerBoqun Feng <boqun.feng@gmail.com>
Mon, 27 Mar 2023 18:16:10 +0000 (11:16 -0700)
In order to test the new SRCU-lockdep functionality, this commit adds
an rcutorture.test_srcu_lockdep module parameter that, when non-zero,
selects an SRCU deadlock scenario to execute.  This parameter is a
five-digit number formatted as DNNL, where "D" is 1 to force a deadlock
and 0 to avoid doing so; "NN" is the test number, 0 for SRCU-based, 1
for SRCU/mutex-based, and 2 for SRCU/rwsem-based; and "L" is the number
of steps in the deadlock cycle.

Note that rcutorture.test_srcu_lockdep=1 will also force a hard hang.

If a non-zero value of rcutorture.test_srcu_lockdep does not select a
deadlock scenario, a console message is printed and testing continues.

[ paulmck: Apply kernel test robot feedback, add rwsem support. ]
[ paulmck: Apply Dan Carpenter feedback. ]

Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Boqun Feng <boqun.feng@gmail.com>
kernel/rcu/rcutorture.c

index 8e6c023..80ff9a7 100644 (file)
@@ -120,6 +120,7 @@ torture_param(int, test_boost, 1, "Test RCU prio boost: 0=no, 1=maybe, 2=yes.");
 torture_param(int, test_boost_duration, 4, "Duration of each boost test, seconds.");
 torture_param(int, test_boost_interval, 7, "Interval between boost tests, seconds.");
 torture_param(bool, test_no_idle_hz, true, "Test support for tickless idle CPUs");
+torture_param(int, test_srcu_lockdep, 0, "Test specified SRCU deadlock scenario.");
 torture_param(int, verbose, 1, "Enable verbose debugging printk()s");
 
 static char *torture_type = "rcu";
@@ -3463,6 +3464,154 @@ static void rcutorture_sync(void)
                cur_ops->sync();
 }
 
+static DEFINE_MUTEX(mut0);
+static DEFINE_MUTEX(mut1);
+static DEFINE_MUTEX(mut2);
+static DEFINE_MUTEX(mut3);
+static DEFINE_MUTEX(mut4);
+static DEFINE_MUTEX(mut5);
+static DEFINE_MUTEX(mut6);
+static DEFINE_MUTEX(mut7);
+static DEFINE_MUTEX(mut8);
+static DEFINE_MUTEX(mut9);
+
+static DECLARE_RWSEM(rwsem0);
+static DECLARE_RWSEM(rwsem1);
+static DECLARE_RWSEM(rwsem2);
+static DECLARE_RWSEM(rwsem3);
+static DECLARE_RWSEM(rwsem4);
+static DECLARE_RWSEM(rwsem5);
+static DECLARE_RWSEM(rwsem6);
+static DECLARE_RWSEM(rwsem7);
+static DECLARE_RWSEM(rwsem8);
+static DECLARE_RWSEM(rwsem9);
+
+DEFINE_STATIC_SRCU(srcu0);
+DEFINE_STATIC_SRCU(srcu1);
+DEFINE_STATIC_SRCU(srcu2);
+DEFINE_STATIC_SRCU(srcu3);
+DEFINE_STATIC_SRCU(srcu4);
+DEFINE_STATIC_SRCU(srcu5);
+DEFINE_STATIC_SRCU(srcu6);
+DEFINE_STATIC_SRCU(srcu7);
+DEFINE_STATIC_SRCU(srcu8);
+DEFINE_STATIC_SRCU(srcu9);
+
+static int srcu_lockdep_next(const char *f, const char *fl, const char *fs, const char *fu, int i,
+                            int cyclelen, int deadlock)
+{
+       int j = i + 1;
+
+       if (j >= cyclelen)
+               j = deadlock ? 0 : -1;
+       if (j >= 0)
+               pr_info("%s: %s(%d), %s(%d), %s(%d)\n", f, fl, i, fs, j, fu, i);
+       else
+               pr_info("%s: %s(%d), %s(%d)\n", f, fl, i, fu, i);
+       return j;
+}
+
+// Test lockdep on SRCU-based deadlock scenarios.
+static void rcu_torture_init_srcu_lockdep(void)
+{
+       int cyclelen;
+       int deadlock;
+       bool err = false;
+       int i;
+       int j;
+       int idx;
+       struct mutex *muts[] = { &mut0, &mut1, &mut2, &mut3, &mut4,
+                                &mut5, &mut6, &mut7, &mut8, &mut9 };
+       struct rw_semaphore *rwsems[] = { &rwsem0, &rwsem1, &rwsem2, &rwsem3, &rwsem4,
+                                         &rwsem5, &rwsem6, &rwsem7, &rwsem8, &rwsem9 };
+       struct srcu_struct *srcus[] = { &srcu0, &srcu1, &srcu2, &srcu3, &srcu4,
+                                       &srcu5, &srcu6, &srcu7, &srcu8, &srcu9 };
+       int testtype;
+
+       if (!test_srcu_lockdep)
+               return;
+
+       deadlock = test_srcu_lockdep / 1000;
+       testtype = (test_srcu_lockdep / 10) % 100;
+       cyclelen = test_srcu_lockdep % 10;
+       WARN_ON_ONCE(ARRAY_SIZE(muts) != ARRAY_SIZE(srcus));
+       if (WARN_ONCE(deadlock != !!deadlock,
+                     "%s: test_srcu_lockdep=%d and deadlock digit %d must be zero or one.\n",
+                     __func__, test_srcu_lockdep, deadlock))
+               err = true;
+       if (WARN_ONCE(cyclelen <= 0,
+                     "%s: test_srcu_lockdep=%d and cycle-length digit %d must be greater than zero.\n",
+                     __func__, test_srcu_lockdep, cyclelen))
+               err = true;
+       if (err)
+               goto err_out;
+
+       if (testtype == 0) {
+               pr_info("%s: test_srcu_lockdep = %05d: SRCU %d-way %sdeadlock.\n",
+                       __func__, test_srcu_lockdep, cyclelen, deadlock ? "" : "non-");
+               if (deadlock && cyclelen == 1)
+                       pr_info("%s: Expect hang.\n", __func__);
+               for (i = 0; i < cyclelen; i++) {
+                       j = srcu_lockdep_next(__func__, "srcu_read_lock", "synchronize_srcu",
+                                             "srcu_read_unlock", i, cyclelen, deadlock);
+                       idx = srcu_read_lock(srcus[i]);
+                       if (j >= 0)
+                               synchronize_srcu(srcus[j]);
+                       srcu_read_unlock(srcus[i], idx);
+               }
+               return;
+       }
+
+       if (testtype == 1) {
+               pr_info("%s: test_srcu_lockdep = %05d: SRCU/mutex %d-way %sdeadlock.\n",
+                       __func__, test_srcu_lockdep, cyclelen, deadlock ? "" : "non-");
+               for (i = 0; i < cyclelen; i++) {
+                       pr_info("%s: srcu_read_lock(%d), mutex_lock(%d), mutex_unlock(%d), srcu_read_unlock(%d)\n",
+                               __func__, i, i, i, i);
+                       idx = srcu_read_lock(srcus[i]);
+                       mutex_lock(muts[i]);
+                       mutex_unlock(muts[i]);
+                       srcu_read_unlock(srcus[i], idx);
+
+                       j = srcu_lockdep_next(__func__, "mutex_lock", "synchronize_srcu",
+                                             "mutex_unlock", i, cyclelen, deadlock);
+                       mutex_lock(muts[i]);
+                       if (j >= 0)
+                               synchronize_srcu(srcus[j]);
+                       mutex_unlock(muts[i]);
+               }
+               return;
+       }
+
+       if (testtype == 2) {
+               pr_info("%s: test_srcu_lockdep = %05d: SRCU/rwsem %d-way %sdeadlock.\n",
+                       __func__, test_srcu_lockdep, cyclelen, deadlock ? "" : "non-");
+               for (i = 0; i < cyclelen; i++) {
+                       pr_info("%s: srcu_read_lock(%d), down_read(%d), up_read(%d), srcu_read_unlock(%d)\n",
+                               __func__, i, i, i, i);
+                       idx = srcu_read_lock(srcus[i]);
+                       down_read(rwsems[i]);
+                       up_read(rwsems[i]);
+                       srcu_read_unlock(srcus[i], idx);
+
+                       j = srcu_lockdep_next(__func__, "down_write", "synchronize_srcu",
+                                             "up_write", i, cyclelen, deadlock);
+                       down_write(rwsems[i]);
+                       if (j >= 0)
+                               synchronize_srcu(srcus[j]);
+                       up_write(rwsems[i]);
+               }
+               return;
+       }
+
+err_out:
+       pr_info("%s: test_srcu_lockdep = %05d does nothing.\n", __func__, test_srcu_lockdep);
+       pr_info("%s: test_srcu_lockdep = DNNL.\n", __func__);
+       pr_info("%s: D: Deadlock if nonzero.\n", __func__);
+       pr_info("%s: NN: Test number, 0=SRCU, 1=SRCU/mutex, 2=SRCU/rwsem.\n", __func__);
+       pr_info("%s: L: Cycle length.\n", __func__);
+}
+
 static int __init
 rcu_torture_init(void)
 {
@@ -3504,6 +3653,8 @@ rcu_torture_init(void)
        if (cur_ops->init)
                cur_ops->init();
 
+       rcu_torture_init_srcu_lockdep();
+
        if (nreaders >= 0) {
                nrealreaders = nreaders;
        } else {