diff --git a/lib/isc/Makefile.am b/lib/isc/Makefile.am index eea0bb5f77..ff2e6215b3 100644 --- a/lib/isc/Makefile.am +++ b/lib/isc/Makefile.am @@ -46,6 +46,7 @@ libisc_la_HEADERS = \ include/isc/lex.h \ include/isc/list.h \ include/isc/log.h \ + include/isc/loop.h \ include/isc/magic.h \ include/isc/managers.h \ include/isc/md.h \ @@ -78,6 +79,7 @@ libisc_la_HEADERS = \ include/isc/rwlock.h \ include/isc/safe.h \ include/isc/serial.h \ + include/isc/signal.h\ include/isc/siphash.h \ include/isc/sockaddr.h \ include/isc/stat.h \ @@ -151,6 +153,7 @@ libisc_la_SOURCES = \ lex.c \ lib.c \ log.c \ + loop.c \ managers.c \ md.c \ mem.c \ @@ -181,6 +184,7 @@ libisc_la_SOURCES = \ rwlock.c \ safe.c \ serial.c \ + signal.c \ siphash.c \ sockaddr.c \ stats.c \ diff --git a/lib/isc/include/isc/loop.h b/lib/isc/include/isc/loop.h new file mode 100644 index 0000000000..afc72dfef8 --- /dev/null +++ b/lib/isc/include/isc/loop.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +typedef struct isc_loopmgr isc_loopmgr_t; +typedef struct isc_loop isc_loop_t; +typedef struct isc_job isc_job_t; + +typedef void (*isc_job_cb)(void *); + +typedef struct isc_signal isc_signal_t; + +typedef void (*isc_signal_cb)(void *, int); + +#define ISC_LOOPMGR_TID_UNKNOWN UINT32_MAX + +struct isc_signal { + uv_signal_t signal; + isc_mem_t *mctx; + isc_signal_cb cb; + void *cbarg; + int signum; +}; + +/* + * Per-thread loop + */ +#define LOOP_MAGIC ISC_MAGIC('L', 'O', 'O', 'P') +#define VALID_LOOP(t) ISC_MAGIC_VALID(t, LOOP_MAGIC) + +struct isc_job { + isc_mem_t *mctx; + uv_idle_t idle; + isc_job_cb cb; + void *cbarg; + LINK(isc_job_t) link; +}; + +struct isc_loop { + int magic; + isc_refcount_t references; + isc_thread_t thread; + + isc_loopmgr_t *loopmgr; + + uv_loop_t loop; + uint32_t tid; + + isc_mem_t *mctx; + + /* states */ + bool paused; + bool finished; + bool shuttingdown; + + /* Pause */ + uv_async_t pause; + + /* Shutdown */ + uv_async_t shutdown; + ISC_LIST(isc_job_t) ctors; + ISC_LIST(isc_job_t) dtors; +}; + +/* + * Loop Manager + */ +#define LOOPMGR_MAGIC ISC_MAGIC('L', 'o', 'o', 'M') +#define VALID_LOOPMGR(t) ISC_MAGIC_VALID(t, LOOPMGR_MAGIC) + +struct isc_loopmgr { + int magic; + isc_refcount_t references; + isc_mem_t *mctx; + + uint_fast32_t nloops; + + atomic_bool shuttingdown; + atomic_bool running; + atomic_bool paused; + + /* signal handling */ + isc_signal_t *sigint; + isc_signal_t *sigterm; + + /* pause/resume */ + isc_barrier_t pausing; + isc_barrier_t resuming; + + /* per-thread objects */ + isc_loop_t *loops; +}; + +/* FIXME: Deduplicate with netmgr-int.h */ +#define UV_RUNTIME_CHECK(func, ret) \ + if (ret != 0) { \ + isc_error_fatal(__FILE__, __LINE__, "%s failed: %s\n", #func, \ + uv_strerror(ret)); \ + } + +#define DEFAULT_LOOP(loopmgr) (&(loopmgr)->loops[0]) +#define CURRENT_LOOP(loopmgr) (&(loopmgr)->loops[isc__loopmgr_tid_v]) + +isc_loopmgr_t * +isc_loopmgr_new(isc_mem_t *, uint32_t); + +void +isc_loopmgr_destroy(isc_loopmgr_t **); + +void +isc_loopmgr_shutdown(isc_loopmgr_t *); + +isc_loop_t * +isc_loopmgr_getloop(isc_loopmgr_t *); + +int +isc_loopmgr_tid(void); + +void +isc_loopmgr_run(isc_loopmgr_t *); + +void +isc_loopmgr_pause(isc_loopmgr_t *); + +void +isc_loopmgr_resume(isc_loopmgr_t *); + +void +isc_loopmgr_schedule_ctor(isc_loopmgr_t *, isc_job_cb, void *); + +void +isc_loopmgr_schedule_dtor(isc_loopmgr_t *, isc_job_cb, void *); + +void +isc_loop_schedule_ctor(isc_loop_t *loop, isc_job_cb cb, void *cbarg); + +void +isc_loop_schedule_dtor(isc_loop_t *loop, isc_job_cb cb, void *cbarg); + +void +isc_loop_mem_attach(isc_loop_t *loop, isc_mem_t **mctx); + +isc_loop_t * +isc_loopmgr_default_loop(isc_loopmgr_t *loopmgr); diff --git a/lib/isc/include/isc/signal.h b/lib/isc/include/isc/signal.h new file mode 100644 index 0000000000..597c8a6f1c --- /dev/null +++ b/lib/isc/include/isc/signal.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include + +#include + +isc_signal_t * +isc_signal_new(isc_loopmgr_t *loopmgr, isc_signal_cb cb, void *cbarg, + int signum); + +void +isc_signal_free(isc_signal_t *signal); + +void +isc_signal_start(isc_signal_t *signal); + +void +isc_signal_stop(isc_signal_t *signal); diff --git a/lib/isc/loop.c b/lib/isc/loop.c new file mode 100644 index 0000000000..01452d4029 --- /dev/null +++ b/lib/isc/loop.c @@ -0,0 +1,467 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Private + */ + +static thread_local uint32_t isc__loopmgr_tid_v = ISC_LOOPMGR_TID_UNKNOWN; + +static void +ignore_signal(int sig, void (*handler)(int)) { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + + if (sigfillset(&sa.sa_mask) != 0 || sigaction(sig, &sa, NULL) < 0) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, "%s() %d setup: %s", + __func__, sig, strbuf); + } +} + +static void +isc__loopmgr_shutdown(isc_loopmgr_t *loopmgr) { + REQUIRE(DEFAULT_LOOP(loopmgr) == CURRENT_LOOP(loopmgr)); + + if (!atomic_compare_exchange_strong(&loopmgr->shuttingdown, + &(bool){ false }, true)) + { + return; + } + + /* Stop the signal handlers */ + isc_signal_stop(loopmgr->sigterm); + isc_signal_stop(loopmgr->sigint); + + /* Free the signal handlers */ + isc_signal_free(loopmgr->sigterm); + isc_signal_free(loopmgr->sigint); + + for (size_t i = 0; i < loopmgr->nloops; i++) { + isc_loop_t *loop = &loopmgr->loops[i]; + + uv_async_send(&loop->shutdown); + } +} + +void +isc_loopmgr_shutdown(isc_loopmgr_t *loopmgr) { + /* Invoked from non-default loop, just pass the signal */ + if (DEFAULT_LOOP(loopmgr) != CURRENT_LOOP(loopmgr)) { + kill(getpid(), SIGTERM); + return; + } + + isc__loopmgr_shutdown(loopmgr); +} + +static void +isc__loopmgr_signal(void *arg, int signum) { + isc_loopmgr_t *loopmgr = (isc_loopmgr_t *)arg; + + switch (signum) { + case SIGINT: + case SIGTERM: + isc__loopmgr_shutdown(loopmgr); + break; + default: + UNREACHABLE(); + } +} + +static void +pause_loop(isc_loop_t *loop) { + isc_loopmgr_t *loopmgr = loop->loopmgr; + + loop->paused = true; + (void)isc_barrier_wait(&loopmgr->pausing); +} + +static void +resume_loop(isc_loop_t *loop) { + isc_loopmgr_t *loopmgr = loop->loopmgr; + + (void)isc_barrier_wait(&loopmgr->resuming); + loop->paused = false; +} + +static void +pauseresume_cb(uv_async_t *handle) { + isc_loop_t *loop = + (isc_loop_t *)uv_handle_get_data((uv_handle_t *)handle); + + pause_loop(loop); + resume_loop(loop); +} + +static void +isc__job_free(uv_handle_t *handle) { + isc_job_t *job = uv_handle_get_data(handle); + + isc_mem_putanddetach(&job->mctx, job, sizeof(*job)); +} + +static void +isc__job_cb(uv_idle_t *idle) { + isc_job_t *job = uv_handle_get_data((uv_handle_t *)idle); + + job->cb(job->cbarg); + + uv_idle_stop(idle); + uv_close((uv_handle_t *)idle, isc__job_free); +} + +static void +shutdown_cb(uv_async_t *handle) { + isc_job_t *job = NULL; + isc_loop_t *loop = + (isc_loop_t *)uv_handle_get_data((uv_handle_t *)handle); + + /* + * The loop resources are freed only after uv_run() is finished, so we + * don't need to worry about freeing memory used for async callbacks. + */ + uv_close((uv_handle_t *)&loop->shutdown, NULL); + uv_close((uv_handle_t *)&loop->pause, NULL); + + job = ISC_LIST_HEAD(loop->dtors); + while (job != NULL) { + int r; + isc_job_t *next = ISC_LIST_NEXT(job, link); + ISC_LIST_UNLINK(loop->dtors, job, link); + + r = uv_idle_start(&job->idle, isc__job_cb); + UV_RUNTIME_CHECK(uv_idle_start, r); + + job = next; + } +} + +static void +loop_init(isc_loop_t *loop) { + int r = uv_loop_init(&loop->loop); + UV_RUNTIME_CHECK(uv_loop_init, r); + + r = uv_async_init(&loop->loop, &loop->pause, pauseresume_cb); + UV_RUNTIME_CHECK(uv_async_init, r); + uv_handle_set_data((uv_handle_t *)&loop->pause, loop); + + r = uv_async_init(&loop->loop, &loop->shutdown, shutdown_cb); + UV_RUNTIME_CHECK(uv_async_init, r); + uv_handle_set_data((uv_handle_t *)&loop->shutdown, loop); + + isc_mem_create(&loop->mctx); + + ISC_LIST_INIT(loop->ctors); + ISC_LIST_INIT(loop->dtors); +} + +static void +loop_run(isc_loop_t *loop) { + int r; + isc_job_t *job; + + job = ISC_LIST_HEAD(loop->ctors); + while (job != NULL) { + isc_job_t *next = ISC_LIST_NEXT(job, link); + ISC_LIST_UNLINK(loop->ctors, job, link); + + r = uv_idle_start(&job->idle, isc__job_cb); + UV_RUNTIME_CHECK(uv_idle_start, r); + + job = next; + } + + r = uv_run(&loop->loop, UV_RUN_DEFAULT); + UV_RUNTIME_CHECK(uv_run, r); +} + +static void +loop_close(isc_loop_t *loop) { + int r = uv_loop_close(&loop->loop); + UV_RUNTIME_CHECK(uv_loop_close, r); + + isc_mem_detach(&loop->mctx); +} + +static isc_threadresult_t +loop_thread(isc_threadarg_t arg) { + isc_loop_t *loop = (isc_loop_t *)arg; + + REQUIRE(VALID_LOOP(loop)); + + /* Initialize the thread_local variable */ + isc__loopmgr_tid_v = loop->tid; + + loop_run(loop); + + return ((isc_threadresult_t)0); +} + +enum { + isc_loop_ctor, + isc_loop_dtor, +}; + +static void +isc__loop_schedule(isc_loop_t *loop, int when, isc_job_cb cb, void *cbarg) { + isc_job_t *job = NULL; + isc_loopmgr_t *loopmgr; + + REQUIRE(VALID_LOOP(loop)); + + loopmgr = loop->loopmgr; + + REQUIRE(loop->tid == isc__loopmgr_tid_v || + !atomic_load(&loopmgr->running) || + atomic_load(&loopmgr->paused)); + + job = isc_mem_get(loop->mctx, sizeof(*job)); + *job = (isc_job_t){ + .cb = cb, + .cbarg = cbarg, + }; + + ISC_LINK_INIT(job, link); + + isc_mem_attach(loop->mctx, &job->mctx); + + int r = uv_idle_init(&loop->loop, &job->idle); + UV_RUNTIME_CHECK(uv_idle_init, r); + uv_handle_set_data((uv_handle_t *)&job->idle, job); + + switch (when) { + case isc_loop_ctor: + ISC_LIST_PREPEND(loop->ctors, job, link); + break; + case isc_loop_dtor: + ISC_LIST_PREPEND(loop->dtors, job, link); + break; + default: + UNREACHABLE(); + } +} + +/** + * Public + */ + +int +isc_loopmgr_tid(void) { + return (isc__loopmgr_tid_v); +} + +isc_loopmgr_t * +isc_loopmgr_new(isc_mem_t *mctx, uint32_t nloops) { + isc_loopmgr_t *loopmgr = NULL; + + REQUIRE(nloops > 0); + + loopmgr = isc_mem_get(mctx, sizeof(*loopmgr)); + *loopmgr = (isc_loopmgr_t){ + .nloops = nloops, + }; + + isc_mem_attach(mctx, &loopmgr->mctx); + isc_refcount_init(&loopmgr->references, 1); + + isc_barrier_init(&loopmgr->pausing, loopmgr->nloops); + isc_barrier_init(&loopmgr->resuming, loopmgr->nloops); + + loopmgr->loops = isc_mem_get( + loopmgr->mctx, loopmgr->nloops * sizeof(loopmgr->loops[0])); + for (size_t i = 0; i < loopmgr->nloops; i++) { + isc_loop_t *loop = &loopmgr->loops[i]; + *loop = (isc_loop_t){ + .tid = i, + .loopmgr = loopmgr, + .magic = LOOP_MAGIC, + }; + + loop_init(loop); + } + + loopmgr->sigint = isc_signal_new(loopmgr, isc__loopmgr_signal, loopmgr, + SIGINT); + loopmgr->sigterm = isc_signal_new(loopmgr, isc__loopmgr_signal, loopmgr, + SIGTERM); + + isc_signal_start(loopmgr->sigint); + isc_signal_start(loopmgr->sigterm); + + loopmgr->magic = LOOPMGR_MAGIC; + + return (loopmgr); +} + +void +isc_loop_schedule_ctor(isc_loop_t *loop, isc_job_cb cb, void *cbarg) { + isc__loop_schedule(loop, isc_loop_ctor, cb, cbarg); +} + +void +isc_loop_schedule_dtor(isc_loop_t *loop, isc_job_cb cb, void *cbarg) { + isc__loop_schedule(loop, isc_loop_dtor, cb, cbarg); +} + +static void +isc__loopmgr_schedule(isc_loopmgr_t *loopmgr, int when, isc_job_cb cb, + void *cbarg) { + REQUIRE(VALID_LOOPMGR(loopmgr)); + REQUIRE(!atomic_load(&loopmgr->running) || + atomic_load(&loopmgr->paused)); + + for (size_t i = 0; i < loopmgr->nloops; i++) { + isc_loop_t *loop = &loopmgr->loops[i]; + isc__loop_schedule(loop, when, cb, cbarg); + } +} + +void +isc_loopmgr_schedule_ctor(isc_loopmgr_t *loopmgr, isc_job_cb cb, void *cbarg) { + isc__loopmgr_schedule(loopmgr, isc_loop_ctor, cb, cbarg); +} + +void +isc_loopmgr_schedule_dtor(isc_loopmgr_t *loopmgr, isc_job_cb cb, void *cbarg) { + isc__loopmgr_schedule(loopmgr, isc_loop_dtor, cb, cbarg); +} + +void +isc_loopmgr_run(isc_loopmgr_t *loopmgr) { + REQUIRE(VALID_LOOPMGR(loopmgr)); + RUNTIME_CHECK(atomic_compare_exchange_strong(&loopmgr->running, + &(bool){ false }, true)); + + /* + * Always ignore SIGPIPE. + */ + ignore_signal(SIGPIPE, SIG_IGN); + + /* + * The thread 0 is this one. + */ + for (size_t i = 1; i < loopmgr->nloops; i++) { + isc_loop_t *loop = &loopmgr->loops[i]; + + isc_thread_create(loop_thread, loop, &loop->thread); + } + + loop_thread(&loopmgr->loops[0]); +} + +void +isc_loopmgr_pause(isc_loopmgr_t *loopmgr) { + REQUIRE(VALID_LOOPMGR(loopmgr)); + + for (size_t i = 0; i < loopmgr->nloops; i++) { + isc_loop_t *loop = &loopmgr->loops[i]; + + /* Skip current loop */ + if (i == isc__loopmgr_tid_v) { + continue; + } + uv_async_send(&loop->pause); + } + + RUNTIME_CHECK(atomic_compare_exchange_strong(&loopmgr->paused, + &(bool){ false }, true)); + pause_loop(CURRENT_LOOP(loopmgr)); +} + +void +isc_loopmgr_resume(isc_loopmgr_t *loopmgr) { + REQUIRE(VALID_LOOPMGR(loopmgr)); + + RUNTIME_CHECK(atomic_compare_exchange_strong(&loopmgr->paused, + &(bool){ true }, false)); + resume_loop(CURRENT_LOOP(loopmgr)); +} + +void +isc_loopmgr_destroy(isc_loopmgr_t **loopmgrp) { + isc_loopmgr_t *loopmgr = NULL; + + REQUIRE(loopmgrp != NULL); + REQUIRE(VALID_LOOPMGR(*loopmgrp)); + + loopmgr = *loopmgrp; + *loopmgrp = NULL; + + isc_refcount_decrement0(&loopmgr->references); + isc_refcount_destroy(&loopmgr->references); + + loopmgr->magic = 0; + + /* FIXME: We need to split the ->running and ->paused variable */ + if (atomic_compare_exchange_strong(&loopmgr->running, &(bool){ true }, + false)) { + /* First wait for all loops to finish */ + for (size_t i = 1; i < loopmgr->nloops; i++) { + isc_loop_t *loop = &loopmgr->loops[i]; + isc_thread_join(loop->thread, NULL); + } + } + + for (size_t i = 0; i < loopmgr->nloops; i++) { + isc_loop_t *loop = &loopmgr->loops[i]; + + loop_close(loop); + loop->magic = 0; + } + isc_mem_put(loopmgr->mctx, loopmgr->loops, + loopmgr->nloops * sizeof(loopmgr->loops[0])); + + isc_barrier_destroy(&loopmgr->resuming); + isc_barrier_destroy(&loopmgr->pausing); + + isc_mem_putanddetach(&loopmgr->mctx, loopmgr, sizeof(*loopmgr)); +} + +void +isc_loop_mem_attach(isc_loop_t *loop, isc_mem_t **mctxp) { + REQUIRE(VALID_LOOP(loop)); + REQUIRE(mctxp != NULL && *mctxp == NULL); + + isc_mem_attach(loop->mctx, mctxp); +} + +isc_loop_t * +isc_loopmgr_default_loop(isc_loopmgr_t *loopmgr) { + REQUIRE(VALID_LOOPMGR(loopmgr)); + + return (DEFAULT_LOOP(loopmgr)); +} diff --git a/lib/isc/signal.c b/lib/isc/signal.c new file mode 100644 index 0000000000..65696ee759 --- /dev/null +++ b/lib/isc/signal.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include + +#include +#include + +isc_signal_t * +isc_signal_new(isc_loopmgr_t *loopmgr, isc_signal_cb cb, void *cbarg, + int signum) { + isc_loop_t *loop = NULL; + isc_signal_t *signal = NULL; + isc_mem_t *mctx = NULL; + + loop = DEFAULT_LOOP(loopmgr); + + isc_loop_mem_attach(loop, &mctx); + + signal = isc_mem_get(mctx, sizeof(*signal)); + *signal = (isc_signal_t){ + .mctx = mctx, + .cb = cb, + .cbarg = cbarg, + .signum = signum, + }; + + int r = uv_signal_init(&loop->loop, &signal->signal); + UV_RUNTIME_CHECK(uv_signal_init, r); + + uv_handle_set_data((uv_handle_t *)&signal->signal, signal); + + return (signal); +} + +static void +isc__signal_free(uv_handle_t *handle) { + isc_signal_t *signal = uv_handle_get_data(handle); + + isc_mem_putanddetach(&signal->mctx, signal, sizeof(*signal)); +} + +void +isc_signal_free(isc_signal_t *signal) { + uv_close((uv_handle_t *)&signal->signal, isc__signal_free); +} + +void +isc_signal_stop(isc_signal_t *signal) { + int r = uv_signal_stop(&signal->signal); + UV_RUNTIME_CHECK(uv_signal_stop, r); +} + +static void +isc__signal_cb(uv_signal_t *handle, int signum) { + isc_signal_t *signal = uv_handle_get_data((uv_handle_t *)handle); + + REQUIRE(signum == signal->signum); + + signal->cb(signal->cbarg, signum); +} + +void +isc_signal_start(isc_signal_t *signal) { + int r = uv_signal_start(&signal->signal, isc__signal_cb, + signal->signum); + UV_RUNTIME_CHECK(uv_signal_start, r); +} diff --git a/lib/isc/tests/Makefile.am b/lib/isc/tests/Makefile.am index d84e167e1b..c23e2d17de 100644 --- a/lib/isc/tests/Makefile.am +++ b/lib/isc/tests/Makefile.am @@ -25,6 +25,7 @@ check_PROGRAMS = \ hmac_test \ ht_test \ lex_test \ + loop_test \ md_test \ mem_test \ netaddr_test \ @@ -70,6 +71,14 @@ hmac_test_LDADD = \ $(LDADD) \ $(OPENSSL_LIBS) +loop_test_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(LIBUV_CFLAGS) + +loop_test_LDADD = \ + $(LDADD) \ + $(LIBUV_LIBS) + md_test_CPPFLAGS = \ $(AM_CPPFLAGS) \ $(OPENSSL_CFLAGS) diff --git a/lib/isc/tests/loop_test.c b/lib/isc/tests/loop_test.c new file mode 100644 index 0000000000..ab68fb82ce --- /dev/null +++ b/lib/isc/tests/loop_test.c @@ -0,0 +1,203 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include + +#include "../loop.c" +#include "isctest.h" + +static int +setup_mctx(void **state) { + UNUSED(state); + + isc_mem_create(&test_mctx); + + return (0); +} + +static int +teardown_mctx(void **state) { + UNUSED(state); + + isc_mem_destroy(&test_mctx); + + return (0); +} + +static int +setup_loopmgr(void **state) { + isc_loopmgr_t *loopmgr = NULL; + + loopmgr = isc_loopmgr_new(test_mctx, isc_os_ncpus()); + if (loopmgr == NULL) { + return (1); + } + + *state = loopmgr; + + return (0); +} + +static int +teardown_loopmgr(void **state) { + isc_loopmgr_t *loopmgr = *state; + + isc_loopmgr_destroy(&loopmgr); + + return (0); +} + +static atomic_uint scheduled = 0; + +static void +count(void *arg) { + UNUSED(arg); + + atomic_fetch_add(&scheduled, 1); +} + +static void +shutdown_loopmgr(void *arg) { + isc_loopmgr_t *loopmgr = (isc_loopmgr_t *)arg; + + while (atomic_load(&scheduled) != loopmgr->nloops) { + isc_thread_yield(); + } + + isc_loopmgr_shutdown(loopmgr); +} + +static void +isc_loopmgr_test(void **state) { + isc_loopmgr_t *loopmgr = *state; + + isc_loopmgr_schedule_ctor(loopmgr, count, loopmgr); + + isc_loop_schedule_ctor(DEFAULT_LOOP(loopmgr), shutdown_loopmgr, + loopmgr); + + isc_loopmgr_run(loopmgr); + + assert_int_equal(atomic_load(&scheduled), loopmgr->nloops); +} + +static void +pause_loopmgr(void *arg) { + isc_loopmgr_t *loopmgr = (isc_loopmgr_t *)arg; + + isc_loopmgr_pause(loopmgr); + + assert_true(atomic_load(&loopmgr->paused)); + + for (size_t i = 0; i < loopmgr->nloops; i++) { + isc_loop_t *loop = &loopmgr->loops[i]; + + assert_true(loop->paused); + } + + atomic_init(&scheduled, loopmgr->nloops); + + isc_loopmgr_resume(loopmgr); +} + +static void +isc_loopmgr_pause_test(void **state) { + isc_loopmgr_t *loopmgr = *state; + + isc_loop_schedule_ctor(DEFAULT_LOOP(loopmgr), pause_loopmgr, loopmgr); + isc_loop_schedule_ctor(DEFAULT_LOOP(loopmgr), shutdown_loopmgr, + loopmgr); + + isc_loopmgr_run(loopmgr); +} + +static void +send_sigint(void *arg) { + UNUSED(arg); + + kill(getpid(), SIGINT); +} + +static void +isc_loopmgr_sigint_test(void **state) { + isc_loopmgr_t *loopmgr = *state; + + isc_loop_schedule_ctor(CURRENT_LOOP(loopmgr), send_sigint, loopmgr); + + isc_loopmgr_run(loopmgr); +} + +static void +send_sigterm(void *arg) { + UNUSED(arg); + + kill(getpid(), SIGINT); +} + +static void +isc_loopmgr_sigterm_test(void **state) { + isc_loopmgr_t *loopmgr = *state; + + isc_loop_schedule_ctor(CURRENT_LOOP(loopmgr), send_sigterm, loopmgr); + + isc_loopmgr_run(loopmgr); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(isc_loopmgr_test, setup_loopmgr, + teardown_loopmgr), + cmocka_unit_test_setup_teardown(isc_loopmgr_pause_test, + setup_loopmgr, + teardown_loopmgr), + cmocka_unit_test_setup_teardown(isc_loopmgr_sigint_test, + setup_loopmgr, + teardown_loopmgr), + cmocka_unit_test_setup_teardown(isc_loopmgr_sigterm_test, + setup_loopmgr, + teardown_loopmgr), + }; + + return (cmocka_run_group_tests(tests, setup_mctx, teardown_mctx)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */