Module Name:    src
Committed By:   martin
Date:           Mon Sep  4 16:57:57 UTC 2023

Modified Files:
        src/sys/kern [netbsd-10]: subr_workqueue.c
        src/tests/rump/kernspace [netbsd-10]: kernspace.h workqueue.c
        src/tests/rump/rumpkern [netbsd-10]: Makefile t_workqueue.c

Log Message:
Pull up following revision(s) (requested by riastradh in ticket #342):

        sys/kern/subr_workqueue.c: revision 1.42
        sys/kern/subr_workqueue.c: revision 1.43
        sys/kern/subr_workqueue.c: revision 1.44
        sys/kern/subr_workqueue.c: revision 1.45
        sys/kern/subr_workqueue.c: revision 1.46
        tests/rump/kernspace/workqueue.c: revision 1.7
        sys/kern/subr_workqueue.c: revision 1.47
        tests/rump/kernspace/workqueue.c: revision 1.8
        tests/rump/kernspace/workqueue.c: revision 1.9
        tests/rump/rumpkern/t_workqueue.c: revision 1.3
        tests/rump/rumpkern/t_workqueue.c: revision 1.4
        tests/rump/kernspace/kernspace.h: revision 1.9
        tests/rump/rumpkern/Makefile: revision 1.20

tests/rump/rumpkern: Use PROGDPLIBS, not explicit -L/-l.

This way we relink the t_* test programs whenever changes under
tests/rump/kernspace change libkernspace.a.

workqueue(9) tests: Nix trailing whitespace.

workqueue(9) tests: Destroy struct work immediately on entry.

workqueue(9) tests: Add test for PR kern/57574.

workqueue(9): Avoid touching running work items in workqueue_wait.

As soon as the workqueue function has called, it is forbidden to
touch the struct work passed to it -- the function might free or
reuse the data structure it is embedded in.

So workqueue_wait is forbidden to search the queue for the batch of
running work items.  Instead, use a generation number which is odd
while the thread is processing a batch of work and even when not.

There's still a small optimization available with the struct work
pointer to wait for: if we find the work item in one of the per-CPU
_pending_ queues, then after we wait for a batch of work to complete
on that CPU, we don't need to wait for work on any other CPUs.
PR kern/57574

workqueue(9): Sprinkle dtrace probes for workqueue_wait edge cases.

Let's make it easy to find out whether these are hit.

workqueue(9): Stop violating queue(3) internals.

workqueue(9): Avoid unnecessary mutex_exit/enter cycle each loop.

workqueue(9): Sort includes.
No functional change intended.

workqueue(9): Factor out wq->wq_flags & WQ_FPU in workqueue_worker.
No functional change intended.  Makes it clearer that s is
initialized when used.


To generate a diff of this commit:
cvs rdiff -u -r1.41 -r1.41.2.1 src/sys/kern/subr_workqueue.c
cvs rdiff -u -r1.8 -r1.8.10.1 src/tests/rump/kernspace/kernspace.h
cvs rdiff -u -r1.6 -r1.6.16.1 src/tests/rump/kernspace/workqueue.c
cvs rdiff -u -r1.19 -r1.19.8.1 src/tests/rump/rumpkern/Makefile
cvs rdiff -u -r1.2 -r1.2.16.1 src/tests/rump/rumpkern/t_workqueue.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/kern/subr_workqueue.c
diff -u src/sys/kern/subr_workqueue.c:1.41 src/sys/kern/subr_workqueue.c:1.41.2.1
--- src/sys/kern/subr_workqueue.c:1.41	Sat Oct 29 11:41:00 2022
+++ src/sys/kern/subr_workqueue.c	Mon Sep  4 16:57:56 2023
@@ -1,4 +1,4 @@
-/*	$NetBSD: subr_workqueue.c,v 1.41 2022/10/29 11:41:00 riastradh Exp $	*/
+/*	$NetBSD: subr_workqueue.c,v 1.41.2.1 2023/09/04 16:57:56 martin Exp $	*/
 
 /*-
  * Copyright (c)2002, 2005, 2006, 2007 YAMAMOTO Takashi,
@@ -27,19 +27,20 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: subr_workqueue.c,v 1.41 2022/10/29 11:41:00 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: subr_workqueue.c,v 1.41.2.1 2023/09/04 16:57:56 martin Exp $");
 
 #include <sys/param.h>
+
+#include <sys/condvar.h>
 #include <sys/cpu.h>
-#include <sys/systm.h>
-#include <sys/kthread.h>
 #include <sys/kmem.h>
-#include <sys/proc.h>
-#include <sys/workqueue.h>
+#include <sys/kthread.h>
 #include <sys/mutex.h>
-#include <sys/condvar.h>
-#include <sys/sdt.h>
+#include <sys/proc.h>
 #include <sys/queue.h>
+#include <sys/sdt.h>
+#include <sys/systm.h>
+#include <sys/workqueue.h>
 
 typedef struct work_impl {
 	SIMPLEQ_ENTRY(work_impl) wk_entry;
@@ -51,7 +52,7 @@ struct workqueue_queue {
 	kmutex_t q_mutex;
 	kcondvar_t q_cv;
 	struct workqhead q_queue_pending;
-	struct workqhead q_queue_running;
+	uint64_t q_gen;
 	lwp_t *q_worker;
 };
 
@@ -98,6 +99,12 @@ SDT_PROBE_DEFINE4(sdt, kernel, workqueue
 SDT_PROBE_DEFINE2(sdt, kernel, workqueue, wait__start,
     "struct workqueue *"/*wq*/,
     "struct work *"/*wk*/);
+SDT_PROBE_DEFINE2(sdt, kernel, workqueue, wait__self,
+    "struct workqueue *"/*wq*/,
+    "struct work *"/*wk*/);
+SDT_PROBE_DEFINE2(sdt, kernel, workqueue, wait__hit,
+    "struct workqueue *"/*wq*/,
+    "struct work *"/*wk*/);
 SDT_PROBE_DEFINE2(sdt, kernel, workqueue, wait__done,
     "struct workqueue *"/*wq*/,
     "struct work *"/*wk*/);
@@ -134,10 +141,6 @@ workqueue_runlist(struct workqueue *wq, 
 	work_impl_t *wk;
 	work_impl_t *next;
 
-	/*
-	 * note that "list" is not a complete SIMPLEQ.
-	 */
-
 	for (wk = SIMPLEQ_FIRST(list); wk != NULL; wk = next) {
 		next = SIMPLEQ_NEXT(wk, wk_entry);
 		SDT_PROBE4(sdt, kernel, workqueue, entry,
@@ -153,37 +156,44 @@ workqueue_worker(void *cookie)
 {
 	struct workqueue *wq = cookie;
 	struct workqueue_queue *q;
-	int s;
+	int s, fpu = wq->wq_flags & WQ_FPU;
 
 	/* find the workqueue of this kthread */
 	q = workqueue_queue_lookup(wq, curlwp->l_cpu);
 
-	if (wq->wq_flags & WQ_FPU)
+	if (fpu)
 		s = kthread_fpu_enter();
+	mutex_enter(&q->q_mutex);
 	for (;;) {
-		/*
-		 * we violate abstraction of SIMPLEQ.
-		 */
+		struct workqhead tmp;
+
+		SIMPLEQ_INIT(&tmp);
 
-		mutex_enter(&q->q_mutex);
 		while (SIMPLEQ_EMPTY(&q->q_queue_pending))
 			cv_wait(&q->q_cv, &q->q_mutex);
-		KASSERT(SIMPLEQ_EMPTY(&q->q_queue_running));
-		q->q_queue_running.sqh_first =
-		    q->q_queue_pending.sqh_first; /* XXX */
+		SIMPLEQ_CONCAT(&tmp, &q->q_queue_pending);
 		SIMPLEQ_INIT(&q->q_queue_pending);
+
+		/*
+		 * Mark the queue as actively running a batch of work
+		 * by setting the generation number odd.
+		 */
+		q->q_gen |= 1;
 		mutex_exit(&q->q_mutex);
 
-		workqueue_runlist(wq, &q->q_queue_running);
+		workqueue_runlist(wq, &tmp);
 
+		/*
+		 * Notify workqueue_wait that we have completed a batch
+		 * of work by incrementing the generation number.
+		 */
 		mutex_enter(&q->q_mutex);
-		KASSERT(!SIMPLEQ_EMPTY(&q->q_queue_running));
-		SIMPLEQ_INIT(&q->q_queue_running);
-		/* Wake up workqueue_wait */
+		KASSERTMSG(q->q_gen & 1, "q=%p gen=%"PRIu64, q, q->q_gen);
+		q->q_gen++;
 		cv_broadcast(&q->q_cv);
-		mutex_exit(&q->q_mutex);
 	}
-	if (wq->wq_flags & WQ_FPU)
+	mutex_exit(&q->q_mutex);
+	if (fpu)
 		kthread_fpu_exit(s);
 }
 
@@ -212,7 +222,7 @@ workqueue_initqueue(struct workqueue *wq
 	mutex_init(&q->q_mutex, MUTEX_DEFAULT, ipl);
 	cv_init(&q->q_cv, wq->wq_name);
 	SIMPLEQ_INIT(&q->q_queue_pending);
-	SIMPLEQ_INIT(&q->q_queue_running);
+	q->q_gen = 0;
 	ktf = ((wq->wq_flags & WQ_MPSAFE) != 0 ? KTHREAD_MPSAFE : 0);
 	if (wq->wq_prio < PRI_KERNEL)
 		ktf |= KTHREAD_TS;
@@ -325,29 +335,56 @@ workqueue_create(struct workqueue **wqp,
 }
 
 static bool
-workqueue_q_wait(struct workqueue_queue *q, work_impl_t *wk_target)
+workqueue_q_wait(struct workqueue *wq, struct workqueue_queue *q,
+    work_impl_t *wk_target)
 {
 	work_impl_t *wk;
 	bool found = false;
+	uint64_t gen;
 
 	mutex_enter(&q->q_mutex);
-	if (q->q_worker == curlwp)
+
+	/*
+	 * Avoid a deadlock scenario.  We can't guarantee that
+	 * wk_target has completed at this point, but we can't wait for
+	 * it either, so do nothing.
+	 *
+	 * XXX Are there use-cases that require this semantics?
+	 */
+	if (q->q_worker == curlwp) {
+		SDT_PROBE2(sdt, kernel, workqueue, wait__self,  wq, wk_target);
 		goto out;
+	}
+
+	/*
+	 * Wait until the target is no longer pending.  If we find it
+	 * on this queue, the caller can stop looking in other queues.
+	 * If we don't find it in this queue, however, we can't skip
+	 * waiting -- it may be hidden in the running queue which we
+	 * have no access to.
+	 */
     again:
 	SIMPLEQ_FOREACH(wk, &q->q_queue_pending, wk_entry) {
-		if (wk == wk_target)
-			goto found;
+		if (wk == wk_target) {
+			SDT_PROBE2(sdt, kernel, workqueue, wait__hit,  wq, wk);
+			found = true;
+			cv_wait(&q->q_cv, &q->q_mutex);
+			goto again;
+		}
 	}
-	SIMPLEQ_FOREACH(wk, &q->q_queue_running, wk_entry) {
-		if (wk == wk_target)
-			goto found;
-	}
-    found:
-	if (wk != NULL) {
-		found = true;
-		cv_wait(&q->q_cv, &q->q_mutex);
-		goto again;
+
+	/*
+	 * The target may be in the batch of work currently running,
+	 * but we can't touch that queue.  So if there's anything
+	 * running, wait until the generation changes.
+	 */
+	gen = q->q_gen;
+	if (gen & 1) {
+		do
+			cv_wait(&q->q_cv, &q->q_mutex);
+		while (gen == q->q_gen);
 	}
+
     out:
 	mutex_exit(&q->q_mutex);
 
@@ -374,13 +411,13 @@ workqueue_wait(struct workqueue *wq, str
 		CPU_INFO_ITERATOR cii;
 		for (CPU_INFO_FOREACH(cii, ci)) {
 			q = workqueue_queue_lookup(wq, ci);
-			found = workqueue_q_wait(q, (work_impl_t *)wk);
+			found = workqueue_q_wait(wq, q, (work_impl_t *)wk);
 			if (found)
 				break;
 		}
 	} else {
 		q = workqueue_queue_lookup(wq, NULL);
-		(void) workqueue_q_wait(q, (work_impl_t *)wk);
+		(void)workqueue_q_wait(wq, q, (work_impl_t *)wk);
 	}
 	SDT_PROBE2(sdt, kernel, workqueue, wait__done,  wq, wk);
 }

Index: src/tests/rump/kernspace/kernspace.h
diff -u src/tests/rump/kernspace/kernspace.h:1.8 src/tests/rump/kernspace/kernspace.h:1.8.10.1
--- src/tests/rump/kernspace/kernspace.h:1.8	Fri Dec 28 19:54:36 2018
+++ src/tests/rump/kernspace/kernspace.h	Mon Sep  4 16:57:56 2023
@@ -1,4 +1,4 @@
-/*	$NetBSD: kernspace.h,v 1.8 2018/12/28 19:54:36 thorpej Exp $	*/
+/*	$NetBSD: kernspace.h,v 1.8.10.1 2023/09/04 16:57:56 martin Exp $	*/
 
 /*-
  * Copyright (c) 2010, 2018 The NetBSD Foundation, Inc.
@@ -42,6 +42,7 @@ void rumptest_alloc(size_t);
 void rumptest_lockme(enum locktest);
 void rumptest_workqueue1(void);
 void rumptest_workqueue_wait(void);
+void rumptest_workqueue_wait_pause(void);
 
 void rumptest_sendsig(char *);
 void rumptest_localsig(int);

Index: src/tests/rump/kernspace/workqueue.c
diff -u src/tests/rump/kernspace/workqueue.c:1.6 src/tests/rump/kernspace/workqueue.c:1.6.16.1
--- src/tests/rump/kernspace/workqueue.c:1.6	Thu Dec 28 07:46:34 2017
+++ src/tests/rump/kernspace/workqueue.c	Mon Sep  4 16:57:56 2023
@@ -1,4 +1,4 @@
-/*	$NetBSD: workqueue.c,v 1.6 2017/12/28 07:46:34 ozaki-r Exp $	*/
+/*	$NetBSD: workqueue.c,v 1.6.16.1 2023/09/04 16:57:56 martin Exp $	*/
 
 /*-
  * Copyright (c) 2017 The NetBSD Foundation, Inc.
@@ -29,7 +29,7 @@
 
 #include <sys/cdefs.h>
 #if !defined(lint)
-__RCSID("$NetBSD: workqueue.c,v 1.6 2017/12/28 07:46:34 ozaki-r Exp $");
+__RCSID("$NetBSD: workqueue.c,v 1.6.16.1 2023/09/04 16:57:56 martin Exp $");
 #endif /* !lint */
 
 #include <sys/param.h>
@@ -48,13 +48,19 @@ struct test_softc {
 	struct workqueue *wq;
 	struct work wk;
 	int counter;
-};	
-	
+	bool pause;
+};
+
 static void
 rump_work1(struct work *wk, void *arg)
 {
 	struct test_softc *sc = arg;
 
+	memset(wk, 0x5a, sizeof(*wk));
+
+	if (sc->pause)
+		kpause("tstwk1", /*intr*/false, /*timo*/2, /*lock*/NULL);
+
 	mutex_enter(&sc->mtx);
 	++sc->counter;
 	cv_broadcast(&sc->cv);
@@ -137,3 +143,33 @@ rumptest_workqueue_wait(void)
 	destroy_sc(sc);
 #undef ITERATIONS
 }
+
+void
+rumptest_workqueue_wait_pause(void)
+{
+	struct test_softc *sc;
+	struct work dummy;
+
+	sc = create_sc();
+	sc->pause = true;
+
+#define ITERATIONS 1
+	for (size_t i = 0; i < ITERATIONS; ++i) {
+		struct work wk;
+
+		KASSERT(sc->counter == i);
+		workqueue_enqueue(sc->wq, &wk, NULL);
+		workqueue_enqueue(sc->wq, &sc->wk, NULL);
+		kpause("tstwk2", /*intr*/false, /*timo*/1, /*lock*/NULL);
+		workqueue_wait(sc->wq, &sc->wk);
+		KASSERT(sc->counter == (i + 1));
+	}
+
+	KASSERT(sc->counter == ITERATIONS);
+
+	/* Wait for a work that is not enqueued. Just return immediately. */
+	workqueue_wait(sc->wq, &dummy);
+
+	destroy_sc(sc);
+#undef ITERATIONS
+}

Index: src/tests/rump/rumpkern/Makefile
diff -u src/tests/rump/rumpkern/Makefile:1.19 src/tests/rump/rumpkern/Makefile:1.19.8.1
--- src/tests/rump/rumpkern/Makefile:1.19	Sun Mar  1 18:08:16 2020
+++ src/tests/rump/rumpkern/Makefile	Mon Sep  4 16:57:56 2023
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.19 2020/03/01 18:08:16 christos Exp $
+# $NetBSD: Makefile,v 1.19.8.1 2023/09/04 16:57:56 martin Exp $
 
 .include <bsd.own.mk>
 
@@ -24,8 +24,8 @@ LDADD.t_modlinkset+=	-lukfs -lrumpdev_di
 LDADD.t_modlinkset+=	-lrumpfs_cd9660 ${LIBRUMPBASE}
 LDADD+=			${LIBRUMPBASE}
 
-KERNSPACE != cd ${.CURDIR}/../kernspace && ${PRINTOBJDIR}
-LDADD+=	-L${KERNSPACE} -lkernspace -lrump
+PROGDPLIBS+=	kernspace	${.CURDIR}/../kernspace
+LDADD+=		-lrump
 
 WARNS=	4
 

Index: src/tests/rump/rumpkern/t_workqueue.c
diff -u src/tests/rump/rumpkern/t_workqueue.c:1.2 src/tests/rump/rumpkern/t_workqueue.c:1.2.16.1
--- src/tests/rump/rumpkern/t_workqueue.c:1.2	Thu Dec 28 07:10:26 2017
+++ src/tests/rump/rumpkern/t_workqueue.c	Mon Sep  4 16:57:56 2023
@@ -1,4 +1,4 @@
-/*	$NetBSD: t_workqueue.c,v 1.2 2017/12/28 07:10:26 ozaki-r Exp $	*/
+/*	$NetBSD: t_workqueue.c,v 1.2.16.1 2023/09/04 16:57:56 martin Exp $	*/
 
 /*-
  * Copyright (c) 2017 The NetBSD Foundation, Inc.
@@ -72,10 +72,36 @@ ATF_TC_BODY(workqueue_wait, tc)
 	rump_unschedule();
 }
 
+static void
+sigsegv(int signo)
+{
+	atf_tc_fail("SIGSEGV");
+}
+
+ATF_TC(workqueue_wait_pause);
+ATF_TC_HEAD(workqueue_wait_pause, tc)
+{
+
+	atf_tc_set_md_var(tc, "descr", "Checks workqueue_wait with pause");
+}
+
+ATF_TC_BODY(workqueue_wait_pause, tc)
+{
+
+	REQUIRE_LIBC(signal(SIGSEGV, &sigsegv), SIG_ERR);
+
+	rump_init();
+
+	rump_schedule();
+	rumptest_workqueue_wait_pause(); /* panics or SIGSEGVs if fails */
+	rump_unschedule();
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 	ATF_TP_ADD_TC(tp, workqueue1);
 	ATF_TP_ADD_TC(tp, workqueue_wait);
+	ATF_TP_ADD_TC(tp, workqueue_wait_pause);
 
 	return atf_no_error();
 }

Reply via email to