/* $NetBSD: threadpool.c,v 1.5 2019/01/04 05:35:24 thorpej Exp $ */ /*- * Copyright (c) 2018 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Jason R. Thorpe. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #if !defined(lint) __RCSID("$NetBSD: threadpool.c,v 1.5 2019/01/04 05:35:24 thorpej Exp $"); #endif /* !lint */ #include #include #include #include #include #include #include "kernspace.h" void rumptest_threadpool_unbound_lifecycle(void) { struct threadpool *pool0, *pool1, *pool2; int error; error = threadpool_get(&pool0, PRI_NONE); KASSERT(error == 0); error = threadpool_get(&pool1, PRI_NONE); KASSERT(error == 0); KASSERT(pool0 == pool1); error = threadpool_get(&pool2, PRI_KERNEL_RT); KASSERT(error == 0); KASSERT(pool0 != pool2); threadpool_put(pool0, PRI_NONE); threadpool_put(pool1, PRI_NONE); threadpool_put(pool2, PRI_KERNEL_RT); } void rumptest_threadpool_percpu_lifecycle(void) { struct threadpool_percpu *pcpu0, *pcpu1, *pcpu2; int error; error = threadpool_percpu_get(&pcpu0, PRI_NONE); KASSERT(error == 0); error = threadpool_percpu_get(&pcpu1, PRI_NONE); KASSERT(error == 0); KASSERT(pcpu0 == pcpu1); error = threadpool_percpu_get(&pcpu2, PRI_KERNEL_RT); KASSERT(error == 0); KASSERT(pcpu0 != pcpu2); threadpool_percpu_put(pcpu0, PRI_NONE); threadpool_percpu_put(pcpu1, PRI_NONE); threadpool_percpu_put(pcpu2, PRI_KERNEL_RT); } struct test_job_data { kmutex_t mutex; kcondvar_t cond; unsigned int count; struct threadpool_job job; }; #define FINAL_COUNT 12345 static void test_job_func_schedule(struct threadpool_job *job) { struct test_job_data *data = container_of(job, struct test_job_data, job); mutex_enter(&data->mutex); KASSERT(data->count != FINAL_COUNT); data->count++; cv_broadcast(&data->cond); threadpool_job_done(job); mutex_exit(&data->mutex); } static void test_job_func_cancel(struct threadpool_job *job) { struct test_job_data *data = container_of(job, struct test_job_data, job); mutex_enter(&data->mutex); if (data->count == 0) { data->count = 1; cv_broadcast(&data->cond); } while (data->count != FINAL_COUNT - 1) cv_wait(&data->cond, &data->mutex); data->count = FINAL_COUNT; cv_broadcast(&data->cond); threadpool_job_done(job); mutex_exit(&data->mutex); } static void init_test_job_data(struct test_job_data *data, threadpool_job_fn_t fn) { mutex_init(&data->mutex, MUTEX_DEFAULT, IPL_NONE); cv_init(&data->cond, "testjob"); threadpool_job_init(&data->job, fn, &data->mutex, "testjob"); data->count = 0; } static void fini_test_job_data(struct test_job_data *data) { threadpool_job_destroy(&data->job); cv_destroy(&data->cond); mutex_destroy(&data->mutex); } void rumptest_threadpool_unbound_schedule(void) { struct test_job_data data; struct threadpool *pool; int error; error = threadpool_get(&pool, PRI_NONE); KASSERT(error == 0); init_test_job_data(&data, test_job_func_schedule); mutex_enter(&data.mutex); while (data.count != FINAL_COUNT) { threadpool_schedule_job(pool, &data.job); error = cv_timedwait(&data.cond, &data.mutex, hz * 2); KASSERT(error != EWOULDBLOCK); } mutex_exit(&data.mutex); fini_test_job_data(&data); threadpool_put(pool, PRI_NONE); } void rumptest_threadpool_percpu_schedule(void) { struct test_job_data data; struct threadpool_percpu *pcpu; struct threadpool *pool; int error; error = threadpool_percpu_get(&pcpu, PRI_NONE); KASSERT(error == 0); pool = threadpool_percpu_ref(pcpu); init_test_job_data(&data, test_job_func_schedule); mutex_enter(&data.mutex); while (data.count != FINAL_COUNT) { threadpool_schedule_job(pool, &data.job); error = cv_timedwait(&data.cond, &data.mutex, hz * 2); KASSERT(error != EWOULDBLOCK); } mutex_exit(&data.mutex); fini_test_job_data(&data); threadpool_percpu_put(pcpu, PRI_NONE); } void rumptest_threadpool_job_cancel(void) { struct test_job_data data; struct threadpool *pool; int error; bool rv; error = threadpool_get(&pool, PRI_NONE); KASSERT(error == 0); init_test_job_data(&data, test_job_func_cancel); mutex_enter(&data.mutex); threadpool_schedule_job(pool, &data.job); while (data.count == 0) cv_wait(&data.cond, &data.mutex); KASSERT(data.count == 1); /* Job is already running (and is not finished); this shold fail. */ rv = threadpool_cancel_job_async(pool, &data.job); KASSERT(rv == false); data.count = FINAL_COUNT - 1; cv_broadcast(&data.cond); /* Now wait for the job to finish. */ threadpool_cancel_job(pool, &data.job); KASSERT(data.count == FINAL_COUNT); mutex_exit(&data.mutex); fini_test_job_data(&data); threadpool_put(pool, PRI_NONE); } void rumptest_threadpool_job_cancelthrash(void) { struct test_job_data data; struct threadpool *pool; int i, error; error = threadpool_get(&pool, PRI_NONE); KASSERT(error == 0); init_test_job_data(&data, test_job_func_cancel); mutex_enter(&data.mutex); for (i = 0; i < 10000; i++) { threadpool_schedule_job(pool, &data.job); if ((i % 3) == 0) { mutex_exit(&data.mutex); mutex_enter(&data.mutex); } /* * If the job managed to start, ensure that its exit * condition is met so that we don't wait forever * for the job to finish. */ data.count = FINAL_COUNT - 1; cv_broadcast(&data.cond); threadpool_cancel_job(pool, &data.job); /* * After cancellation, either the job didn't start * (data.count == FINAL_COUNT - 1, per above) or * it finished (data.count == FINAL_COUNT). */ KASSERT(data.count == (FINAL_COUNT - 1) || data.count == FINAL_COUNT); /* Reset for the loop. */ data.count = 0; } mutex_exit(&data.mutex); fini_test_job_data(&data); threadpool_put(pool, PRI_NONE); }