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();
}