Index | Thread | Search

From:
Claudio Jeker <cjeker@diehard.n-r-g.com>
Subject:
Re: fix stop signal delivery
To:
tech@openbsd.org
Date:
Thu, 20 Feb 2025 14:05:38 +0100

Download raw body.

Thread
On Tue, Feb 18, 2025 at 03:52:49PM +0100, Claudio Jeker wrote:
> On Mon, Feb 10, 2025 at 07:49:39AM +0100, Claudio Jeker wrote:
> > On Tue, Feb 04, 2025 at 03:02:06PM +0100, Claudio Jeker wrote:
> > > Long story short, stop signals (SIGSTOP and SIGTTIN, SIGTTOU and SIGTSTP)
> > > are busted for multithreaded processes (e.g. mpv needs workarounds because
> > > of that). 
> > > 
> > > This diff fixes this by totally reworking the way processes are stopped.
> > > Now this diff is too big to go in like this but it would be great if
> > > people could try this out. Especially if you have problems with stop
> > > signals or also when you debug multithreaded processes with egdb.
> >  
> > Updated diff now that the first bits of this have been committed.
> 
> This is the latest version of this diff.

New version that fixes a race in proc_stop().
This has been stable for me even when hammering the system with
signal-stress over and over again.
-- 
:wq Claudio

Index: dev/dt/dt_prov_static.c
===================================================================
RCS file: /cvs/src/sys/dev/dt/dt_prov_static.c,v
diff -u -p -r1.24 dt_prov_static.c
--- dev/dt/dt_prov_static.c	9 Jan 2025 17:43:33 -0000	1.24
+++ dev/dt/dt_prov_static.c	20 Feb 2025 12:33:50 -0000
@@ -44,9 +44,11 @@ DT_STATIC_PROBE2(sched, off__cpu, "pid_t
 DT_STATIC_PROBE0(sched, on__cpu);
 DT_STATIC_PROBE0(sched, remain__cpu);
 DT_STATIC_PROBE0(sched, sleep);
+DT_STATIC_PROBE0(sched, stop);
 DT_STATIC_PROBE3(sched, steal, "pid_t", "pid_t", "int");
 DT_STATIC_PROBE2(sched, unsleep, "pid_t", "pid_t");
 DT_STATIC_PROBE3(sched, wakeup, "pid_t", "pid_t", "int");
+DT_STATIC_PROBE3(sched, unstop, "pid_t", "pid_t", "int");
 
 /*
  * Raw syscalls
@@ -116,9 +118,11 @@ struct dt_probe *const dtps_static[] = {
 	&_DT_STATIC_P(sched, on__cpu),
 	&_DT_STATIC_P(sched, remain__cpu),
 	&_DT_STATIC_P(sched, sleep),
+	&_DT_STATIC_P(sched, stop),
 	&_DT_STATIC_P(sched, steal),
 	&_DT_STATIC_P(sched, unsleep),
 	&_DT_STATIC_P(sched, wakeup),
+	&_DT_STATIC_P(sched, unstop),
 	/* Raw syscalls */
 	&_DT_STATIC_P(raw_syscalls, sys_enter),
 	&_DT_STATIC_P(raw_syscalls, sys_exit),
Index: dev/pci/drm/drm_linux.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/drm/drm_linux.c,v
diff -u -p -r1.120 drm_linux.c
--- dev/pci/drm/drm_linux.c	7 Feb 2025 03:03:08 -0000	1.120
+++ dev/pci/drm/drm_linux.c	20 Feb 2025 12:33:50 -0000
@@ -122,7 +122,7 @@ __set_current_state(int state)
 	SCHED_LOCK();
 	unsleep(p);
 	p->p_stat = SONPROC;
-	atomic_clearbits_int(&p->p_flag, P_WSLEEP);
+	atomic_clearbits_int(&p->p_flag, P_INSCHED);
 	SCHED_UNLOCK();
 }
 
Index: kern/kern_exit.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_exit.c,v
diff -u -p -r1.242 kern_exit.c
--- kern/kern_exit.c	17 Feb 2025 10:16:05 -0000	1.242
+++ kern/kern_exit.c	20 Feb 2025 12:33:52 -0000
@@ -164,13 +164,11 @@ exit1(struct proc *p, int xexit, int xsi
 	pr->ps_exitcnt++;
 
 	/*
-	 * if somebody else wants to take us to single threaded mode,
-	 * count ourselves out.
+	 * if somebody else wants to take us to single threaded mode
+	 * or stop us, count ourselves out.
 	 */
-	if (pr->ps_single) {
-		if (--pr->ps_singlecnt == 0)
-			wakeup(&pr->ps_singlecnt);
-	}
+	if (pr->ps_single != NULL || ISSET(pr->ps_flags, PS_STOPPING))
+		process_suspend_signal(pr);
 
 	/* proc is off ps_threads list so update accounting of process now */
 	tuagg_add_runtime();
@@ -525,7 +523,6 @@ loop:
 		}
 		nfound++;
 		if ((options & WEXITED) && (pr->ps_flags & PS_ZOMBIE)) {
-			mtx_leave(&pr->ps_mtx);
 			*retval = pr->ps_pid;
 			if (info != NULL) {
 				info->si_pid = pr->ps_pid;
@@ -548,17 +545,15 @@ loop:
 				    pr->ps_xsig);
 			if (rusage != NULL)
 				memcpy(rusage, pr->ps_ru, sizeof(*rusage));
+			mtx_leave(&pr->ps_mtx);
 			if ((options & WNOWAIT) == 0)
 				proc_finish_wait(q, pr);
 			return (0);
 		}
 		if ((options & WTRAPPED) && (pr->ps_flags & PS_TRACED) &&
 		    (pr->ps_flags & PS_WAITED) == 0 &&
+		    (pr->ps_flags & PS_STOPPED) &&
 		    (pr->ps_flags & PS_TRAPPED)) {
-			mtx_leave(&pr->ps_mtx);
-			if (single_thread_wait(pr, 0))
-				goto loop;
-
 			if ((options & WNOWAIT) == 0)
 				atomic_setbits_int(&pr->ps_flags, PS_WAITED);
 
@@ -573,6 +568,7 @@ loop:
 
 			if (statusp != NULL)
 				*statusp = W_STOPCODE(pr->ps_xsig);
+			mtx_leave(&pr->ps_mtx);
 			if (rusage != NULL)
 				memset(rusage, 0, sizeof(*rusage));
 			return (0);
@@ -581,7 +577,6 @@ loop:
 		    (pr->ps_flags & PS_WAITED) == 0 &&
 		    (pr->ps_flags & PS_STOPPED) &&
 		    (pr->ps_flags & PS_TRAPPED) == 0) {
-			mtx_leave(&pr->ps_mtx);
 			if ((options & WNOWAIT) == 0)
 				atomic_setbits_int(&pr->ps_flags, PS_WAITED);
 
@@ -596,12 +591,12 @@ loop:
 
 			if (statusp != NULL)
 				*statusp = W_STOPCODE(pr->ps_xsig);
+			mtx_leave(&pr->ps_mtx);
 			if (rusage != NULL)
 				memset(rusage, 0, sizeof(*rusage));
 			return (0);
 		}
 		if ((options & WCONTINUED) && (pr->ps_flags & PS_CONTINUED)) {
-			mtx_leave(&pr->ps_mtx);
 			if ((options & WNOWAIT) == 0)
 				atomic_clearbits_int(&pr->ps_flags,
 				    PS_CONTINUED);
@@ -615,6 +610,7 @@ loop:
 				info->si_status = SIGCONT;
 			}
 
+			mtx_leave(&pr->ps_mtx);
 			if (statusp != NULL)
 				*statusp = _WCONTINUED;
 			if (rusage != NULL)
Index: kern/kern_fork.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_fork.c,v
diff -u -p -r1.268 kern_fork.c
--- kern/kern_fork.c	10 Nov 2024 06:51:59 -0000	1.268
+++ kern/kern_fork.c	20 Feb 2025 12:33:52 -0000
@@ -590,12 +590,13 @@ thread_fork(struct proc *curp, void *sta
 	pr->ps_threadcnt++;
 
 	/*
-	 * if somebody else wants to take us to single threaded mode,
-	 * count ourselves in.
+	 * if somebody else wants to take us to single threaded mode
+	 * or suspend the process, count ourselves in.
 	 */
-	if (pr->ps_single) {
-		pr->ps_singlecnt++;
-		atomic_setbits_int(&p->p_flag, P_SUSPSINGLE);
+	if (pr->ps_single != NULL || ISSET(pr->ps_flags, PS_STOPPING)) {
+		pr->ps_suspendcnt++;
+		atomic_setbits_int(&p->p_flag,
+		    curp->p_flag & (P_SUSPSINGLE | P_SUSPSIG));
 	}
 	mtx_leave(&pr->ps_mtx);
 
Index: kern/kern_proc.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_proc.c,v
diff -u -p -r1.101 kern_proc.c
--- kern/kern_proc.c	22 Oct 2024 11:54:04 -0000	1.101
+++ kern/kern_proc.c	20 Feb 2025 12:33:52 -0000
@@ -501,7 +501,7 @@ proc_printit(struct proc *p, const char 
 	    p->p_runpri, p->p_usrpri, p->p_slppri, p->p_p->ps_nice);
 	(*pr)("    wchan=%p, wmesg=%s, ps_single=%p scnt=%d ecnt=%d\n",
 	    p->p_wchan, (p->p_wchan && p->p_wmesg) ?  p->p_wmesg : "",
-	    p->p_p->ps_single, p->p_p->ps_singlecnt, p->p_p->ps_exitcnt);
+	    p->p_p->ps_single, p->p_p->ps_suspendcnt, p->p_p->ps_exitcnt);
 	(*pr)("    forw=%p, list=%p,%p\n",
 	    TAILQ_NEXT(p, p_runq), p->p_list.le_next, p->p_list.le_prev);
 	(*pr)("    process=%p user=%p, vmspace=%p\n",
Index: kern/kern_sched.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_sched.c,v
diff -u -p -r1.103 kern_sched.c
--- kern/kern_sched.c	24 Nov 2024 13:05:14 -0000	1.103
+++ kern/kern_sched.c	20 Feb 2025 12:33:52 -0000
@@ -277,7 +277,7 @@ setrunqueue(struct cpu_info *ci, struct 
 	KASSERT(ci != NULL);
 	SCHED_ASSERT_LOCKED();
 	KASSERT(p->p_wchan == NULL);
-	KASSERT(!ISSET(p->p_flag, P_WSLEEP));
+	KASSERT(!ISSET(p->p_flag, P_INSCHED));
 
 	p->p_cpu = ci;
 	p->p_stat = SRUN;
@@ -368,7 +368,7 @@ again:
 	} 
 
 	KASSERT(p->p_wchan == NULL);
-	KASSERT(!ISSET(p->p_flag, P_WSLEEP));
+	KASSERT(!ISSET(p->p_flag, P_INSCHED));
 	return (p);
 }
 
Index: kern/kern_sig.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_sig.c,v
diff -u -p -r1.363 kern_sig.c
--- kern/kern_sig.c	17 Feb 2025 15:45:55 -0000	1.363
+++ kern/kern_sig.c	20 Feb 2025 12:33:52 -0000
@@ -61,6 +61,7 @@
 #include <sys/pledge.h>
 #include <sys/witness.h>
 #include <sys/exec_elf.h>
+#include <sys/tracepoint.h>
 
 #include <sys/mount.h>
 #include <sys/syscallargs.h>
@@ -121,12 +122,10 @@ const int sigprop[NSIG] = {
 void setsigvec(struct proc *, int, struct sigaction *);
 
 int proc_trap(struct proc *, int);
-void proc_stop(struct proc *p, int);
-void proc_stop_sweep(void *);
-void *proc_stop_si;
+void proc_stop_setup(struct proc *p);
+void proc_stop_finish(struct proc *p);
 
 void process_continue(struct process *, int);
-void process_stop(struct process *, int, int);
 
 void setsigctx(struct proc *, int, struct sigctx *);
 void postsig_done(struct proc *, int, sigset_t, int);
@@ -134,6 +133,7 @@ void postsig(struct proc *, int, struct 
 int cansignal(struct proc *, struct process *, int);
 
 void ptsignal_locked(struct proc *, int, enum signal_type);
+int proc_suspend_check_locked(struct proc *, int);
 
 struct pool sigacts_pool;	/* memory pool for sigacts structures */
 
@@ -203,11 +203,6 @@ cansignal(struct proc *p, struct process
 void
 signal_init(void)
 {
-	proc_stop_si = softintr_establish(IPL_SOFTCLOCK, proc_stop_sweep,
-	    NULL);
-	if (proc_stop_si == NULL)
-		panic("signal_init failed to register softintr");
-
 	pool_init(&sigacts_pool, sizeof(struct sigacts), 0, IPL_NONE,
 	    PR_WAITOK, "sigapl", NULL);
 }
@@ -917,7 +912,6 @@ prsignal(struct process *pr, int signum)
 /*
  * type = SPROCESS	process signal, can be diverted (sigwait())
  * type = STHREAD	thread signal, but should be propagated if unhandled
- * type = SPROPAGATED	propagated to this thread, so don't propagate again
  */
 void
 ptsignal(struct proc *p, int signum, enum signal_type type)
@@ -1009,8 +1003,7 @@ ptsignal_locked(struct proc *p, int sign
 		}
 	}
 
-	if (type != SPROPAGATED)
-		knote_locked(&pr->ps_klist, NOTE_SIGNAL | signum);
+	knote_locked(&pr->ps_klist, NOTE_SIGNAL | signum);
 
 	prop = sigprop[signum];
 
@@ -1058,20 +1051,11 @@ ptsignal_locked(struct proc *p, int sign
 	}
 	/*
 	 * If delivered to process, mark as pending there.  Continue and stop
-	 * signals will be propagated to all threads.  So they are always
-	 * marked at thread level.
+	 * signals are always marked at process level.
 	 */
 	siglist = (type == SPROCESS) ? &pr->ps_siglist : &p->p_siglist;
 	if (prop & (SA_CONT | SA_STOP))
-		siglist = &p->p_siglist;
-
-	/*
-	 * XXX delay processing of SA_STOP signals unless action == SIG_DFL?
-	 */
-	if (prop & SA_STOP && type != SPROPAGATED)
-		TAILQ_FOREACH(q, &pr->ps_threads, p_thr_link)
-			if (q != p)
-				ptsignal_locked(q, signum, SPROPAGATED);
+		siglist = &pr->ps_siglist;
 
 	SCHED_LOCK();
 
@@ -1222,7 +1206,9 @@ ptsignal_locked(struct proc *p, int sign
 				goto out;
 			mask = 0;
 			pr->ps_xsig = signum;
-			proc_stop(p, 0);
+			atomic_setbits_int(&pr->ps_flags, PS_STOPPING);
+			process_stop(pr, P_SUSPSIG, SINGLE_SUSPEND);
+			wakeparent = 1;
 			goto out;
 		}
 		/*
@@ -1270,8 +1256,13 @@ out:
 
 	SCHED_UNLOCK();
 	if (wakeparent) {
-		atomic_setbits_int(&pr->ps_pptr->ps_flags, PS_WAITEVENT);
-		wakeup(pr->ps_pptr);
+		if (prop & SA_STOP)
+			process_suspend_signal(pr);
+		else {
+			atomic_setbits_int(&pr->ps_pptr->ps_flags,
+			    PS_WAITEVENT);
+			wakeup(pr->ps_pptr);
+		}
 	}
 }
 
@@ -1446,10 +1437,17 @@ cursig(struct proc *p, struct sigctx *sc
 			 * then clear the signal.
 			 */
 			if (sctx->sig_stop) {
+				mtx_enter(&pr->ps_mtx);
 				pr->ps_xsig = signum;
+				atomic_setbits_int(&pr->ps_flags, PS_STOPPING);
 				SCHED_LOCK();
-				proc_stop(p, 1);
+				process_stop(pr, P_SUSPSIG, SINGLE_SUSPEND);
+				atomic_setbits_int(&p->p_flag, P_SUSPSIG);
+				proc_stop_setup(p);
 				SCHED_UNLOCK();
+				process_suspend_signal(pr);
+				proc_stop_finish(p);
+				mtx_leave(&pr->ps_mtx);
 				break;
 			} else if (prop & SA_IGNORE) {
 				/*
@@ -1499,29 +1497,43 @@ proc_trap(struct proc *p, int signum)
 {
 	struct process *pr = p->p_p;
 
-	single_thread_set(p, SINGLE_SUSPEND | SINGLE_NOWAIT);
-
 	mtx_enter(&pr->ps_mtx);
+	/*
+	 * Wait until any other suspend condition cleared,
+	 * including other traps.
+	 */
+	proc_suspend_check_locked(p, 0);
+
+	atomic_setbits_int(&pr->ps_flags, PS_STOPPING | PS_TRAPPED);
+	SCHED_LOCK();
+	process_stop(pr, P_SUSPSIG, SINGLE_SUSPEND);
+	atomic_setbits_int(&p->p_flag, P_SUSPSIG);
+	proc_stop_setup(p);
+	SCHED_UNLOCK();
 	pr->ps_xsig = signum;
 	pr->ps_trapped = p;
-	mtx_leave(&pr->ps_mtx);
 
-	SCHED_LOCK();
-	atomic_setbits_int(&pr->ps_flags, PS_TRAPPED);
-	proc_stop(p, 1);
+	process_suspend_signal(pr);
+	proc_stop_finish(p);
+	/*
+	 * Clear all flags for proc and process by hand here since ptrace
+	 * just calls setrunnable on the thread without clearing anything.
+	 */
+	atomic_clearbits_int(&p->p_flag, P_SUSPSIG);
 	atomic_clearbits_int(&pr->ps_flags,
 	    PS_WAITED | PS_STOPPED | PS_TRAPPED);
-	SCHED_UNLOCK();
 
-	mtx_enter(&pr->ps_mtx);
 	signum = pr->ps_xsig;
 	pr->ps_xsig = 0;
 	pr->ps_trapped = NULL;
-	mtx_leave(&pr->ps_mtx);
 
-	if ((p->p_flag & P_TRACESINGLE) == 0)
-		single_thread_clear(p);
+	if ((p->p_flag & P_TRACESINGLE) == 0) {
+		SCHED_LOCK();
+		process_continue(pr, P_SUSPSIG);
+		SCHED_UNLOCK();
+	}
 	atomic_clearbits_int(&p->p_flag, P_TRACESINGLE);
+	mtx_leave(&pr->ps_mtx);
 
 	return signum;
 }
@@ -1562,7 +1574,8 @@ process_continue(struct process *pr, int
 		 * Clearing either makes the thread runnable or puts
 		 * it back into some sleep queue.
 		 */
-		if (q->p_stat == SSTOP) {
+		if (q->p_stat == SSTOP &&
+		    ISSET(q->p_flag, P_SUSPSIG | P_SUSPSINGLE) == 0) {
 			if (q->p_wchan == NULL)
 				setrunnable(q);
 			else
@@ -1586,10 +1599,10 @@ process_stop(struct process *pr, int fla
 	/* skip curproc if it is part of pr, caller takes care of that */
 	if (curproc->p_p == pr) {
 		p = curproc;
-		KASSERT(ISSET(p->p_flag, P_SUSPSINGLE) == 0);
+		KASSERT(ISSET(p->p_flag, P_SUSPSIG | P_SUSPSINGLE) == 0);
 	}
 
-	pr->ps_singlecnt = pr->ps_threadcnt;
+	pr->ps_suspendcnt = pr->ps_threadcnt;
 	TAILQ_FOREACH(q, &pr->ps_threads, p_thr_link) {
 		if (q == p)
 			continue;
@@ -1608,7 +1621,7 @@ process_stop(struct process *pr, int fla
 				unsleep(q);
 				setrunnable(q);
 			} else
-				--pr->ps_singlecnt;
+				--pr->ps_suspendcnt;
 			break;
 		case SSLEEP:
 			/* if it's not interruptible, then just have to wait */
@@ -1616,7 +1629,7 @@ process_stop(struct process *pr, int fla
 				/* merely need to suspend?  just stop it */
 				if (mode == SINGLE_SUSPEND) {
 					q->p_stat = SSTOP;
-					--pr->ps_singlecnt;
+					--pr->ps_suspendcnt;
 				} else {
 					/* need to unwind or exit, so wake it */
 					unsleep(q);
@@ -1637,56 +1650,77 @@ process_stop(struct process *pr, int fla
 }
 
 /*
- * Put the argument process into the stopped state and notify the parent
- * via wakeup.  Signals are handled elsewhere.  The process must not be
- * on the run queue.
+ * Prepare a proc to be stopped.
  */
 void
-proc_stop(struct proc *p, int sw)
+proc_stop_setup(struct proc *p)
 {
-	struct process *pr = p->p_p;
-
-#ifdef MULTIPROCESSOR
+	MUTEX_ASSERT_LOCKED(&p->p_p->ps_mtx);
+	/*
+	 * XXX in ptsignal the SCHED_LOCK is already held so we can't
+	 * grab it here until that is fixed.
+	 */
+	/* XXX SCHED_LOCK(); */
 	SCHED_ASSERT_LOCKED();
-#endif
-	/* do not stop exiting procs */
-	if (ISSET(p->p_flag, P_WEXIT))
-		return;
 
+	TRACEPOINT(sched, stop, NULL);
+
+	atomic_setbits_int(&p->p_flag, P_INSCHED);
 	p->p_stat = SSTOP;
-	atomic_clearbits_int(&pr->ps_flags, PS_WAITED);
-	atomic_setbits_int(&pr->ps_flags, PS_STOPPING);
-	atomic_setbits_int(&p->p_flag, P_SUSPSIG);
-	/*
-	 * We need this soft interrupt to be handled fast.
-	 * Extra calls to softclock don't hurt.
-	 */
-	softintr_schedule(proc_stop_si);
-	if (sw)
+	/* XXX SCHED_UNLOCK(); */
+}
+
+/*
+ * Finish stopping a process if the condition still holds.
+ */
+void
+proc_stop_finish(struct proc *p)
+{
+	struct process *pr = p->p_p;
+
+	MUTEX_ASSERT_LOCKED(&pr->ps_mtx);
+	mtx_leave(&pr->ps_mtx);
+	SCHED_LOCK();
+
+	atomic_clearbits_int(&p->p_flag, P_INSCHED);
+	if (p->p_stat == SSTOP) {
+		p->p_ru.ru_nvcsw++;
 		mi_switch();
+	}
+	KASSERT(p->p_stat == SONPROC);
+
+	SCHED_UNLOCK();
+	mtx_enter(&pr->ps_mtx);
 }
 
 /*
- * Called from a soft interrupt to send signals to the parents of stopped
- * processes.
- * We can't do this in proc_stop because it's called with nasty locks held
- * and we would need recursive scheduler lock to deal with that.
+ * Signal either the parent process or the ps_single thread depending on
+ * the mode. Only do this if the suspendcnt dropped to 0. If curproc part
+ * of the process count it out first.
  */
 void
-proc_stop_sweep(void *v)
+process_suspend_signal(struct process *pr)
 {
-	struct process *pr;
+	MUTEX_ASSERT_LOCKED(&pr->ps_mtx);
 
-	LIST_FOREACH(pr, &allprocess, ps_list) {
-		if ((pr->ps_flags & PS_STOPPING) == 0)
-			continue;
+	/* if part of the process, count us out */
+	if (curproc->p_p == pr)
+		--pr->ps_suspendcnt;
+
+	if (pr->ps_suspendcnt != 0)
+		return;
+
+	if (pr->ps_single == NULL) {
+		atomic_clearbits_int(&pr->ps_flags,
+		    PS_STOPPING | PS_WAITED | PS_CONTINUED);
 		atomic_setbits_int(&pr->ps_flags, PS_STOPPED);
-		atomic_clearbits_int(&pr->ps_flags, PS_STOPPING);
 
 		if ((pr->ps_pptr->ps_sigacts->ps_sigflags & SAS_NOCLDSTOP) == 0)
 			prsignal(pr->ps_pptr, SIGCHLD);
 		atomic_setbits_int(&pr->ps_pptr->ps_flags, PS_WAITEVENT);
 		wakeup(pr->ps_pptr);
+	} else {
+		wakeup(&pr->ps_suspendcnt);
 	}
 }
 
@@ -2156,7 +2190,7 @@ userret(struct proc *p)
 	struct sigctx ctx;
 	int signum;
 
-	if (p->p_flag & P_SUSPSINGLE)
+	if (atomic_load_int(&p->p_flag) & (P_SUSPSINGLE | P_SUSPSIG))
 		proc_suspend_check(p, 0);
 
 	/* send SIGPROF or SIGVTALRM if their timers interrupted this thread */
@@ -2200,7 +2234,8 @@ proc_suspend_check_locked(struct proc *p
 
 	MUTEX_ASSERT_LOCKED(&pr->ps_mtx);
 
-	if (pr->ps_single == NULL || pr->ps_single == p)
+	if ((pr->ps_single == NULL || pr->ps_single == p) &&
+	    !ISSET(pr->ps_flags, PS_STOPPING))
 		return (0);
 
 	/* if we're in deep, we need to unwind to the edge */
@@ -2225,18 +2260,14 @@ proc_suspend_check_locked(struct proc *p
 			/* NOTREACHED */
 		}
 
-		if (--pr->ps_singlecnt == 0)
-			wakeup(&pr->ps_singlecnt);
-
-		/* not exiting and don't need to unwind, so suspend */
-		mtx_leave(&pr->ps_mtx);
-
 		SCHED_LOCK();
-		p->p_stat = SSTOP;
-		mi_switch();
+		proc_stop_setup(p);
 		SCHED_UNLOCK();
-		mtx_enter(&pr->ps_mtx);
-	} while (pr->ps_single != NULL);
+		process_suspend_signal(pr);
+
+		/* not exiting and don't need to unwind, so suspend */
+		proc_stop_finish(p);
+	} while (pr->ps_single != NULL || ISSET(pr->ps_flags, PS_STOPPING));
 
 	return (0);
 }
@@ -2299,37 +2330,15 @@ single_thread_set(struct proc *p, int fl
 	SCHED_UNLOCK();
 
 	/* count ourself out */
-	--pr->ps_singlecnt;
-	mtx_leave(&pr->ps_mtx);
-
-	if ((flags & SINGLE_NOWAIT) == 0)
-		single_thread_wait(pr, 1);
+	--pr->ps_suspendcnt;
 
-	return 0;
-}
-
-/*
- * Wait for other threads to stop. If recheck is false then the function
- * returns non-zero if the caller needs to restart the check else 0 is
- * returned. If recheck is true the return value is always 0.
- */
-int
-single_thread_wait(struct process *pr, int recheck)
-{
-	int wait;
-
-	/* wait until they're all suspended */
-	mtx_enter(&pr->ps_mtx);
-	while ((wait = pr->ps_singlecnt > 0)) {
-		msleep_nsec(&pr->ps_singlecnt, &pr->ps_mtx, PWAIT, "suspend",
+	/* wait until all other threads suspended */
+	while (pr->ps_suspendcnt > 0)
+		msleep_nsec(&pr->ps_suspendcnt, &pr->ps_mtx, PWAIT, "suspend",
 		    INFSLP);
-		if (!recheck)
-			break;
-	}
-	KASSERT((pr->ps_single->p_flag & P_SUSPSINGLE) == 0);
 	mtx_leave(&pr->ps_mtx);
-
-	return wait;
+	KASSERT((pr->ps_single->p_flag & P_SUSPSINGLE) == 0);
+	return 0;
 }
 
 void
Index: kern/kern_synch.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_synch.c,v
diff -u -p -r1.219 kern_synch.c
--- kern/kern_synch.c	5 Feb 2025 12:21:27 -0000	1.219
+++ kern/kern_synch.c	20 Feb 2025 12:33:52 -0000
@@ -64,8 +64,6 @@
 
 int	sleep_signal_check(struct proc *, int);
 
-extern void proc_stop(struct proc *p, int);
-
 /*
  * We're only looking at 7 bits of the address; everything is
  * aligned to 4, lots of things are aligned to greater powers
@@ -353,7 +351,7 @@ sleep_setup(const volatile void *ident, 
 	p->p_wmesg = wmesg;
 	p->p_slptime = 0;
 	p->p_slppri = prio & PRIMASK;
-	atomic_setbits_int(&p->p_flag, P_WSLEEP);
+	atomic_setbits_int(&p->p_flag, P_INSCHED);
 	TAILQ_INSERT_TAIL(&slpque[LOOKUP(ident)], p, p_runq);
 	if (prio & PCATCH)
 		atomic_setbits_int(&p->p_flag, P_SINTR);
@@ -399,7 +397,7 @@ sleep_finish(int timo, int do_sleep)
 		unsleep(p);
 	if (p->p_stat == SSTOP)
 		do_sleep = 1;
-	atomic_clearbits_int(&p->p_flag, P_WSLEEP);
+	atomic_clearbits_int(&p->p_flag, P_INSCHED);
 
 	if (do_sleep) {
 		KASSERT(p->p_stat == SSLEEP || p->p_stat == SSTOP);
@@ -458,6 +456,7 @@ sleep_finish(int timo, int do_sleep)
 int
 sleep_signal_check(struct proc *p, int after_sleep)
 {
+	struct process *pr = p->p_p;
 	struct sigctx ctx;
 	int err, sig;
 
@@ -467,24 +466,38 @@ sleep_signal_check(struct proc *p, int a
 
 		/* requested to stop */
 		if (!after_sleep) {
-			mtx_enter(&p->p_p->ps_mtx);
-			if (--p->p_p->ps_singlecnt == 0)
-				wakeup(&p->p_p->ps_singlecnt);
-			mtx_leave(&p->p_p->ps_mtx);
+			mtx_enter(&pr->ps_mtx);
+			process_suspend_signal(pr);
 
 			SCHED_LOCK();
 			p->p_stat = SSTOP;
 			SCHED_UNLOCK();
+			mtx_leave(&pr->ps_mtx);
 		}
 	}
 
 	if ((sig = cursig(p, &ctx, 1)) != 0) {
 		if (ctx.sig_stop) {
 			if (!after_sleep) {
-				p->p_p->ps_xsig = sig;
+				mtx_enter(&pr->ps_mtx);
+				pr->ps_xsig = sig;
+				/*
+				 * This is for stop signals delivered before
+				 * sleep_setup() was called. We need to do the
+				 * full dance here before going to sleep.
+				 */
+				atomic_clearbits_int(&p->p_siglist,
+				    sigmask(sig));
+				atomic_setbits_int(&pr->ps_flags, PS_STOPPING);
+				SCHED_LOCK();
+				process_stop(pr, P_SUSPSIG, SINGLE_SUSPEND);
+				SCHED_UNLOCK();
+				atomic_setbits_int(&p->p_flag, P_SUSPSIG);
+				process_suspend_signal(pr);
 				SCHED_LOCK();
-				proc_stop(p, 0);
+				p->p_stat = SSTOP;
 				SCHED_UNLOCK();
+				mtx_leave(&pr->ps_mtx);
 			}
 		} else if (ctx.sig_intr && !ctx.sig_ignore)
 			return EINTR;
Index: kern/sched_bsd.c
===================================================================
RCS file: /cvs/src/sys/kern/sched_bsd.c,v
diff -u -p -r1.98 sched_bsd.c
--- kern/sched_bsd.c	24 Nov 2024 13:02:37 -0000	1.98
+++ kern/sched_bsd.c	20 Feb 2025 12:33:52 -0000
@@ -465,9 +465,15 @@ setrunnable(struct proc *p)
 		panic("setrunnable");
 	case SSTOP:
 		prio = p->p_usrpri;
-		/* if not yet asleep, unstop but don't add to runqueue */
-		if (ISSET(p->p_flag, P_WSLEEP)) {
-			p->p_stat = SSLEEP;
+		TRACEPOINT(sched, unstop, p->p_tid + THREAD_PID_OFFSET,
+		    p->p_p->ps_pid, CPU_INFO_UNIT(p->p_cpu));
+
+		/* If not yet stopped or asleep, unstop but don't add to runq */
+		if (ISSET(p->p_flag, P_INSCHED)) {
+			if (p->p_wchan != NULL)
+				p->p_stat = SSLEEP;
+			else
+				p->p_stat = SONPROC;
 			return;
 		}
 		setrunqueue(NULL, p, prio);
@@ -475,12 +481,12 @@ setrunnable(struct proc *p)
 	case SSLEEP:
 		prio = p->p_slppri;
 
+		TRACEPOINT(sched, wakeup, p->p_tid + THREAD_PID_OFFSET,
+		    p->p_p->ps_pid, CPU_INFO_UNIT(p->p_cpu));
 		/* if not yet asleep, don't add to runqueue */
-		if (ISSET(p->p_flag, P_WSLEEP))
+		if (ISSET(p->p_flag, P_INSCHED))
 			return;
 		setrunqueue(NULL, p, prio);
-		TRACEPOINT(sched, wakeup, p->p_tid + THREAD_PID_OFFSET,
-		    p->p_p->ps_pid, CPU_INFO_UNIT(p->p_cpu));
 		break;
 	}
 	if (p->p_slptime > 1) {
Index: sys/proc.h
===================================================================
RCS file: /cvs/src/sys/sys/proc.h,v
diff -u -p -r1.382 proc.h
--- sys/proc.h	17 Feb 2025 15:45:55 -0000	1.382
+++ sys/proc.h	20 Feb 2025 12:33:52 -0000
@@ -191,7 +191,7 @@ struct process {
 
 	struct	proc *ps_single;	/* [m] Thread for single-threading. */
 	struct	proc *ps_trapped;	/* [m] Thread trapped for ptrace. */
-	u_int	ps_singlecnt;		/* [m] Number of threads to suspend. */
+	u_int	ps_suspendcnt;		/* [m] Number of threads to suspend. */
 	u_int	ps_exitcnt;		/* [m] Number of threads in exit1. */
 
 	int	ps_traceflag;		/* Kernel trace points. */
@@ -437,7 +437,7 @@ struct proc {
 #define	P_ALRMPEND	0x00000004	/* SIGVTALRM needs to be posted */
 #define	P_SIGSUSPEND	0x00000008	/* Need to restore before-suspend mask*/
 #define	P_CANTSLEEP	0x00000010	/* insomniac thread */
-#define	P_WSLEEP	0x00000020	/* Working on going to sleep. */
+#define	P_INSCHED	0x00000020	/* Switching scheduler state. */
 #define	P_SINTR		0x00000080	/* Sleep is interruptible. */
 #define	P_SYSTEM	0x00000200	/* No sigs, stats or swapping. */
 #define	P_TIMEOUT	0x00000400	/* Timing out during sleep. */
@@ -451,7 +451,7 @@ struct proc {
 
 #define	P_BITS \
     ("\20" "\01INKTR" "\02PROFPEND" "\03ALRMPEND" "\04SIGSUSPEND" \
-     "\05CANTSLEEP" "\06WSLEEP" "\010SINTR" "\012SYSTEM" "\013TIMEOUT" \
+     "\05CANTSLEEP" "\06INSCHED" "\010SINTR" "\012SYSTEM" "\013TIMEOUT" \
      "\015TRACESINGLE" "\016WEXIT" "\020OWEUPC" "\024SUSPSINGLE" \
      "\033THREAD" "\034SUSPSIG" "\037CPUPEG")
 
@@ -599,13 +599,13 @@ refreshcreds(struct proc *p)
 #define	SINGLE_MASK	0x0f
 /* extra flags for single_thread_set */
 #define	SINGLE_DEEP	0x10	/* call is in deep */
-#define	SINGLE_NOWAIT	0x20	/* do not wait for other threads to stop */
 
 int	single_thread_set(struct proc *, int);
-int	single_thread_wait(struct process *, int);
 void	single_thread_clear(struct proc *);
 
 int	proc_suspend_check(struct proc *, int);
+void	process_suspend_signal(struct process *);
+void	process_stop(struct process *, int, int);
 
 void	child_return(void *);
 
Index: sys/signalvar.h
===================================================================
RCS file: /cvs/src/sys/sys/signalvar.h,v
diff -u -p -r1.57 signalvar.h
--- sys/signalvar.h	4 Nov 2024 22:41:50 -0000	1.57
+++ sys/signalvar.h	20 Feb 2025 12:33:52 -0000
@@ -89,7 +89,7 @@ struct	sigacts {
 #define	sigcantmask	(sigmask(SIGKILL) | sigmask(SIGSTOP))
 
 #ifdef _KERNEL
-enum signal_type { SPROCESS, STHREAD, SPROPAGATED };
+enum signal_type { SPROCESS, STHREAD };
 
 struct sigio_ref;