Re: signal: break out of wait loops on kthread_stop()

From: Sultan Alsawaf
Date: Wed Oct 19 2022 - 15:05:49 EST


On Wed, Oct 19, 2022 at 12:16:22PM -0600, Jason A. Donenfeld wrote:
> On Wed, Oct 19, 2022 at 06:57:38PM +0100, Tvrtko Ursulin wrote:
> >
> > On 19/10/2022 17:00, Jason A. Donenfeld wrote:
> > > On Wed, Oct 19, 2022 at 7:31 AM Tvrtko Ursulin
> > > <tvrtko.ursulin@xxxxxxxxxxxxxxx> wrote:
> > >>
> > >>
> > >> Hi,
> > >>
> > >> A question regarding a7c01fa93aeb ("signal: break out of wait loops on
> > >> kthread_stop()") if I may.
> > >>
> > >> We have a bunch code in i915, possibly limited to self tests (ie debug
> > >> builds) but still important for our flows, which spawn kernel threads
> > >> and exercises parts of the driver.
> > >>
> > >> Problem we are hitting with this patch is that code did not really need
> > >> to be signal aware until now. Well to say that more accurately - we were
> > >> able to test the code which is normally executed from userspace, so is
> > >> signal aware, but not worry about -ERESTARTSYS or -EINTR within the test
> > >> cases itself.
> > >>
> > >> For example threads which exercise an internal API for a while until the
> > >> parent calls kthread_stop. Now those tests can hit unexpected errors.
> > >>
> > >> Question is how to best approach working around this change. It is of
> > >> course technically possible to rework our code in more than one way,
> > >> although with some cost and impact already felt due reduced pass rates
> > >> in our automated test suites.
> > >>
> > >> Maybe an opt out kthread flag from this new behavior? Would that be
> > >> acceptable as a quick fix? Or any other comments?
> > >
> > > You can opt out by running `clear_tsk_thread_flag(current,
> > > TIF_NOTIFY_SIGNAL);` at the top of your kthread. But you should really
> > > fix your code instead. Were I your reviewer, I wouldn't merge code
> > > that took the lazy path like that. However, that should work, if you
> > > do opt for the quick fix.
> >
> > Also, are you confident that the change will not catch anyone else by
> > surprise? In the original thread I did not spot any concerns about the
> > kthreads being generally unprepared to start receiving EINTR/ERESTARTSYS
> > from random call chains.
>
> Pretty sure, yea. i915 is unique in its abuse of the API. Keep in mind
> that kthread_stop() also sets KTHREAD_SHOULD_STOP and such. Your code is
> abusing the API by calling kthread_run() followed by kthread_stop().
>
> As evidence of how broken your code actually is, the kthread_stop()
> function has a comment that makes it clear, "This can also be called
> after kthread_create() instead of calling wake_up_process(): the thread
> will exit without calling threadfn()," yet i915 has attempted to hack
> around it with ridiculous yields and msleeps. For example:
>
> threads[n] = kthread_run(__igt_breadcrumbs_smoketest,
> &t, "igt/%d", n);
> ...
>
> yield(); /* start all threads before we begin */
> msleep(jiffies_to_msecs(i915_selftest.timeout_jiffies));
> ...
> err = kthread_stop(threads[n]);
>
>
> Or here's another one:
>
> tsk = kthread_run(fn, &thread[i], "igt-%d", i);
> ...
> msleep(10); /* start all threads before we kthread_stop() */
> ...
> status = kthread_stop(tsk);
>
> I mean come on.
>
> This is brittle and bad and kind of ridiculous that it shipped this way.
> Now you're asking to extend your brittleness, so that you can avoid the
> work of cleaning up 5 call sites. Just clean up those 5 call sites. It's
> only 5, as far as I can tell.
>
>
> > Right, but our hand is a bit forced at the moment. Since 6.1-rc1 has
> > propagated to our development tree on Monday, our automated testing
> > started failing significantly, which prevents us merging new work until
> > resolved. So a quick fix trumps the ideal road in the short term. Just
> > because it is quick.
>
> "Short term" -- somehow I can imagine the short term hack will turn into
> a long term one.
>
> Anyway, what I suspect you might actually want as a bandaid is a
> "kthread_wait()"-like function, that doesn't try to stop the thread with
> KTHREAD_SHOULD_STOP and such, but just waits for the completion:
>
> diff --git a/include/linux/kthread.h b/include/linux/kthread.h
> index 30e5bec81d2b..2699cc45ad15 100644
> --- a/include/linux/kthread.h
> +++ b/include/linux/kthread.h
> @@ -86,6 +86,7 @@ void free_kthread_struct(struct task_struct *k);
> void kthread_bind(struct task_struct *k, unsigned int cpu);
> void kthread_bind_mask(struct task_struct *k, const struct cpumask *mask);
> int kthread_stop(struct task_struct *k);
> +int kthread_wait(struct task_struct *k);
> bool kthread_should_stop(void);
> bool kthread_should_park(void);
> bool __kthread_should_park(struct task_struct *k);
> diff --git a/kernel/kthread.c b/kernel/kthread.c
> index f97fd01a2932..d581d78a3a26 100644
> --- a/kernel/kthread.c
> +++ b/kernel/kthread.c
> @@ -715,6 +715,22 @@ int kthread_stop(struct task_struct *k)
> }
> EXPORT_SYMBOL(kthread_stop);
>
> +int kthread_wait(struct task_struct *k)
> +{
> + struct kthread *kthread;
> + int ret;
> +
> + get_task_struct(k);
> + kthread = to_kthread(k);
> + wake_up_process(k);
> + wait_for_completion(&kthread->exited);
> + ret = kthread->result;
> + put_task_struct(k);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(kthread_stop);
> +
> int kthreadd(void *unused)
> {
> struct task_struct *tsk = current;
>

Hi,

Given the need to ensure the kthreads are started and then synchronously
flushed, this seems like a good fit for the kthread_work API, which provides all
of the necessary plumbing for this usecase. No need for the eldritch kthread API
abuse and flimsy yield+msleep.

Sultan