This commit adds an OOM notifier during rcutorture forward-progress
testing.  If this notifier is invoked, it dumps out some grace-period
state to help debug the forward-progress problem.

Signed-off-by: Paul E. McKenney <paul...@linux.ibm.com>
---
 kernel/rcu/rcu.h        |  2 ++
 kernel/rcu/rcutorture.c | 31 ++++++++++++++++++++++++++++---
 kernel/rcu/tree.c       | 20 ++++++++++++++++++++
 3 files changed, 50 insertions(+), 3 deletions(-)

diff --git a/kernel/rcu/rcu.h b/kernel/rcu/rcu.h
index 0f0f5ae8c3d4..a393e24a9195 100644
--- a/kernel/rcu/rcu.h
+++ b/kernel/rcu/rcu.h
@@ -526,12 +526,14 @@ srcu_batches_completed(struct srcu_struct *sp) { return 
0; }
 static inline void rcu_force_quiescent_state(void) { }
 static inline void show_rcu_gp_kthreads(void) { }
 static inline int rcu_get_gp_kthreads_prio(void) { return 0; }
+static inline void rcu_fwd_progress_check(unsigned long j) { }
 #else /* #ifdef CONFIG_TINY_RCU */
 unsigned long rcu_get_gp_seq(void);
 unsigned long rcu_exp_batches_completed(void);
 unsigned long srcu_batches_completed(struct srcu_struct *sp);
 void show_rcu_gp_kthreads(void);
 int rcu_get_gp_kthreads_prio(void);
+void rcu_fwd_progress_check(unsigned long j);
 void rcu_force_quiescent_state(void);
 extern struct workqueue_struct *rcu_gp_wq;
 extern struct workqueue_struct *rcu_par_gp_wq;
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index c4fd61dccedb..f28b88ecb47a 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -56,6 +56,7 @@
 #include <linux/vmalloc.h>
 #include <linux/sched/debug.h>
 #include <linux/sched/sysctl.h>
+#include <linux/oom.h>
 
 #include "rcu.h"
 
@@ -1624,6 +1625,7 @@ static struct rcu_fwd_cb *rcu_fwd_cb_head;
 static struct rcu_fwd_cb **rcu_fwd_cb_tail = &rcu_fwd_cb_head;
 static long n_launders_cb;
 static unsigned long rcu_fwd_startat;
+static bool rcu_fwd_emergency_stop;
 #define MAX_FWD_CB_JIFFIES     (8 * HZ) /* Maximum CB test duration. */
 #define MIN_FWD_CB_LAUNDERS    3       /* This many CB invocations to count. */
 #define MIN_FWD_CBS_LAUNDERED  100     /* Number of counted CBs. */
@@ -1681,7 +1683,8 @@ static void rcu_torture_fwd_prog_nr(int *tested, int 
*tested_tries)
        dur = sd4 + torture_random(&trs) % (sd - sd4);
        WRITE_ONCE(rcu_fwd_startat, jiffies);
        stopat = rcu_fwd_startat + dur;
-       while (time_before(jiffies, stopat) && !torture_must_stop()) {
+       while (time_before(jiffies, stopat) &&
+              !READ_ONCE(rcu_fwd_emergency_stop) && !torture_must_stop()) {
                idx = cur_ops->readlock();
                udelay(10);
                cur_ops->readunlock(idx);
@@ -1689,7 +1692,8 @@ static void rcu_torture_fwd_prog_nr(int *tested, int 
*tested_tries)
                        cond_resched();
        }
        (*tested_tries)++;
-       if (!time_before(jiffies, stopat) && !torture_must_stop()) {
+       if (!time_before(jiffies, stopat) &&
+           !READ_ONCE(rcu_fwd_emergency_stop) && !torture_must_stop()) {
                (*tested)++;
                cver = READ_ONCE(rcu_torture_current_version) - cver;
                gps = rcutorture_seq_diff(cur_ops->get_gp_seq(), gps);
@@ -1739,7 +1743,8 @@ static void rcu_torture_fwd_prog_cr(void)
                n_launders_hist[i] = 0;
        cver = READ_ONCE(rcu_torture_current_version);
        gps = cur_ops->get_gp_seq();
-       while (time_before(jiffies, stopat) && !torture_must_stop()) {
+       while (time_before(jiffies, stopat) &&
+              !READ_ONCE(rcu_fwd_emergency_stop) && !torture_must_stop()) {
                rfcp = READ_ONCE(rcu_fwd_cb_head);
                rfcpn = NULL;
                if (rfcp)
@@ -1796,6 +1801,23 @@ static void rcu_torture_fwd_prog_cr(void)
        }
 }
 
+
+/*
+ * OOM notifier, but this only prints diagnostic information for the
+ * current forward-progress test.
+ */
+static int rcutorture_oom_notify(struct notifier_block *self,
+                                unsigned long notused, void *nfreed)
+{
+       rcu_fwd_progress_check(1 + (jiffies - READ_ONCE(rcu_fwd_startat) / 2));
+       WRITE_ONCE(rcu_fwd_emergency_stop, true);
+       return NOTIFY_OK;
+}
+
+static struct notifier_block rcutorture_oom_nb = {
+       .notifier_call = rcutorture_oom_notify
+};
+
 /* Carry out grace-period forward-progress testing. */
 static int rcu_torture_fwd_prog(void *args)
 {
@@ -1808,8 +1830,11 @@ static int rcu_torture_fwd_prog(void *args)
                set_user_nice(current, MAX_NICE);
        do {
                schedule_timeout_interruptible(fwd_progress_holdoff * HZ);
+               WRITE_ONCE(rcu_fwd_emergency_stop, false);
+               register_oom_notifier(&rcutorture_oom_nb);
                rcu_torture_fwd_prog_nr(&tested, &tested_tries);
                rcu_torture_fwd_prog_cr();
+               unregister_oom_notifier(&rcutorture_oom_nb);
 
                /* Avoid slow periods, better to test when busy. */
                stutter_wait("rcu_torture_fwd_prog");
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
index 121f833acd04..6f04352011d7 100644
--- a/kernel/rcu/tree.c
+++ b/kernel/rcu/tree.c
@@ -2654,6 +2654,26 @@ rcu_check_gp_start_stall(struct rcu_node *rnp, struct 
rcu_data *rdp)
        raw_spin_unlock_irqrestore_rcu_node(rnp, flags);
 }
 
+/*
+ * Do a forward-progress check for rcutorture.  This is normally invoked
+ * due to an OOM event.  The argument "j" gives the time period during
+ * which rcutorture would like progress to have been made.
+ */
+void rcu_fwd_progress_check(unsigned long j)
+{
+       struct rcu_data *rdp;
+
+       if (rcu_gp_in_progress()) {
+               show_rcu_gp_kthreads();
+       } else {
+               preempt_disable();
+               rdp = this_cpu_ptr(&rcu_data);
+               rcu_check_gp_start_stall(rdp->mynode, rdp, j);
+               preempt_enable();
+       }
+}
+EXPORT_SYMBOL_GPL(rcu_fwd_progress_check);
+
 /*
  * This does the RCU core processing work for the specified rcu_data
  * structures.  This may be called only from the CPU to whom the rdp
-- 
2.17.1

Reply via email to