#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kern_threadpool.c,v 1.24 2026/01/04 01:54:38 riastradh Exp $");
#include <sys/types.h>
#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/condvar.h>
#include <sys/cpu.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/kthread.h>
#include <sys/mutex.h>
#include <sys/once.h>
#include <sys/percpu.h>
#include <sys/pool.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/sdt.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/threadpool.h>
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, get,
"pri_t");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, get__create,
"pri_t");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, get__race,
"pri_t");
SDT_PROBE_DEFINE2(sdt, kernel, threadpool, put,
"struct threadpool *", "pri_t");
SDT_PROBE_DEFINE2(sdt, kernel, threadpool, put__destroy,
"struct threadpool *", "pri_t");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, percpu__get,
"pri_t");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, percpu__get__create,
"pri_t");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, percpu__get__race,
"pri_t");
SDT_PROBE_DEFINE2(sdt, kernel, threadpool, percpu__put,
"struct threadpool *", "pri_t");
SDT_PROBE_DEFINE2(sdt, kernel, threadpool, percpu__put__destroy,
"struct threadpool *", "pri_t");
SDT_PROBE_DEFINE2(sdt, kernel, threadpool, create,
"struct cpu_info *", "pri_t");
SDT_PROBE_DEFINE3(sdt, kernel, threadpool, create__success,
"struct cpu_info *", "pri_t", "struct threadpool *");
SDT_PROBE_DEFINE3(sdt, kernel, threadpool, create__failure,
"struct cpu_info *", "pri_t", "int");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, destroy,
"struct threadpool *");
SDT_PROBE_DEFINE2(sdt, kernel, threadpool, destroy__wait,
"struct threadpool *", "uint64_t");
SDT_PROBE_DEFINE2(sdt, kernel, threadpool, schedule__job,
"struct threadpool *", "struct threadpool_job *");
SDT_PROBE_DEFINE2(sdt, kernel, threadpool, schedule__job__running,
"struct threadpool *", "struct threadpool_job *");
SDT_PROBE_DEFINE2(sdt, kernel, threadpool, schedule__job__dispatcher,
"struct threadpool *", "struct threadpool_job *");
SDT_PROBE_DEFINE3(sdt, kernel, threadpool, schedule__job__thread,
"struct threadpool *",
"struct threadpool_job *",
"struct lwp *");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, dispatcher__start,
"struct threadpool *");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, dispatcher__dying,
"struct threadpool *");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, dispatcher__spawn,
"struct threadpool *");
SDT_PROBE_DEFINE2(sdt, kernel, threadpool, dispatcher__race,
"struct threadpool *",
"struct threadpool_job *");
SDT_PROBE_DEFINE3(sdt, kernel, threadpool, dispatcher__assign,
"struct threadpool *",
"struct threadpool_job *",
"struct lwp *");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, dispatcher__exit,
"struct threadpool *");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, thread__start,
"struct threadpool *");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, thread__dying,
"struct threadpool *");
SDT_PROBE_DEFINE2(sdt, kernel, threadpool, thread__job,
"struct threadpool *", "struct threadpool_job *");
SDT_PROBE_DEFINE1(sdt, kernel, threadpool, thread__exit,
"struct threadpool *");
TAILQ_HEAD(job_head, threadpool_job);
TAILQ_HEAD(thread_head, threadpool_thread);
struct threadpool_thread {
struct lwp *tpt_lwp;
char *tpt_lwp_savedname;
struct threadpool *tpt_pool;
struct threadpool_job *tpt_job;
kcondvar_t tpt_cv;
TAILQ_ENTRY(threadpool_thread) tpt_entry;
};
struct threadpool {
kmutex_t tp_lock;
struct threadpool_thread tp_dispatcher;
struct job_head tp_jobs;
struct thread_head tp_idle_threads;
uint64_t tp_refcnt;
int tp_flags;
#define THREADPOOL_DYING 0x01
struct cpu_info *tp_cpu;
pri_t tp_pri;
};
static void threadpool_hold(struct threadpool *);
static void threadpool_rele(struct threadpool *);
static int threadpool_percpu_create(struct threadpool_percpu **, pri_t);
static void threadpool_percpu_destroy(struct threadpool_percpu *);
static void threadpool_percpu_init(void *, void *, struct cpu_info *);
static void threadpool_percpu_ok(void *, void *, struct cpu_info *);
static void threadpool_percpu_fini(void *, void *, struct cpu_info *);
static threadpool_job_fn_t threadpool_job_dead;
static void threadpool_job_hold(struct threadpool_job *);
static void threadpool_job_rele(struct threadpool_job *);
static void threadpool_dispatcher_thread(void *) __dead;
static void threadpool_thread(void *) __dead;
static pool_cache_t threadpool_thread_pc __read_mostly;
static kmutex_t threadpools_lock __cacheline_aligned;
static int threadpool_idle_time_ms = 30 * 1000;
struct threadpool_unbound {
struct threadpool tpu_pool;
LIST_ENTRY(threadpool_unbound) tpu_link;
uint64_t tpu_refcnt;
};
static LIST_HEAD(, threadpool_unbound) unbound_threadpools;
static struct threadpool_unbound *
threadpool_lookup_unbound(pri_t pri)
{
struct threadpool_unbound *tpu;
LIST_FOREACH(tpu, &unbound_threadpools, tpu_link) {
if (tpu->tpu_pool.tp_pri == pri)
return tpu;
}
return NULL;
}
static void
threadpool_insert_unbound(struct threadpool_unbound *tpu)
{
KASSERT(threadpool_lookup_unbound(tpu->tpu_pool.tp_pri) == NULL);
LIST_INSERT_HEAD(&unbound_threadpools, tpu, tpu_link);
}
static void
threadpool_remove_unbound(struct threadpool_unbound *tpu)
{
KASSERT(threadpool_lookup_unbound(tpu->tpu_pool.tp_pri) == tpu);
LIST_REMOVE(tpu, tpu_link);
}
struct threadpool_percpu {
percpu_t * tpp_percpu;
pri_t tpp_pri;
LIST_ENTRY(threadpool_percpu) tpp_link;
uint64_t tpp_refcnt;
};
static LIST_HEAD(, threadpool_percpu) percpu_threadpools;
static struct threadpool_percpu *
threadpool_lookup_percpu(pri_t pri)
{
struct threadpool_percpu *tpp;
LIST_FOREACH(tpp, &percpu_threadpools, tpp_link) {
if (tpp->tpp_pri == pri)
return tpp;
}
return NULL;
}
static void
threadpool_insert_percpu(struct threadpool_percpu *tpp)
{
KASSERT(threadpool_lookup_percpu(tpp->tpp_pri) == NULL);
LIST_INSERT_HEAD(&percpu_threadpools, tpp, tpp_link);
}
static void
threadpool_remove_percpu(struct threadpool_percpu *tpp)
{
KASSERT(threadpool_lookup_percpu(tpp->tpp_pri) == tpp);
LIST_REMOVE(tpp, tpp_link);
}
static int
sysctl_kern_threadpool_idle_ms(SYSCTLFN_ARGS)
{
struct sysctlnode node;
int val, error;
node = *rnode;
val = threadpool_idle_time_ms;
node.sysctl_data = &val;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error == 0 && newp != NULL) {
if (val < 1)
error = SET_ERROR(EINVAL);
else
threadpool_idle_time_ms = val;
}
return error;
}
SYSCTL_SETUP_PROTO(sysctl_threadpool_setup);
SYSCTL_SETUP(sysctl_threadpool_setup,
"sysctl kern.threadpool subtree setup")
{
const struct sysctlnode *rnode, *cnode;
int error __diagused;
error = sysctl_createv(clog, 0, NULL, &rnode,
CTLFLAG_PERMANENT,
CTLTYPE_NODE, "threadpool",
SYSCTL_DESCR("threadpool subsystem options"),
NULL, 0, NULL, 0,
CTL_KERN, CTL_CREATE, CTL_EOL);
KASSERT(error == 0);
error = sysctl_createv(clog, 0, &rnode, &cnode,
CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
CTLTYPE_INT, "idle_ms",
SYSCTL_DESCR("idle thread timeout in ms"),
sysctl_kern_threadpool_idle_ms, 0, NULL, 0,
CTL_CREATE, CTL_EOL);
KASSERT(error == 0);
}
void
threadpools_init(void)
{
threadpool_thread_pc =
pool_cache_init(sizeof(struct threadpool_thread), 0, 0, 0,
"thplthrd", NULL, IPL_NONE, NULL, NULL, NULL);
LIST_INIT(&unbound_threadpools);
LIST_INIT(&percpu_threadpools);
mutex_init(&threadpools_lock, MUTEX_DEFAULT, IPL_NONE);
}
static void
threadnamesuffix(char *buf, size_t buflen, struct cpu_info *ci, int pri)
{
buf[0] = '\0';
if (ci)
snprintf(buf + strlen(buf), buflen - strlen(buf), "/%d",
cpu_index(ci));
if (pri != PRI_NONE)
snprintf(buf + strlen(buf), buflen - strlen(buf), "@%d", pri);
}
static bool
threadpool_pri_is_valid(pri_t pri)
{
return (pri == PRI_NONE || (pri >= PRI_USER && pri < PRI_COUNT));
}
static int
threadpool_create(struct threadpool *const pool, struct cpu_info *ci,
pri_t pri)
{
struct lwp *lwp;
char suffix[16];
int ktflags;
int error;
KASSERT(threadpool_pri_is_valid(pri));
SDT_PROBE2(sdt, kernel, threadpool, create, ci, pri);
mutex_init(&pool->tp_lock, MUTEX_DEFAULT, IPL_VM);
TAILQ_INIT(&pool->tp_jobs);
TAILQ_INIT(&pool->tp_idle_threads);
pool->tp_refcnt = 1;
pool->tp_flags = 0;
pool->tp_cpu = ci;
pool->tp_pri = pri;
pool->tp_dispatcher.tpt_lwp = NULL;
pool->tp_dispatcher.tpt_pool = pool;
pool->tp_dispatcher.tpt_job = NULL;
cv_init(&pool->tp_dispatcher.tpt_cv, "pooldisp");
ktflags = 0;
ktflags |= KTHREAD_MPSAFE;
if (pri < PRI_KERNEL)
ktflags |= KTHREAD_TS;
threadnamesuffix(suffix, sizeof(suffix), ci, pri);
error = kthread_create(pri, ktflags, ci, &threadpool_dispatcher_thread,
&pool->tp_dispatcher, &lwp, "pooldisp%s", suffix);
if (error)
goto fail0;
mutex_spin_enter(&pool->tp_lock);
pool->tp_dispatcher.tpt_lwp = lwp;
cv_broadcast(&pool->tp_dispatcher.tpt_cv);
mutex_spin_exit(&pool->tp_lock);
SDT_PROBE3(sdt, kernel, threadpool, create__success, ci, pri, pool);
return 0;
fail0: KASSERT(error);
KASSERT(pool->tp_dispatcher.tpt_job == NULL);
KASSERT(pool->tp_dispatcher.tpt_pool == pool);
KASSERT(pool->tp_flags == 0);
KASSERT(pool->tp_refcnt == 0);
KASSERT(TAILQ_EMPTY(&pool->tp_idle_threads));
KASSERT(TAILQ_EMPTY(&pool->tp_jobs));
KASSERT(!cv_has_waiters(&pool->tp_dispatcher.tpt_cv));
cv_destroy(&pool->tp_dispatcher.tpt_cv);
mutex_destroy(&pool->tp_lock);
SDT_PROBE3(sdt, kernel, threadpool, create__failure, ci, pri, error);
return error;
}
static void
threadpool_destroy(struct threadpool *pool)
{
struct threadpool_thread *thread;
SDT_PROBE1(sdt, kernel, threadpool, destroy, pool);
mutex_spin_enter(&pool->tp_lock);
KASSERT(TAILQ_EMPTY(&pool->tp_jobs));
pool->tp_flags |= THREADPOOL_DYING;
cv_broadcast(&pool->tp_dispatcher.tpt_cv);
TAILQ_FOREACH(thread, &pool->tp_idle_threads, tpt_entry)
cv_broadcast(&thread->tpt_cv);
while (0 < pool->tp_refcnt) {
SDT_PROBE2(sdt, kernel, threadpool, destroy__wait,
pool, pool->tp_refcnt);
cv_wait(&pool->tp_dispatcher.tpt_cv, &pool->tp_lock);
}
mutex_spin_exit(&pool->tp_lock);
KASSERT(pool->tp_dispatcher.tpt_job == NULL);
KASSERT(pool->tp_dispatcher.tpt_pool == pool);
KASSERT(pool->tp_flags == THREADPOOL_DYING);
KASSERT(pool->tp_refcnt == 0);
KASSERT(TAILQ_EMPTY(&pool->tp_idle_threads));
KASSERT(TAILQ_EMPTY(&pool->tp_jobs));
KASSERT(!cv_has_waiters(&pool->tp_dispatcher.tpt_cv));
cv_destroy(&pool->tp_dispatcher.tpt_cv);
mutex_destroy(&pool->tp_lock);
}
static void
threadpool_hold(struct threadpool *pool)
{
KASSERT(mutex_owned(&pool->tp_lock));
pool->tp_refcnt++;
KASSERT(pool->tp_refcnt != 0);
}
static void
threadpool_rele(struct threadpool *pool)
{
KASSERT(mutex_owned(&pool->tp_lock));
KASSERT(0 < pool->tp_refcnt);
if (--pool->tp_refcnt == 0)
cv_broadcast(&pool->tp_dispatcher.tpt_cv);
}
int
threadpool_get(struct threadpool **poolp, pri_t pri)
{
struct threadpool_unbound *tpu, *tmp = NULL;
int error;
ASSERT_SLEEPABLE();
SDT_PROBE1(sdt, kernel, threadpool, get, pri);
if (! threadpool_pri_is_valid(pri))
return SET_ERROR(EINVAL);
mutex_enter(&threadpools_lock);
tpu = threadpool_lookup_unbound(pri);
if (tpu == NULL) {
mutex_exit(&threadpools_lock);
SDT_PROBE1(sdt, kernel, threadpool, get__create, pri);
tmp = kmem_zalloc(sizeof(*tmp), KM_SLEEP);
error = threadpool_create(&tmp->tpu_pool, NULL, pri);
if (error) {
kmem_free(tmp, sizeof(*tmp));
return error;
}
mutex_enter(&threadpools_lock);
tpu = threadpool_lookup_unbound(pri);
if (tpu == NULL) {
tpu = tmp;
tmp = NULL;
threadpool_insert_unbound(tpu);
} else {
SDT_PROBE1(sdt, kernel, threadpool, get__race, pri);
}
}
KASSERT(tpu != NULL);
tpu->tpu_refcnt++;
KASSERT(tpu->tpu_refcnt != 0);
mutex_exit(&threadpools_lock);
if (tmp != NULL) {
threadpool_destroy(&tmp->tpu_pool);
kmem_free(tmp, sizeof(*tmp));
}
KASSERT(tpu != NULL);
*poolp = &tpu->tpu_pool;
return 0;
}
void
threadpool_put(struct threadpool *pool, pri_t pri)
{
struct threadpool_unbound *tpu =
container_of(pool, struct threadpool_unbound, tpu_pool);
ASSERT_SLEEPABLE();
KASSERT(threadpool_pri_is_valid(pri));
SDT_PROBE2(sdt, kernel, threadpool, put, pool, pri);
mutex_enter(&threadpools_lock);
KASSERT(tpu == threadpool_lookup_unbound(pri));
KASSERT(0 < tpu->tpu_refcnt);
if (--tpu->tpu_refcnt == 0) {
SDT_PROBE2(sdt, kernel, threadpool, put__destroy, pool, pri);
threadpool_remove_unbound(tpu);
} else {
tpu = NULL;
}
mutex_exit(&threadpools_lock);
if (tpu) {
threadpool_destroy(&tpu->tpu_pool);
kmem_free(tpu, sizeof(*tpu));
}
}
int
threadpool_percpu_get(struct threadpool_percpu **pool_percpup, pri_t pri)
{
struct threadpool_percpu *pool_percpu, *tmp = NULL;
int error;
ASSERT_SLEEPABLE();
SDT_PROBE1(sdt, kernel, threadpool, percpu__get, pri);
if (! threadpool_pri_is_valid(pri))
return SET_ERROR(EINVAL);
mutex_enter(&threadpools_lock);
pool_percpu = threadpool_lookup_percpu(pri);
if (pool_percpu == NULL) {
mutex_exit(&threadpools_lock);
SDT_PROBE1(sdt, kernel, threadpool, percpu__get__create, pri);
error = threadpool_percpu_create(&tmp, pri);
if (error)
return error;
KASSERT(tmp != NULL);
mutex_enter(&threadpools_lock);
pool_percpu = threadpool_lookup_percpu(pri);
if (pool_percpu == NULL) {
pool_percpu = tmp;
tmp = NULL;
threadpool_insert_percpu(pool_percpu);
} else {
SDT_PROBE1(sdt, kernel, threadpool, percpu__get__race,
pri);
}
}
KASSERT(pool_percpu != NULL);
pool_percpu->tpp_refcnt++;
KASSERT(pool_percpu->tpp_refcnt != 0);
mutex_exit(&threadpools_lock);
if (tmp != NULL)
threadpool_percpu_destroy(tmp);
KASSERT(pool_percpu != NULL);
*pool_percpup = pool_percpu;
return 0;
}
void
threadpool_percpu_put(struct threadpool_percpu *pool_percpu, pri_t pri)
{
ASSERT_SLEEPABLE();
KASSERT(threadpool_pri_is_valid(pri));
SDT_PROBE2(sdt, kernel, threadpool, percpu__put, pool_percpu, pri);
mutex_enter(&threadpools_lock);
KASSERT(pool_percpu == threadpool_lookup_percpu(pri));
KASSERT(0 < pool_percpu->tpp_refcnt);
if (--pool_percpu->tpp_refcnt == 0) {
SDT_PROBE2(sdt, kernel, threadpool, percpu__put__destroy,
pool_percpu, pri);
threadpool_remove_percpu(pool_percpu);
} else {
pool_percpu = NULL;
}
mutex_exit(&threadpools_lock);
if (pool_percpu)
threadpool_percpu_destroy(pool_percpu);
}
struct threadpool *
threadpool_percpu_ref(struct threadpool_percpu *pool_percpu)
{
struct threadpool **poolp, *pool;
poolp = percpu_getref(pool_percpu->tpp_percpu);
pool = *poolp;
percpu_putref(pool_percpu->tpp_percpu);
return pool;
}
struct threadpool *
threadpool_percpu_ref_remote(struct threadpool_percpu *pool_percpu,
struct cpu_info *ci)
{
struct threadpool **poolp, *pool;
kpreempt_disable();
poolp = percpu_getptr_remote(pool_percpu->tpp_percpu, ci);
pool = *poolp;
kpreempt_enable();
return pool;
}
static int
threadpool_percpu_create(struct threadpool_percpu **pool_percpup, pri_t pri)
{
struct threadpool_percpu *pool_percpu;
bool ok = true;
pool_percpu = kmem_zalloc(sizeof(*pool_percpu), KM_SLEEP);
pool_percpu->tpp_pri = pri;
pool_percpu->tpp_percpu = percpu_create(sizeof(struct threadpool *),
threadpool_percpu_init, threadpool_percpu_fini,
(void *)(intptr_t)pri);
percpu_foreach(pool_percpu->tpp_percpu, &threadpool_percpu_ok, &ok);
if (!ok)
goto fail;
*pool_percpup = (struct threadpool_percpu *)pool_percpu;
return 0;
fail: percpu_free(pool_percpu->tpp_percpu, sizeof(struct threadpool *));
kmem_free(pool_percpu, sizeof(*pool_percpu));
return SET_ERROR(ENOMEM);
}
static void
threadpool_percpu_destroy(struct threadpool_percpu *pool_percpu)
{
percpu_free(pool_percpu->tpp_percpu, sizeof(struct threadpool *));
kmem_free(pool_percpu, sizeof(*pool_percpu));
}
static void
threadpool_percpu_init(void *vpoolp, void *vpri, struct cpu_info *ci)
{
struct threadpool **const poolp = vpoolp;
pri_t pri = (intptr_t)(void *)vpri;
int error;
*poolp = kmem_zalloc(sizeof(**poolp), KM_SLEEP);
error = threadpool_create(*poolp, ci, pri);
if (error) {
KASSERT(error == ENOMEM);
kmem_free(*poolp, sizeof(**poolp));
*poolp = NULL;
}
}
static void
threadpool_percpu_ok(void *vpoolp, void *vokp, struct cpu_info *ci)
{
struct threadpool **const poolp = vpoolp;
bool *okp = vokp;
if (*poolp == NULL)
atomic_store_relaxed(okp, false);
}
static void
threadpool_percpu_fini(void *vpoolp, void *vprip, struct cpu_info *ci)
{
struct threadpool **const poolp = vpoolp;
if (*poolp == NULL)
return;
threadpool_destroy(*poolp);
kmem_free(*poolp, sizeof(**poolp));
}
void __printflike(4,5)
threadpool_job_init(struct threadpool_job *job, threadpool_job_fn_t fn,
kmutex_t *lock, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
(void)vsnprintf(job->job_name, sizeof(job->job_name), fmt, ap);
va_end(ap);
job->job_lock = lock;
job->job_thread = NULL;
job->job_refcnt = 0;
cv_init(&job->job_cv, job->job_name);
job->job_fn = fn;
}
static void
threadpool_job_dead(struct threadpool_job *job)
{
panic("threadpool job %p ran after destruction", job);
}
void
threadpool_job_destroy(struct threadpool_job *job)
{
ASSERT_SLEEPABLE();
KASSERTMSG((job->job_thread == NULL), "job %p still running", job);
mutex_enter(job->job_lock);
while (0 < atomic_load_relaxed(&job->job_refcnt))
cv_wait(&job->job_cv, job->job_lock);
mutex_exit(job->job_lock);
job->job_lock = NULL;
KASSERT(job->job_thread == NULL);
KASSERT(job->job_refcnt == 0);
KASSERT(!cv_has_waiters(&job->job_cv));
cv_destroy(&job->job_cv);
job->job_fn = threadpool_job_dead;
(void)strlcpy(job->job_name, "deadjob", sizeof(job->job_name));
}
static void
threadpool_job_hold(struct threadpool_job *job)
{
unsigned int refcnt __diagused;
refcnt = atomic_inc_uint_nv(&job->job_refcnt);
KASSERT(refcnt != 0);
}
static void
threadpool_job_rele(struct threadpool_job *job)
{
unsigned int refcnt;
KASSERT(mutex_owned(job->job_lock));
refcnt = atomic_dec_uint_nv(&job->job_refcnt);
KASSERT(refcnt != UINT_MAX);
if (refcnt == 0)
cv_broadcast(&job->job_cv);
}
void
threadpool_job_done(struct threadpool_job *job)
{
KASSERT(mutex_owned(job->job_lock));
KASSERT(job->job_thread != NULL);
KASSERT(job->job_thread->tpt_lwp == curlwp);
lwp_lock(curlwp);
curlwp->l_name = job->job_thread->tpt_lwp_savedname;
lwp_unlock(curlwp);
KASSERT(0 < atomic_load_relaxed(&job->job_refcnt));
unsigned int refcnt __diagused = atomic_dec_uint_nv(&job->job_refcnt);
KASSERT(refcnt != UINT_MAX);
cv_broadcast(&job->job_cv);
job->job_thread = NULL;
}
void
threadpool_schedule_job(struct threadpool *pool, struct threadpool_job *job)
{
KASSERT(mutex_owned(job->job_lock));
SDT_PROBE2(sdt, kernel, threadpool, schedule__job, pool, job);
if (__predict_true(job->job_thread != NULL)) {
SDT_PROBE2(sdt, kernel, threadpool, schedule__job__running,
pool, job);
return;
}
threadpool_job_hold(job);
mutex_spin_enter(&pool->tp_lock);
if (__predict_false(TAILQ_EMPTY(&pool->tp_idle_threads))) {
SDT_PROBE2(sdt, kernel, threadpool, schedule__job__dispatcher,
pool, job);
job->job_thread = &pool->tp_dispatcher;
TAILQ_INSERT_TAIL(&pool->tp_jobs, job, job_entry);
} else {
job->job_thread = TAILQ_FIRST(&pool->tp_idle_threads);
SDT_PROBE3(sdt, kernel, threadpool, schedule__job__thread,
pool, job, job->job_thread->tpt_lwp);
TAILQ_REMOVE(&pool->tp_idle_threads, job->job_thread,
tpt_entry);
job->job_thread->tpt_job = job;
}
KASSERT(job->job_thread != NULL);
cv_broadcast(&job->job_thread->tpt_cv);
mutex_spin_exit(&pool->tp_lock);
}
bool
threadpool_cancel_job_async(struct threadpool *pool, struct threadpool_job *job)
{
KASSERT(mutex_owned(job->job_lock));
if (job->job_thread == NULL) {
return true;
} else if (job->job_thread == &pool->tp_dispatcher) {
job->job_thread = NULL;
mutex_spin_enter(&pool->tp_lock);
TAILQ_REMOVE(&pool->tp_jobs, job, job_entry);
mutex_spin_exit(&pool->tp_lock);
threadpool_job_rele(job);
return true;
} else {
return false;
}
}
void
threadpool_cancel_job(struct threadpool *pool, struct threadpool_job *job)
{
KASSERT(mutex_owned(job->job_lock));
if (threadpool_cancel_job_async(pool, job))
return;
while (job->job_thread != NULL)
cv_wait(&job->job_cv, job->job_lock);
}
static void __dead
threadpool_dispatcher_thread(void *arg)
{
struct threadpool_thread *const dispatcher = arg;
struct threadpool *const pool = dispatcher->tpt_pool;
struct lwp *lwp = NULL;
int ktflags;
char suffix[16];
int error;
KASSERT((pool->tp_cpu == NULL) || (pool->tp_cpu == curcpu()));
KASSERT((pool->tp_cpu == NULL) || (curlwp->l_pflag & LP_BOUND));
mutex_spin_enter(&pool->tp_lock);
while (dispatcher->tpt_lwp == NULL)
cv_wait(&dispatcher->tpt_cv, &pool->tp_lock);
SDT_PROBE1(sdt, kernel, threadpool, dispatcher__start, pool);
for (;;) {
while (TAILQ_EMPTY(&pool->tp_jobs)) {
if (ISSET(pool->tp_flags, THREADPOOL_DYING)) {
SDT_PROBE1(sdt, kernel, threadpool,
dispatcher__dying, pool);
break;
}
cv_wait(&dispatcher->tpt_cv, &pool->tp_lock);
}
if (__predict_false(TAILQ_EMPTY(&pool->tp_jobs)))
break;
if (TAILQ_EMPTY(&pool->tp_idle_threads)) {
SDT_PROBE1(sdt, kernel, threadpool, dispatcher__spawn,
pool);
threadpool_hold(pool);
mutex_spin_exit(&pool->tp_lock);
struct threadpool_thread *const thread =
pool_cache_get(threadpool_thread_pc, PR_WAITOK);
thread->tpt_lwp = NULL;
thread->tpt_pool = pool;
thread->tpt_job = NULL;
cv_init(&thread->tpt_cv, "pooljob");
ktflags = 0;
ktflags |= KTHREAD_MPSAFE;
if (pool->tp_pri < PRI_KERNEL)
ktflags |= KTHREAD_TS;
threadnamesuffix(suffix, sizeof(suffix), pool->tp_cpu,
pool->tp_pri);
error = kthread_create(pool->tp_pri, ktflags,
pool->tp_cpu, &threadpool_thread, thread, &lwp,
"poolthread%s", suffix);
mutex_spin_enter(&pool->tp_lock);
if (error) {
pool_cache_put(threadpool_thread_pc, thread);
threadpool_rele(pool);
(void)kpause("thrdplcr", false, hz,
&pool->tp_lock);
continue;
}
KASSERT(lwp != NULL);
TAILQ_INSERT_TAIL(&pool->tp_idle_threads, thread,
tpt_entry);
thread->tpt_lwp = lwp;
lwp = NULL;
cv_broadcast(&thread->tpt_cv);
continue;
}
struct threadpool_job *const job = TAILQ_FIRST(&pool->tp_jobs);
threadpool_job_hold(job);
mutex_spin_exit(&pool->tp_lock);
mutex_enter(job->job_lock);
if (__predict_true(job->job_thread == dispatcher)) {
mutex_spin_enter(&pool->tp_lock);
TAILQ_REMOVE(&pool->tp_jobs, job, job_entry);
if (__predict_false(
TAILQ_EMPTY(&pool->tp_idle_threads))) {
SDT_PROBE2(sdt, kernel, threadpool,
dispatcher__race, pool, job);
TAILQ_INSERT_HEAD(&pool->tp_jobs, job,
job_entry);
} else {
struct threadpool_thread *const thread =
TAILQ_FIRST(&pool->tp_idle_threads);
SDT_PROBE2(sdt, kernel, threadpool,
dispatcher__assign, job, thread->tpt_lwp);
KASSERT(thread->tpt_job == NULL);
TAILQ_REMOVE(&pool->tp_idle_threads, thread,
tpt_entry);
thread->tpt_job = job;
job->job_thread = thread;
cv_broadcast(&thread->tpt_cv);
}
mutex_spin_exit(&pool->tp_lock);
}
threadpool_job_rele(job);
mutex_exit(job->job_lock);
mutex_spin_enter(&pool->tp_lock);
}
threadpool_rele(pool);
mutex_spin_exit(&pool->tp_lock);
SDT_PROBE1(sdt, kernel, threadpool, dispatcher__exit, pool);
kthread_exit(0);
}
static void __dead
threadpool_thread(void *arg)
{
struct threadpool_thread *const thread = arg;
struct threadpool *const pool = thread->tpt_pool;
KASSERT((pool->tp_cpu == NULL) || (pool->tp_cpu == curcpu()));
KASSERT((pool->tp_cpu == NULL) || (curlwp->l_pflag & LP_BOUND));
mutex_spin_enter(&pool->tp_lock);
while (thread->tpt_lwp == NULL)
cv_wait(&thread->tpt_cv, &pool->tp_lock);
SDT_PROBE1(sdt, kernel, threadpool, thread__start, pool);
KASSERT(thread->tpt_lwp == curlwp);
for (;;) {
while (thread->tpt_job == NULL) {
if (ISSET(pool->tp_flags, THREADPOOL_DYING)) {
SDT_PROBE1(sdt, kernel, threadpool,
thread__dying, pool);
break;
}
if (cv_timedwait(&thread->tpt_cv, &pool->tp_lock,
mstohz(threadpool_idle_time_ms)))
break;
}
if (__predict_false(thread->tpt_job == NULL)) {
TAILQ_REMOVE(&pool->tp_idle_threads, thread,
tpt_entry);
break;
}
struct threadpool_job *const job = thread->tpt_job;
KASSERT(job != NULL);
lwp_lock(curlwp);
char *const lwp_name __diagused = curlwp->l_name;
thread->tpt_lwp_savedname = curlwp->l_name;
curlwp->l_name = job->job_name;
lwp_unlock(curlwp);
mutex_spin_exit(&pool->tp_lock);
SDT_PROBE2(sdt, kernel, threadpool, thread__job, pool, job);
(*job->job_fn)(job);
KASSERTMSG((curlwp->l_name == lwp_name),
"someone forgot to call threadpool_job_done()!");
mutex_spin_enter(&pool->tp_lock);
KASSERT(thread->tpt_job == job);
thread->tpt_job = NULL;
TAILQ_INSERT_TAIL(&pool->tp_idle_threads, thread, tpt_entry);
}
threadpool_rele(pool);
mutex_spin_exit(&pool->tp_lock);
SDT_PROBE1(sdt, kernel, threadpool, thread__exit, pool);
KASSERT(!cv_has_waiters(&thread->tpt_cv));
cv_destroy(&thread->tpt_cv);
pool_cache_put(threadpool_thread_pc, thread);
kthread_exit(0);
}