--- a/sys/kern/kern_condvar.c 2026-06-29 12:51:19.000000000 +0000 +++ b/sys/kern/kern_condvar.c 2026-10-26 03:49:12.313898647 +0000 @@ -2,6 +2,8 @@ #include #include #include +#include + void cv_init(struct cv *c, const char *desc) @@ -14,6 +16,13 @@ void cv_destroy(struct cv *c) { +#ifdef INVARIANTS + if (c->cv_waiters && tsleep_wchan_busy(c)) { + kprintf("cv_destroy: cv %p (%s) destroyed with waiters\n", + c, c->cv_desc); + KKASSERT(0 == 1); + } +#endif spin_uninit(&c->cv_lock); } --- a/sys/kern/kern_synch.c 2026-06-29 12:51:19.000000000 +0000 +++ b/sys/kern/kern_synch.c 2026-10-26 03:48:50.586174551 +0000 @@ -447,6 +447,32 @@ crit_exit_quick(td); } +#ifdef INVARIANTS +/* + * TRUE if any thread is currently enqueued (td_wchan == wchan) in any + * CPU's tsleep hash. Diagnostic helper for cv_destroy() (kern_condvar.c). + */ +int +tsleep_wchan_busy(const void *wchan) +{ + globaldata_t gd; + struct tslpque *qp; + struct thread *td; + int cpu; + + for (cpu = 0; cpu < ncpus; cpu++) { + gd = globaldata_find(cpu); + qp = &gd->gd_tsleep_hash[ + TCHASHSHIFT(LOOKUP(wchan))]; + TAILQ_FOREACH(td, &qp->queue, td_sleepq) { + if (td->td_wchan == wchan) + return (1); + } + } + return (0); +} +#endif + void tsleep_interlock(const volatile void *ident, int flags) { --- a/sys/sys/systm.h 2026-06-29 12:51:19.000000000 +0000 +++ b/sys/sys/systm.h 2026-10-26 03:49:12.277899104 +0000 @@ -418,6 +418,9 @@ int zsleep(const volatile void *, struct lwkt_serialize *, int, const char *, int) __nonnull(1); void tsleep_interlock(const volatile void *, int) __nonnull(1); +#ifdef INVARIANTS +int tsleep_wchan_busy(const void *wchan); +#endif void tsleep_remove (struct thread *); int lwkt_sleep (const char *, int); void tstop (void);