rcu: Add full-sized polling for cond_sync_full()
authorPaul E. McKenney <paulmck@kernel.org>
Thu, 4 Aug 2022 20:46:05 +0000 (13:46 -0700)
committerPaul E. McKenney <paulmck@kernel.org>
Wed, 31 Aug 2022 12:08:08 +0000 (05:08 -0700)
The cond_synchronize_rcu() API compresses the combined expedited and
normal grace-period states into a single unsigned long, which conserves
storage, but can miss grace periods in certain cases involving overlapping
normal and expedited grace periods.  Missing the occasional grace period
is usually not a problem, but there are use cases that care about each
and every grace period.

This commit therefore adds yet another member of the full-state RCU
grace-period polling API, which is the cond_synchronize_rcu_full()
function.  This uses up to three times the storage (rcu_gp_oldstate
structure instead of unsigned long), but is guaranteed not to miss
grace periods.

[ paulmck: Apply feedback from kernel test robot and Julia Lawall. ]

Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
include/linux/rcutiny.h
include/linux/rcutree.h
kernel/rcu/rcutorture.c
kernel/rcu/tree.c

index 653e357..3bee97f 100644 (file)
@@ -44,6 +44,11 @@ static inline void cond_synchronize_rcu(unsigned long oldstate)
        might_sleep();
 }
 
+static inline void cond_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp)
+{
+       cond_synchronize_rcu(rgosp->rgos_norm);
+}
+
 static inline unsigned long start_poll_synchronize_rcu_expedited(void)
 {
        return start_poll_synchronize_rcu();
index 7151fd8..1b44288 100644 (file)
@@ -57,6 +57,7 @@ void start_poll_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp);
 bool poll_state_synchronize_rcu(unsigned long oldstate);
 bool poll_state_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp);
 void cond_synchronize_rcu(unsigned long oldstate);
+void cond_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp);
 
 bool rcu_is_idle_cpu(int cpu);
 
index f9ca335..9d22161 100644 (file)
@@ -84,6 +84,7 @@ torture_param(int, fwd_progress_holdoff, 60, "Time between forward-progress test
 torture_param(bool, fwd_progress_need_resched, 1, "Hide cond_resched() behind need_resched()");
 torture_param(bool, gp_cond, false, "Use conditional/async GP wait primitives");
 torture_param(bool, gp_cond_exp, false, "Use conditional/async expedited GP wait primitives");
+torture_param(bool, gp_cond_full, false, "Use conditional/async full-state GP wait primitives");
 torture_param(bool, gp_exp, false, "Use expedited GP wait primitives");
 torture_param(bool, gp_normal, false, "Use normal (non-expedited) GP wait primitives");
 torture_param(bool, gp_poll, false, "Use polling GP wait primitives");
@@ -196,20 +197,22 @@ static int rcu_torture_writer_state;
 #define RTWS_DEF_FREE          3
 #define RTWS_EXP_SYNC          4
 #define RTWS_COND_GET          5
-#define RTWS_COND_GET_EXP      6
-#define RTWS_COND_SYNC         7
-#define RTWS_COND_SYNC_EXP     8
-#define RTWS_POLL_GET          9
-#define RTWS_POLL_GET_FULL     10
-#define RTWS_POLL_GET_EXP      11
-#define RTWS_POLL_GET_EXP_FULL 12
-#define RTWS_POLL_WAIT         13
-#define RTWS_POLL_WAIT_FULL    14
-#define RTWS_POLL_WAIT_EXP     15
-#define RTWS_POLL_WAIT_EXP_FULL        16
-#define RTWS_SYNC              17
-#define RTWS_STUTTER           18
-#define RTWS_STOPPING          19
+#define RTWS_COND_GET_FULL     6
+#define RTWS_COND_GET_EXP      7
+#define RTWS_COND_SYNC         8
+#define RTWS_COND_SYNC_FULL    9
+#define RTWS_COND_SYNC_EXP     10
+#define RTWS_POLL_GET          11
+#define RTWS_POLL_GET_FULL     12
+#define RTWS_POLL_GET_EXP      13
+#define RTWS_POLL_GET_EXP_FULL 14
+#define RTWS_POLL_WAIT         15
+#define RTWS_POLL_WAIT_FULL    16
+#define RTWS_POLL_WAIT_EXP     17
+#define RTWS_POLL_WAIT_EXP_FULL        18
+#define RTWS_SYNC              19
+#define RTWS_STUTTER           20
+#define RTWS_STOPPING          21
 static const char * const rcu_torture_writer_state_names[] = {
        "RTWS_FIXED_DELAY",
        "RTWS_DELAY",
@@ -217,8 +220,10 @@ static const char * const rcu_torture_writer_state_names[] = {
        "RTWS_DEF_FREE",
        "RTWS_EXP_SYNC",
        "RTWS_COND_GET",
+       "RTWS_COND_GET_FULL",
        "RTWS_COND_GET_EXP",
        "RTWS_COND_SYNC",
+       "RTWS_COND_SYNC_FULL",
        "RTWS_COND_SYNC_EXP",
        "RTWS_POLL_GET",
        "RTWS_POLL_GET_FULL",
@@ -355,6 +360,7 @@ struct rcu_torture_ops {
        bool (*poll_gp_state_full)(struct rcu_gp_oldstate *rgosp);
        bool (*poll_need_2gp)(bool poll, bool poll_full);
        void (*cond_sync)(unsigned long oldstate);
+       void (*cond_sync_full)(struct rcu_gp_oldstate *rgosp);
        call_rcu_func_t call;
        void (*cb_barrier)(void);
        void (*fqs)(void);
@@ -532,6 +538,7 @@ static struct rcu_torture_ops rcu_ops = {
        .poll_gp_state_full     = poll_state_synchronize_rcu_full,
        .poll_need_2gp          = rcu_poll_need_2gp,
        .cond_sync              = cond_synchronize_rcu,
+       .cond_sync_full         = cond_synchronize_rcu_full,
        .get_gp_state_exp       = get_state_synchronize_rcu,
        .start_gp_poll_exp      = start_poll_synchronize_rcu_expedited,
        .start_gp_poll_exp_full = start_poll_synchronize_rcu_expedited_full,
@@ -1175,16 +1182,17 @@ static int nsynctypes;
  */
 static void rcu_torture_write_types(void)
 {
-       bool gp_cond1 = gp_cond, gp_cond_exp1 = gp_cond_exp, gp_exp1 = gp_exp;
-       bool gp_poll_exp1 = gp_poll_exp, gp_poll_exp_full1 = gp_poll_exp_full;
+       bool gp_cond1 = gp_cond, gp_cond_exp1 = gp_cond_exp, gp_cond_full1 = gp_cond_full;
+       bool gp_exp1 = gp_exp, gp_poll_exp1 = gp_poll_exp, gp_poll_exp_full1 = gp_poll_exp_full;
        bool gp_normal1 = gp_normal, gp_poll1 = gp_poll, gp_poll_full1 = gp_poll_full;
        bool gp_sync1 = gp_sync;
 
        /* Initialize synctype[] array.  If none set, take default. */
-       if (!gp_cond1 && !gp_cond_exp1 && !gp_exp1 && !gp_poll_exp && !gp_poll_exp_full1 &&
-           !gp_normal1 && !gp_poll1 && !gp_poll_full1 && !gp_sync1)
-               gp_cond1 = gp_cond_exp1 = gp_exp1 = gp_poll_exp1 = gp_poll_exp_full1 =
-                          gp_normal1 = gp_poll1 = gp_poll_full1 = gp_sync1 = true;
+       if (!gp_cond1 && !gp_cond_exp1 && !gp_cond_full1 && !gp_exp1 && !gp_poll_exp &&
+           !gp_poll_exp_full1 && !gp_normal1 && !gp_poll1 && !gp_poll_full1 && !gp_sync1)
+               gp_cond1 = gp_cond_exp1 = gp_cond_full1 = gp_exp1 = gp_poll_exp1 =
+                          gp_poll_exp_full1 = gp_normal1 = gp_poll1 = gp_poll_full1 =
+                          gp_sync1 = true;
        if (gp_cond1 && cur_ops->get_gp_state && cur_ops->cond_sync) {
                synctype[nsynctypes++] = RTWS_COND_GET;
                pr_info("%s: Testing conditional GPs.\n", __func__);
@@ -1197,6 +1205,12 @@ static void rcu_torture_write_types(void)
        } else if (gp_cond_exp && (!cur_ops->get_gp_state_exp || !cur_ops->cond_sync_exp)) {
                pr_alert("%s: gp_cond_exp without primitives.\n", __func__);
        }
+       if (gp_cond_full1 && cur_ops->get_gp_state && cur_ops->cond_sync_full) {
+               synctype[nsynctypes++] = RTWS_COND_GET_FULL;
+               pr_info("%s: Testing conditional full-state GPs.\n", __func__);
+       } else if (gp_cond_full && (!cur_ops->get_gp_state || !cur_ops->cond_sync_full)) {
+               pr_alert("%s: gp_cond_full without primitives.\n", __func__);
+       }
        if (gp_exp1 && cur_ops->exp_sync) {
                synctype[nsynctypes++] = RTWS_EXP_SYNC;
                pr_info("%s: Testing expedited GPs.\n", __func__);
@@ -1396,6 +1410,14 @@ rcu_torture_writer(void *arg)
                                cur_ops->cond_sync_exp(gp_snap);
                                rcu_torture_pipe_update(old_rp);
                                break;
+                       case RTWS_COND_GET_FULL:
+                               rcu_torture_writer_state = RTWS_COND_GET_FULL;
+                               cur_ops->get_gp_state_full(&gp_snap_full);
+                               torture_hrtimeout_jiffies(torture_random(&rand) % 16, &rand);
+                               rcu_torture_writer_state = RTWS_COND_SYNC_FULL;
+                               cur_ops->cond_sync_full(&gp_snap_full);
+                               rcu_torture_pipe_update(old_rp);
+                               break;
                        case RTWS_POLL_GET:
                                rcu_torture_writer_state = RTWS_POLL_GET;
                                gp_snap = cur_ops->start_gp_poll();
@@ -1540,6 +1562,11 @@ rcu_torture_fakewriter(void *arg)
                                torture_hrtimeout_jiffies(torture_random(&rand) % 16, &rand);
                                cur_ops->cond_sync_exp(gp_snap);
                                break;
+                       case RTWS_COND_GET_FULL:
+                               cur_ops->get_gp_state_full(&gp_snap_full);
+                               torture_hrtimeout_jiffies(torture_random(&rand) % 16, &rand);
+                               cur_ops->cond_sync_full(&gp_snap_full);
+                               break;
                        case RTWS_POLL_GET:
                                gp_snap = cur_ops->start_gp_poll();
                                while (!cur_ops->poll_gp_state(gp_snap)) {
index 0a24ef4..5c46c0d 100644 (file)
@@ -3749,7 +3749,6 @@ EXPORT_SYMBOL_GPL(poll_state_synchronize_rcu_full);
 
 /**
  * cond_synchronize_rcu - Conditionally wait for an RCU grace period
- *
  * @oldstate: value from get_state_synchronize_rcu(), start_poll_synchronize_rcu(), or start_poll_synchronize_rcu_expedited()
  *
  * If a full RCU grace period has elapsed since the earlier call to
@@ -3773,6 +3772,33 @@ void cond_synchronize_rcu(unsigned long oldstate)
 }
 EXPORT_SYMBOL_GPL(cond_synchronize_rcu);
 
+/**
+ * cond_synchronize_rcu_full - Conditionally wait for an RCU grace period
+ * @rgosp: value from get_state_synchronize_rcu_full(), start_poll_synchronize_rcu_full(), or start_poll_synchronize_rcu_expedited_full()
+ *
+ * If a full RCU grace period has elapsed since the call to
+ * get_state_synchronize_rcu_full(), start_poll_synchronize_rcu_full(),
+ * or start_poll_synchronize_rcu_expedited_full() from which @rgosp was
+ * obtained, just return.  Otherwise, invoke synchronize_rcu() to wait
+ * for a full grace period.
+ *
+ * Yes, this function does not take counter wrap into account.
+ * But counter wrap is harmless.  If the counter wraps, we have waited for
+ * more than 2 billion grace periods (and way more on a 64-bit system!),
+ * so waiting for a couple of additional grace periods should be just fine.
+ *
+ * This function provides the same memory-ordering guarantees that
+ * would be provided by a synchronize_rcu() that was invoked at the call
+ * to the function that provided @rgosp and that returned at the end of
+ * this function.
+ */
+void cond_synchronize_rcu_full(struct rcu_gp_oldstate *rgosp)
+{
+       if (!poll_state_synchronize_rcu_full(rgosp))
+               synchronize_rcu();
+}
+EXPORT_SYMBOL_GPL(cond_synchronize_rcu_full);
+
 /*
  * Check to see if there is any immediate RCU-related work to be done by
  * the current CPU, returning 1 if so and zero otherwise.  The checks are