Index | Thread | Search

From:
Claudio Jeker <cjeker@diehard.n-r-g.com>
Subject:
Re: fix stop signal delivery
To:
tech@openbsd.org
Date:
Mon, 10 Feb 2025 07:49:39 +0100

Download raw body.

Thread
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.

-- 
:wq Claudio

diff --git kern/kern_exec.c kern/kern_exec.c
index de95acf6b19..fec6bec2a75 100644
--- kern/kern_exec.c
+++ kern/kern_exec.c
@@ -556,6 +556,7 @@ sys_execve(struct proc *p, void *v, register_t *retval)
 	if (pr->ps_flags & PS_PPWAIT) {
 		atomic_clearbits_int(&pr->ps_flags, PS_PPWAIT);
 		atomic_clearbits_int(&pr->ps_pptr->ps_flags, PS_ISPWAIT);
+		atomic_setbits_int(&pr->ps_pptr->ps_flags, PS_WAITEVENT);
 		wakeup(pr->ps_pptr);
 	}
 
diff --git kern/kern_exit.c kern/kern_exit.c
index aec356ade88..3e85863a183 100644
--- kern/kern_exit.c
+++ kern/kern_exit.c
@@ -148,6 +148,8 @@ exit1(struct proc *p, int xexit, int xsig, int flags)
 			atomic_clearbits_int(&pr->ps_flags, PS_PPWAIT);
 			atomic_clearbits_int(&pr->ps_pptr->ps_flags,
 			    PS_ISPWAIT);
+			atomic_setbits_int(&pr->ps_pptr->ps_flags,
+			    PS_WAITEVENT);
 			wakeup(pr->ps_pptr);
 		}
 
@@ -162,13 +164,11 @@ exit1(struct proc *p, int xexit, int xsig, int flags)
 	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 || 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();
@@ -358,6 +358,7 @@ exit1(struct proc *p, int xexit, int xsig, int flags)
 		if (pr->ps_flags & PS_NOZOMBIE) {
 			struct process *ppr = pr->ps_pptr;
 			process_reparent(pr, initprocess);
+			atomic_setbits_int(&ppr->ps_flags, PS_WAITEVENT);
 			wakeup(ppr);
 		}
 		mtx_leave(&pr->ps_mtx);
@@ -486,6 +487,8 @@ reaper(void *arg)
 			if (pr->ps_flags & PS_ZOMBIE) {
 				/* Post SIGCHLD and wake up parent. */
 				prsignal(pr->ps_pptr, SIGCHLD);
+				atomic_setbits_int(&pr->ps_pptr->ps_flags,
+				    PS_WAITEVENT);
 				wakeup(pr->ps_pptr);
 			} else {
 				/* No one will wait for us, just zap it. */
@@ -508,15 +511,19 @@ dowait6(struct proc *q, idtype_t idtype, id_t id, int *statusp, int options,
 		memset(info, 0, sizeof(*info));
 
 loop:
+	atomic_clearbits_int(&q->p_p->ps_flags, PS_WAITEVENT);
 	nfound = 0;
 	LIST_FOREACH(pr, &q->p_p->ps_children, ps_sibling) {
+		mtx_enter(&pr->ps_mtx);
 		if ((pr->ps_flags & PS_NOZOMBIE) ||
 		    (idtype == P_PID && id != pr->ps_pid) ||
-		    (idtype == P_PGID && id != pr->ps_pgid))
+		    (idtype == P_PGID && id != pr->ps_pgid)) {
+			mtx_leave(&pr->ps_mtx);
 			continue;
-
+		}
 		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;
@@ -545,10 +552,9 @@ loop:
 		}
 		if ((options & WTRAPPED) && (pr->ps_flags & PS_TRACED) &&
 		    (pr->ps_flags & PS_WAITED) == 0 &&
+		    (pr->ps_flags & PS_STOPPED) &&
 		    (pr->ps_flags & PS_TRAPPED)) {
-			if (single_thread_wait(pr, 0))
-				goto loop;
-
+			mtx_leave(&pr->ps_mtx);
 			if ((options & WNOWAIT) == 0)
 				atomic_setbits_int(&pr->ps_flags, PS_WAITED);
 
@@ -571,6 +577,7 @@ 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);
 
@@ -590,6 +597,7 @@ loop:
 			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);
@@ -609,6 +617,7 @@ loop:
 				memset(rusage, 0, sizeof(*rusage));
 			return (0);
 		}
+		mtx_leave(&pr->ps_mtx);
 	}
 	/*
 	 * Look in the orphans list too, to allow the parent to
@@ -638,7 +647,9 @@ loop:
 		*retval = 0;
 		return (0);
 	}
-	if ((error = tsleep_nsec(q->p_p, PWAIT | PCATCH, "wait", INFSLP)) != 0)
+	sleep_setup(q->p_p, PWAIT | PCATCH, "wait");
+	if ((error = sleep_finish(0,
+	    !ISSET(READ_ONCE(q->p_p->ps_flags), PS_WAITEVENT))) != 0)
 		return (error);
 	goto loop;
 }
@@ -746,6 +757,7 @@ proc_finish_wait(struct proc *waiter, struct process *pr)
 		process_reparent(pr, tr);
 		mtx_leave(&pr->ps_mtx);
 		prsignal(tr, SIGCHLD);
+		atomic_setbits_int(&tr->ps_flags, PS_WAITEVENT);
 		wakeup(tr);
 	} else {
 		mtx_leave(&pr->ps_mtx);
diff --git kern/kern_fork.c kern/kern_fork.c
index 33a85a45fe3..61bc434d8b4 100644
--- kern/kern_fork.c
+++ kern/kern_fork.c
@@ -593,8 +593,8 @@ thread_fork(struct proc *curp, void *stack, void *tcb, pid_t *tidptr,
 	 * if somebody else wants to take us to single threaded mode,
 	 * count ourselves in.
 	 */
-	if (pr->ps_single) {
-		pr->ps_singlecnt++;
+	if (pr->ps_single || ISSET(pr->ps_flags, PS_STOPPING)) {
+		pr->ps_suspendcnt++;
 		atomic_setbits_int(&p->p_flag, P_SUSPSINGLE);
 	}
 	mtx_leave(&pr->ps_mtx);
diff --git kern/kern_proc.c kern/kern_proc.c
index 7abbab5fd48..2ce1f577199 100644
--- kern/kern_proc.c
+++ kern/kern_proc.c
@@ -501,7 +501,7 @@ proc_printit(struct proc *p, const char *modif,
 	    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",
diff --git kern/kern_sig.c kern/kern_sig.c
index b3ebcee6686..e5649febda1 100644
--- kern/kern_sig.c
+++ kern/kern_sig.c
@@ -121,12 +121,9 @@ 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(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 +131,7 @@ void postsig(struct proc *, int, struct sigctx *);
 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 +201,6 @@ cansignal(struct proc *p, struct process *qr, int signum)
 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 +910,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 +1001,7 @@ ptsignal_locked(struct proc *p, int signum, enum signal_type type)
 		}
 	}
 
-	if (type != SPROPAGATED)
-		knote_locked(&pr->ps_klist, NOTE_SIGNAL | signum);
+	knote_locked(&pr->ps_klist, NOTE_SIGNAL | signum);
 
 	prop = sigprop[signum];
 
@@ -1058,20 +1049,11 @@ ptsignal_locked(struct proc *p, int signum, enum signal_type type)
 	}
 	/*
 	 * 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 +1204,9 @@ ptsignal_locked(struct proc *p, int signum, enum signal_type type)
 				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;
 		}
 		/*
@@ -1269,8 +1253,15 @@ out:
 	}
 
 	SCHED_UNLOCK();
-	if (wakeparent)
-		wakeup(pr->ps_pptr);
+	if (wakeparent) {
+		if (prop & SA_STOP)
+			process_suspend_signal(pr);
+		else {
+			atomic_setbits_int(&pr->ps_pptr->ps_flags,
+			    PS_WAITEVENT);
+			wakeup(pr->ps_pptr);
+		}
+	}
 }
 
 /* fill the signal context which should be used by postsig() and issignal() */
@@ -1444,10 +1435,16 @@ cursig(struct proc *p, struct sigctx *sctx, int deep)
 			 * 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);
 				SCHED_UNLOCK();
+				atomic_setbits_int(&p->p_flag, P_SUSPSIG);
+				process_suspend_signal(pr);
+				proc_stop(p);
+				mtx_leave(&pr->ps_mtx);
 				break;
 			} else if (prop & SA_IGNORE) {
 				/*
@@ -1497,21 +1494,42 @@ proc_trap(struct proc *p, int signum)
 {
 	struct process *pr = p->p_p;
 
-	single_thread_set(p, SINGLE_SUSPEND | SINGLE_NOWAIT);
-	pr->ps_xsig = signum;
+	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();
-	atomic_setbits_int(&pr->ps_flags, PS_TRAPPED);
-	proc_stop(p, 1);
+	process_stop(pr, P_SUSPSIG, SINGLE_SUSPEND);
+	SCHED_UNLOCK();
+	atomic_setbits_int(&p->p_flag, P_SUSPSIG);
+	pr->ps_xsig = signum;
+	pr->ps_trapped = p;
+
+	process_suspend_signal(pr);
+	proc_stop(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();
 
 	signum = pr->ps_xsig;
 	pr->ps_xsig = 0;
-	if ((p->p_flag & P_TRACESINGLE) == 0)
-		single_thread_clear(p);
+	pr->ps_trapped = NULL;
+
+	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;
 }
@@ -1552,7 +1570,8 @@ process_continue(struct process *pr, int flag)
 		 * 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
@@ -1576,10 +1595,10 @@ process_stop(struct process *pr, int flag, int mode)
 	/* 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;
@@ -1598,7 +1617,7 @@ process_stop(struct process *pr, int flag, int mode)
 				unsleep(q);
 				setrunnable(q);
 			} else
-				--pr->ps_singlecnt;
+				--pr->ps_suspendcnt;
 			break;
 		case SSLEEP:
 			/* if it's not interruptible, then just have to wait */
@@ -1606,7 +1625,7 @@ process_stop(struct process *pr, int flag, int mode)
 				/* 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);
@@ -1632,50 +1651,58 @@ process_stop(struct process *pr, int flag, int mode)
  * on the run queue.
  */
 void
-proc_stop(struct proc *p, int sw)
+proc_stop(struct proc *p)
 {
 	struct process *pr = p->p_p;
 
-#ifdef MULTIPROCESSOR
-	SCHED_ASSERT_LOCKED();
-#endif
-	/* do not stop exiting procs */
-	if (ISSET(p->p_flag, P_WEXIT))
-		return;
+	MUTEX_ASSERT_LOCKED(&pr->ps_mtx);
 
-	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.
+	 * XXX to mi_switch we need to release the ps_mtx but then
+	 * another ptsignal can race against this thread and see
+	 * it in the wrong state. So instead set p_stat while still
+	 * holding the ps_mtx.
+	 * We still have an issue when dropping the ps_mtx and grabbing
+	 * the SCHED_LOCK but that can't be fixed.
 	 */
-	softintr_schedule(proc_stop_si);
-	if (sw)
-		mi_switch();
+	SCHED_LOCK();
+	p->p_stat = SSTOP;
+	SCHED_UNLOCK();
+	mtx_leave(&pr->ps_mtx);
+	SCHED_LOCK();
+	mi_switch();
+	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);
 	}
 }
 
@@ -2145,7 +2172,7 @@ userret(struct proc *p)
 	struct sigctx ctx;
 	int signum;
 
-	if (p->p_flag & P_SUSPSINGLE)
+	if (p->p_flag & (P_SUSPSINGLE | P_SUSPSIG))
 		proc_suspend_check(p, 0);
 
 	/* send SIGPROF or SIGVTALRM if their timers interrupted this thread */
@@ -2189,7 +2216,8 @@ proc_suspend_check_locked(struct proc *p, int deep)
 
 	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 */
@@ -2214,18 +2242,11 @@ proc_suspend_check_locked(struct proc *p, int deep)
 			/* NOTREACHED */
 		}
 
-		if (--pr->ps_singlecnt == 0)
-			wakeup(&pr->ps_singlecnt);
+		process_suspend_signal(pr);
 
 		/* not exiting and don't need to unwind, so suspend */
-		mtx_leave(&pr->ps_mtx);
-
-		SCHED_LOCK();
-		p->p_stat = SSTOP;
-		mi_switch();
-		SCHED_UNLOCK();
-		mtx_enter(&pr->ps_mtx);
-	} while (pr->ps_single != NULL);
+		proc_stop(p);
+	} while (pr->ps_single != NULL || ISSET(pr->ps_flags, PS_STOPPING));
 
 	return (0);
 }
@@ -2288,12 +2309,10 @@ single_thread_set(struct proc *p, int flags)
 	SCHED_UNLOCK();
 
 	/* count ourself out */
-	--pr->ps_singlecnt;
+	--pr->ps_suspendcnt;
 	mtx_leave(&pr->ps_mtx);
 
-	if ((flags & SINGLE_NOWAIT) == 0)
-		single_thread_wait(pr, 1);
-
+	single_thread_wait(pr, 1);
 	return 0;
 }
 
@@ -2309,8 +2328,8 @@ single_thread_wait(struct process *pr, int recheck)
 
 	/* 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",
+	while ((wait = pr->ps_suspendcnt > 0)) {
+		msleep_nsec(&pr->ps_suspendcnt, &pr->ps_mtx, PWAIT, "suspend",
 		    INFSLP);
 		if (!recheck)
 			break;
diff --git kern/kern_synch.c kern/kern_synch.c
index 8548aea1c87..db97fde0c29 100644
--- kern/kern_synch.c
+++ kern/kern_synch.c
@@ -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
@@ -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 after_sleep)
 
 		/* 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;
diff --git kern/sys_process.c kern/sys_process.c
index d06b8f9e19a..365e44258c7 100644
--- kern/sys_process.c
+++ kern/sys_process.c
@@ -330,12 +330,11 @@ ptrace_ctrl(struct proc *p, int req, pid_t pid, caddr_t addr, int data)
 	case PT_ATTACH:
 	case PT_DETACH:
 		/* Find the process we're supposed to be operating on. */
-		if ((tr = prfind(pid)) == NULL) {
+		if (pid > THREAD_PID_OFFSET) {
 			error = ESRCH;
 			goto fail;
 		}
-		t = TAILQ_FIRST(&tr->ps_threads);
-		break;
+		/* FALLTHROUGH */
 
 	/* calls that accept a PID or a thread ID */
 	case PT_CONTINUE:
@@ -466,16 +465,6 @@ ptrace_ctrl(struct proc *p, int req, pid_t pid, caddr_t addr, int data)
 		 * from where it stopped."
 		 */
 
-		if (pid < THREAD_PID_OFFSET && tr->ps_single)
-			t = tr->ps_single;
-		else if (t == tr->ps_single)
-			atomic_setbits_int(&t->p_flag, P_TRACESINGLE);
-		else {
-			error = EINVAL;
-			goto fail;
-		}
-			
-
 		/* If the address parameter is not (int *)1, set the pc. */
 		if ((int *)addr != (int *)1)
 			if ((error = process_set_pc(t, addr)) != 0)
@@ -504,9 +493,6 @@ ptrace_ctrl(struct proc *p, int req, pid_t pid, caddr_t addr, int data)
 		 * from where it stopped."
 		 */
 
-		if (pid < THREAD_PID_OFFSET && tr->ps_single)
-			t = tr->ps_single;
-
 #ifdef PT_STEP
 		/*
 		 * Stop single stepping.
@@ -525,25 +511,29 @@ ptrace_ctrl(struct proc *p, int req, pid_t pid, caddr_t addr, int data)
 		memset(tr->ps_ptstat, 0, sizeof(*tr->ps_ptstat));
 
 		/* Finally, deliver the requested signal (or none). */
-		if (t->p_stat == SSTOP) {
-			tr->ps_xsig = data;
+		if (tr->ps_trapped == t) {
 			SCHED_LOCK();
+			if (pid >= THREAD_PID_OFFSET)
+				atomic_setbits_int(&t->p_flag,
+				    P_TRACESINGLE);
+			tr->ps_xsig = data;
 			unsleep(t);
 			setrunnable(t);
 			SCHED_UNLOCK();
-		} else {
+		} else if (pid < THREAD_PID_OFFSET) {
 			if (data != 0)
-				psignal(t, data);
+				ptsignal(t, data, SPROCESS);
+		} else {
+			/* can not signal a single thread */
+			error = EINVAL;
+			goto fail;
 		}
 		break;
 
 	case PT_KILL:
-		if (pid < THREAD_PID_OFFSET && tr->ps_single)
-			t = tr->ps_single;
-
 		/* just send the process a KILL signal. */
 		data = SIGKILL;
-		goto sendsig;	/* in PT_CONTINUE, above. */
+		goto sendsig;	/* in PT_DETACH, above. */
 
 	case PT_ATTACH:
 		/*
@@ -625,9 +615,13 @@ ptrace_kstate(struct proc *p, int req, pid_t pid, void *addr)
 		tr->ps_ptmask = pe->pe_set_event;
 		break;
 	case PT_GET_PROCESS_STATE:
-		if (tr->ps_single)
-			tr->ps_ptstat->pe_tid =
-			    tr->ps_single->p_tid + THREAD_PID_OFFSET;
+		mtx_enter(&tr->ps_mtx);
+		if (tr->ps_trapped != NULL)
+			tr->ps_ptstat->pe_tid = tr->ps_trapped->p_tid +
+			    THREAD_PID_OFFSET;
+		else
+			tr->ps_ptstat->pe_tid = 0;
+		mtx_leave(&tr->ps_mtx);
 		memcpy(addr, tr->ps_ptstat, sizeof *tr->ps_ptstat);
 		break;
 	default:
@@ -812,21 +806,26 @@ ptrace_ustate(struct proc *p, int req, pid_t pid, void *addr, int data,
 static inline struct process *
 process_tprfind(pid_t tpid, struct proc **tp)
 {
-	if (tpid > THREAD_PID_OFFSET) {
-		struct proc *t = tfind(tpid - THREAD_PID_OFFSET);
+	struct process *tr;
+	struct proc *t;
 
+	if (tpid > THREAD_PID_OFFSET) {
+		t = tfind(tpid - THREAD_PID_OFFSET);
 		if (t == NULL)
 			return NULL;
-		*tp = t;
-		return t->p_p;
+		tr = t->p_p;
 	} else {
-		struct process *tr = prfind(tpid);
-
+		tr = prfind(tpid);
 		if (tr == NULL)
 			return NULL;
-		*tp = TAILQ_FIRST(&tr->ps_threads);
-		return tr;
+		if (tr->ps_trapped != NULL)
+			t = tr->ps_trapped;
+		else
+			t = TAILQ_FIRST(&tr->ps_threads);
 	}
+
+	*tp = t;
+	return tr;
 }
 
 
diff --git sys/proc.h sys/proc.h
index dd4b8686cf9..a68e925e044 100644
--- sys/proc.h
+++ sys/proc.h
@@ -190,7 +190,8 @@ struct process {
 	int	ps_siglist;		/* Signals pending for the process. */
 
 	struct	proc *ps_single;	/* [m] Thread for single-threading. */
-	u_int	ps_singlecnt;		/* [m] Number of threads to suspend. */
+	struct	proc *ps_trapped;	/* [m] Thread trapped for ptrace. */
+	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. */
@@ -303,6 +304,7 @@ struct process {
 #define	PS_CHROOT	0x01000000	/* Process is chrooted */
 #define	PS_NOBTCFI	0x02000000	/* No Branch Target CFI */
 #define	PS_ITIMER	0x04000000	/* Virtual interval timers running */
+#define	PS_WAITEVENT	0x10000000	/* wait(2) event pending */
 #define	PS_CONTINUED	0x20000000	/* Continued proc not yet waited for */
 #define	PS_STOPPED	0x40000000	/* Stopped process */
 #define	PS_TRAPPED	0x80000000	/* Stopped due to tracing event */
@@ -313,8 +315,8 @@ struct process {
      "\013WAITED" "\014COREDUMP" "\015SINGLEEXIT" "\016SINGLEUNWIND" \
      "\017NOZOMBIE" "\020STOPPING" "\021SYSTEM" "\022EMBRYO" "\023ZOMBIE" \
      "\024NOBROADCASTKILL" "\025PLEDGE" "\026WXNEEDED" "\027EXECPLEDGE" \
-     "\030ORPHAN" "\031CHROOT" "\032NOBTCFI" "\033ITIMER" "\036CONTINUED" \
-     "\037STOPPED" "\040TRAPPED")
+     "\030ORPHAN" "\031CHROOT" "\032NOBTCFI" "\033ITIMER" "\035WAITEVENT" \
+     "\036CONTINUED" "\037STOPPED" "\040TRAPPED")
 
 struct kcov_dev;
 struct lock_list_entry;
@@ -596,13 +598,14 @@ 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 *);
 
diff --git sys/signalvar.h sys/signalvar.h
index b41ca3adf3c..dffc5e01567 100644
--- sys/signalvar.h
+++ sys/signalvar.h
@@ -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;