Re: [PATCH 3/4] workqueue: add schedule_on_each_cpumask helper

From: Marcelo Tosatti
Date: Fri Jun 02 2023 - 13:55:11 EST


On Fri, Jun 02, 2023 at 12:48:23PM +0200, Michal Hocko wrote:
> You should be CCing WQ maintainers on changes like this one (now added).
>
> On Tue 30-05-23 11:52:37, Marcelo Tosatti wrote:
> > Add a schedule_on_each_cpumask function, equivalent to
> > schedule_on_each_cpu but accepting a cpumask to operate.
>
> IMHO it is preferable to add a new function along with its user so that
> the usecase is more clear.
>
> > Signed-off-by: Marcelo Tosatti <mtosatti@xxxxxxxxxx>
> >
> > ---
> >
> > Index: linux-vmstat-remote/kernel/workqueue.c
> > ===================================================================
> > --- linux-vmstat-remote.orig/kernel/workqueue.c
> > +++ linux-vmstat-remote/kernel/workqueue.c
> > @@ -3455,6 +3455,56 @@ int schedule_on_each_cpu(work_func_t fun
> > return 0;
> > }
> >
> > +
> > +/**
> > + * schedule_on_each_cpumask - execute a function synchronously on each
> > + * CPU in "cpumask", for those which are online.
> > + *
> > + * @func: the function to call
> > + * @mask: the CPUs which to call function on
> > + *
> > + * schedule_on_each_cpu() executes @func on each specified CPU that is online,
> > + * using the system workqueue and blocks until all such CPUs have completed.
> > + * schedule_on_each_cpu() is very slow.
> > + *
> > + * Return:
> > + * 0 on success, -errno on failure.
> > + */
> > +int schedule_on_each_cpumask(work_func_t func, cpumask_t *cpumask)
> > +{
> > + int cpu;
> > + struct work_struct __percpu *works;
> > + cpumask_var_t effmask;
> > +
> > + works = alloc_percpu(struct work_struct);
> > + if (!works)
> > + return -ENOMEM;
> > +
> > + if (!alloc_cpumask_var(&effmask, GFP_KERNEL)) {
> > + free_percpu(works);
> > + return -ENOMEM;
> > + }
> > +
> > + cpumask_and(effmask, cpumask, cpu_online_mask);
> > +
> > + cpus_read_lock();
> > +
> > + for_each_cpu(cpu, effmask) {
>
> Is the cpu_online_mask dance really necessary?

> Why cannot you simply do for_each_online_cpu here?

Are you suggesting to do:

for_each_online_cpu(cpu) {
if cpu is not in cpumask
continue;
...
}

This does not seem efficient.

> flush_work on unqueued work item should just
> return, no?

Apparently not:

commit 0e8d6a9336b487a1dd6f1991ff376e669d4c87c6
Author: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
Date: Wed Apr 12 22:07:28 2017 +0200

workqueue: Provide work_on_cpu_safe()

work_on_cpu() is not protected against CPU hotplug. For code which requires
to be either executed on an online CPU or to fail if the CPU is not
available the callsite would have to protect against CPU hotplug.

Provide a function which does get/put_online_cpus() around the call to
work_on_cpu() and fails the call with -ENODEV if the target CPU is not
online.

> Also there is no synchronization with the cpu hotplug so cpu_online_mask
> can change under your feet so this construct seem unsafe to me.

Yes, fixed by patch in response to Andrew's comment.