Messages in this thread Patch in this message | | | From | "Jason A. Donenfeld" <> | Subject | [PATCH v2] signal: break out of wait loops on kthread_stop() | Date | Mon, 27 Jun 2022 16:57:16 +0200 |
| |
I was recently surprised to learn that msleep_interruptible(), wait_for_completion_interruptible_timeout(), and related functions simply hung when I called kthread_stop() on kthreads using them. The solution to fixing the case with msleep_interruptible() was more simply to move to schedule_timeout_interruptible(). Why?
The reason is that msleep_interruptible(), and many functions just like it, has a loop like this:
while (timeout && !signal_pending(current)) timeout = schedule_timeout_interruptible(timeout);
The call to kthread_stop() woke up the thread, so schedule_timeout_ interruptible() returned early, but because signal_pending() returned true, it went back into another timeout, which was never woken up.
This wait loop pattern is common to various pieces of code, and I suspect that subtle misuse in a kthread that caused a deadlock in the code I looked at last week is also found elsewhere.
So this commit causes signal_pending() to return true when kthread_stop() is called. This is already what's done for TIF_NOTIFY_SIGNAL, for these same purposes of breaking out of wait loops, so a similar KTHREAD_SHOULD_STOP check isn't too much different.
Cc: Ingo Molnar <mingo@redhat.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Eric W. Biederman <ebiederm@xmission.com> Cc: Toke Høiland-Jørgensen <toke@redhat.com> Cc: Kalle Valo <kvalo@kernel.org> Cc: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> --- include/linux/kthread.h | 1 + include/linux/sched/signal.h | 9 +++++++++ kernel/kthread.c | 8 ++++++++ 3 files changed, 18 insertions(+)
diff --git a/include/linux/kthread.h b/include/linux/kthread.h index 30e5bec81d2b..7061dde23237 100644 --- a/include/linux/kthread.h +++ b/include/linux/kthread.h @@ -87,6 +87,7 @@ 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); bool kthread_should_stop(void); +bool __kthread_should_stop(struct task_struct *k); bool kthread_should_park(void); bool __kthread_should_park(struct task_struct *k); bool kthread_freezable_should_stop(bool *was_frozen); diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h index cafbe03eed01..08700c65b806 100644 --- a/include/linux/sched/signal.h +++ b/include/linux/sched/signal.h @@ -11,6 +11,7 @@ #include <linux/refcount.h> #include <linux/posix-timers.h> #include <linux/mm_types.h> +#include <linux/kthread.h> #include <asm/ptrace.h> /* @@ -397,6 +398,14 @@ static inline int signal_pending(struct task_struct *p) */ if (unlikely(test_tsk_thread_flag(p, TIF_NOTIFY_SIGNAL))) return 1; + + /* + * Likewise, KTHREAD_SHOULD_STOP isn't really a signal, but it also + * requires the same behavior, lest wait loops go forever. + */ + if (unlikely(__kthread_should_stop(p))) + return 1; + return task_sigpending(p); } diff --git a/kernel/kthread.c b/kernel/kthread.c index 3c677918d8f2..80f6ba323060 100644 --- a/kernel/kthread.c +++ b/kernel/kthread.c @@ -145,6 +145,14 @@ void free_kthread_struct(struct task_struct *k) kfree(kthread); } +bool __kthread_should_stop(struct task_struct *k) +{ + struct kthread *kthread = __to_kthread(k); + + return kthread && test_bit(KTHREAD_SHOULD_STOP, &kthread->flags); +} +EXPORT_SYMBOL_GPL(__kthread_should_stop); + /** * kthread_should_stop - should this kthread return now? * -- 2.35.1
| |