Re: [PATCH] rcuscale: Consolidate per-writer arrays into struct writer_state
From: Bradley Morgan
Date: Mon Oct 05 2026 - 05:08:34 EST
On 5 October 2026 01:56:41 BST, Joel Fernandes <joelagnelf@xxxxxxxxxx>
wrote:
>The writer kthreads keep their state in five parallel arrays indexed by
>writer number: writer_tasks, writer_durations, writer_n_durations,
>writer_done and writer_freelists. Each must be allocated, NULL-checked,
>freed and cleared separately, and the cleanup path needs several
>guards to cope with any one of them being missing.
>
>Collapse them into a single array of struct writer_state, holding the
>task pointer, the duration buffer, the measurement count, the done flag
>and the existing struct writer_freelist.
>
>This reduces initialization to one allocation and one out-of-memory
>check, and cleanup to a single kfree(). As a side effect, this also
>fixes a leak: cleanup only ran its frees under "if (writer_tasks)", so
>if the writer_tasks allocation failed while a later allocation (for
>example writer_durations or writer_n_durations) still succeeded, those
>already-allocated arrays were never freed. With a single allocation,
>that partial-allocation state can no longer occur.
All I've gotta say here, is one huge thank you, this cleanup WASS needed.
Reviewed-by: Bradley Morgan <brads@xxxxxxxxxxxxxx>
Also I ran basic tests:
Tested-by: Bradley Morgan <brads@xxxxxxxxxxxxxx> # Power10
>
>Assisted-by: Codex
>Fixes: 8704baab9bc8 ("rcutorture: Add RCU grace-period performance tests")
>Signed-off-by: Joel Fernandes <joelagnelf@xxxxxxxxxx>
>---
> kernel/rcu/rcuscale.c | 127 ++++++++++++++++++------------------------
> 1 file changed, 55 insertions(+), 72 deletions(-)
>
>diff --git a/kernel/rcu/rcuscale.c b/kernel/rcu/rcuscale.c
>index 1097ec15879c..f0bca5dcc754 100644
>--- a/kernel/rcu/rcuscale.c
>+++ b/kernel/rcu/rcuscale.c
>@@ -115,17 +115,21 @@ struct writer_freelist {
> struct writer_mblock *ws_mblocks;
> };
>
>+struct writer_state {
>+ struct writer_freelist wst_fl;
>+ struct task_struct *wst_task;
>+ u64 *wst_durations;
>+ int wst_n_durations;
>+ bool wst_done;
>+};
>+
> static int nrealreaders;
> static int nrealwriters;
> static int nrealexp;
>-static struct task_struct **writer_tasks;
> static struct task_struct **reader_tasks;
> static struct task_struct **exp_tasks;
>
>-static u64 **writer_durations;
>-static bool *writer_done;
>-static struct writer_freelist *writer_freelists;
>-static int *writer_n_durations;
>+static struct writer_state *writer_states;
> static atomic_t n_rcu_scale_reader_started;
> static atomic_t n_rcu_scale_writer_started;
> static atomic_t n_rcu_scale_writer_finished;
>@@ -504,9 +508,9 @@ static struct writer_mblock *rcu_scale_alloc(long me)
> struct writer_freelist *wflp;
> struct writer_mblock *wmbp;
>
>- if (WARN_ON_ONCE(!writer_freelists))
>+ if (WARN_ON_ONCE(!writer_states))
> return NULL;
>- wflp = &writer_freelists[me];
>+ wflp = &writer_states[me].wst_fl;
> if (llist_empty(&wflp->ws_lhp)) {
> // ->ws_lhp is private to its rcu_scale_writer task.
> wmbp = container_of(llist_del_all(&wflp->ws_lhg), struct writer_mblock, wmb_node);
>@@ -560,8 +564,9 @@ rcu_scale_writer(void *arg)
> u64 t;
> DEFINE_TORTURE_RANDOM(tr);
> u64 *wdp;
>- u64 *wdpp = writer_durations[me];
>- struct writer_freelist *wflp = &writer_freelists[me];
>+ struct writer_state *wsp = &writer_states[me];
>+ u64 *wdpp = wsp->wst_durations;
>+ struct writer_freelist *wflp = &wsp->wst_fl;
> struct writer_mblock *wmbp = NULL;
>
> VERBOSE_SCALEOUT_STRING("rcu_scale_writer task started");
>@@ -631,7 +636,7 @@ rcu_scale_writer(void *arg)
> started = true;
> if (!done && i >= MIN_MEAS && time_after(jiffies, jdone)) {
> done = true;
>- WRITE_ONCE(writer_done[me], true);
>+ WRITE_ONCE(wsp->wst_done, true);
> sched_set_normal(current, 0);
> pr_alert("%s%s rcu_scale_writer %ld has %d measurements\n",
> scale_type, SCALE_FLAG, me, MIN_MEAS);
>@@ -649,7 +654,7 @@ rcu_scale_writer(void *arg)
> cur_ops->get_gp_seq();
> }
> if (shutdown_secs) {
>- writer_tasks[me] = NULL;
>+ wsp->wst_task = NULL;
> smp_mb(); /* Assign before wake. */
> rcu_scale_cleanup();
> kernel_power_off();
>@@ -665,10 +670,10 @@ rcu_scale_writer(void *arg)
>
> if (!atomic_xchg(&dumped, 1)) {
> for (i = 0; i < nrealwriters; i++) {
>- if (writer_done[i])
>+ if (writer_states[i].wst_done)
> continue;
> pr_info("%s: Task %ld flags writer %d:\n", __func__, me, i);
>- sched_show_task(writer_tasks[i]);
>+ sched_show_task(writer_states[i].wst_task);
> }
> if (cur_ops->stats)
> cur_ops->stats();
>@@ -676,7 +681,7 @@ rcu_scale_writer(void *arg)
> }
> if (!selfreport && time_after(jiffies, jdone + HZ * (70 + me))) {
> pr_info("%s: Writer %ld self-report: started %d done %d/%d->%d i %d jdone %lu.\n",
>- __func__, me, started, done, writer_done[me], atomic_read(&n_rcu_scale_writer_finished), i, jiffies - jdone);
>+ __func__, me, started, done, wsp->wst_done, atomic_read(&n_rcu_scale_writer_finished), i, jiffies - jdone);
> selfreport = true;
> }
> if (gp_succeeded && started && !alldone && i < MAX_MEAS - 1)
>@@ -687,7 +692,7 @@ rcu_scale_writer(void *arg)
> rcu_scale_free(wmbp);
> cur_ops->gp_barrier();
> }
>- writer_n_durations[me] = i_max + 1;
>+ wsp->wst_n_durations = i_max + 1;
> torture_kthread_stopping("rcu_scale_writer");
> return 0;
> }
>@@ -1031,13 +1036,11 @@ rcu_scale_cleanup(void)
> reader_tasks = NULL;
> }
>
>- if (writer_tasks) {
>+ if (writer_states) {
> for (i = 0; i < nrealwriters; i++) {
> torture_stop_kthread(rcu_scale_writer,
>- writer_tasks[i]);
>- if (!writer_n_durations)
>- continue;
>- j = writer_n_durations[i];
>+ writer_states[i].wst_task);
>+ j = writer_states[i].wst_n_durations;
> pr_alert("%s%s writer %d gps: %d\n",
> scale_type, SCALE_FLAG, i, j);
> ngps += j;
>@@ -1051,14 +1054,12 @@ rcu_scale_cleanup(void)
> rcuscale_seq_diff(b_rcu_gp_test_finished,
> b_rcu_gp_test_started));
> for (i = 0; i < nrealwriters; i++) {
>- if (!writer_durations)
>- break;
>- if (!writer_n_durations)
>- continue;
>- wdpp = writer_durations[i];
>+ struct writer_state *wsp = &writer_states[i];
>+
>+ wdpp = wsp->wst_durations;
> if (!wdpp)
> continue;
>- for (j = 0; j < writer_n_durations[i]; j++) {
>+ for (j = 0; j < wsp->wst_n_durations; j++) {
> wdp = &wdpp[j];
> pr_alert("%s%s %4d writer-duration: %5d %llu\n",
> scale_type, SCALE_FLAG,
>@@ -1066,34 +1067,23 @@ rcu_scale_cleanup(void)
> if (j % 100 == 0)
> schedule_timeout_uninterruptible(1);
> }
>- kfree(writer_durations[i]);
>- if (writer_freelists) {
>+ kfree(wdpp);
>+ if (wsp->wst_fl.ws_mblocks) {
> int ctr = 0;
> struct llist_node *llnp;
>- struct writer_freelist *wflp = &writer_freelists[i];
>
>- if (wflp->ws_mblocks) {
>- llist_for_each(llnp, wflp->ws_lhg.first)
>- ctr++;
>- llist_for_each(llnp, wflp->ws_lhp.first)
>- ctr++;
>- WARN_ONCE(ctr != gp_async_max,
>- "%s: ctr = %d gp_async_max = %d\n",
>- __func__, ctr, gp_async_max);
>- kfree(wflp->ws_mblocks);
>- }
>+ llist_for_each(llnp, wsp->wst_fl.ws_lhg.first)
>+ ctr++;
>+ llist_for_each(llnp, wsp->wst_fl.ws_lhp.first)
>+ ctr++;
>+ WARN_ONCE(ctr != gp_async_max,
>+ "%s: ctr = %d gp_async_max = %d\n",
>+ __func__, ctr, gp_async_max);
>+ kfree(wsp->wst_fl.ws_mblocks);
> }
> }
>- kfree(writer_tasks);
>- writer_tasks = NULL;
>- kfree(writer_durations);
>- writer_durations = NULL;
>- kfree(writer_n_durations);
>- writer_n_durations = NULL;
>- kfree(writer_done);
>- writer_done = NULL;
>- kfree(writer_freelists);
>- writer_freelists = NULL;
>+ kfree(writer_states);
>+ writer_states = NULL;
> }
>
> /* Do torture-type-specific cleanup operations. */
>@@ -1187,37 +1177,30 @@ rcu_scale_init(void)
> goto unwind;
> }
> }
>- writer_tasks = kzalloc_objs(writer_tasks[0], nrealwriters);
>- writer_durations = kcalloc(nrealwriters, sizeof(*writer_durations), GFP_KERNEL);
>- writer_n_durations = kzalloc_objs(*writer_n_durations, nrealwriters);
>- writer_done = kzalloc_objs(writer_done[0], nrealwriters);
>- if (gp_async) {
>- if (gp_async_max <= 0) {
>- pr_warn("%s: gp_async_max = %d must be greater than zero.\n",
>- __func__, gp_async_max);
>- WARN_ON_ONCE(IS_BUILTIN(CONFIG_RCU_TORTURE_TEST));
>- firsterr = -EINVAL;
>- goto unwind;
>- }
>- writer_freelists = kzalloc_objs(writer_freelists[0],
>- nrealwriters);
>+ writer_states = kzalloc_objs(writer_states[0], nrealwriters);
>+ if (gp_async && gp_async_max <= 0) {
>+ pr_warn("%s: gp_async_max = %d must be greater than zero.\n",
>+ __func__, gp_async_max);
>+ WARN_ON_ONCE(IS_BUILTIN(CONFIG_RCU_TORTURE_TEST));
>+ firsterr = -EINVAL;
>+ goto unwind;
> }
>- if (!writer_tasks || !writer_durations || !writer_n_durations || !writer_done ||
>- (gp_async && !writer_freelists)) {
>+ if (!writer_states) {
> SCALEOUT_ERRSTRING("out of memory");
> firsterr = -ENOMEM;
> goto unwind;
> }
> for (i = 0; i < nrealwriters; i++) {
>- writer_durations[i] =
>- kcalloc(MAX_MEAS, sizeof(*writer_durations[i]),
>- GFP_KERNEL);
>- if (!writer_durations[i]) {
>+ struct writer_state *wsp = &writer_states[i];
>+
>+ wsp->wst_durations = kcalloc(MAX_MEAS, sizeof(*wsp->wst_durations),
>+ GFP_KERNEL);
>+ if (!wsp->wst_durations) {
> firsterr = -ENOMEM;
> goto unwind;
> }
>- if (writer_freelists) {
>- struct writer_freelist *wflp = &writer_freelists[i];
>+ if (gp_async) {
>+ struct writer_freelist *wflp = &wsp->wst_fl;
>
> init_llist_head(&wflp->ws_lhg);
> init_llist_head(&wflp->ws_lhp);
>@@ -1235,7 +1218,7 @@ rcu_scale_init(void)
> }
> }
> firsterr = torture_create_kthread(rcu_scale_writer, (void *)i,
>- writer_tasks[i]);
>+ wsp->wst_task);
> if (torture_init_error(firsterr))
> goto unwind;
> }
>
--- Thanks!
"I'm not a very positive person" - Linus torvalds