Files
bind9/lib/isc/app.c
T
Evan Hunt 64e1a4a398 temporarily move ISC_QUEUE to list.h
The double-locked queue implementation is still currently in use
in ns_client, but will be replaced by a fetch-and-add array queue.
This commit moves it from queue.h to list.h so that queue.h can be
used for the new data structure, and clean up dependencies between
list.h and types.h. Later, when the ISC_QUEUE is no longer is use,
it will be removed completely.
2019-11-07 11:55:37 -08:00

570 lines
13 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* 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 http://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
/*! \file */
#include <sys/types.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#ifndef WIN32
#include <inttypes.h>
#include <signal.h>
#include <sys/time.h>
#endif /* WIN32 */
#include <isc/platform.h>
#include <isc/atomic.h>
#include <isc/app.h>
#include <isc/condition.h>
#include <isc/list.h>
#include <isc/mem.h>
#include <isc/mutex.h>
#include <isc/event.h>
#include <isc/platform.h>
#include <isc/strerr.h>
#include <isc/string.h>
#include <isc/task.h>
#include <isc/time.h>
#include <isc/thread.h>
#include <isc/util.h>
#ifdef WIN32
#include <process.h>
#else /* WIN32 */
#include <pthread.h>
#endif /* WIN32 */
/*%
* For BIND9 internal applications built with threads, we use a single app
* context and let multiple worker, I/O, timer threads do actual jobs.
*/
static isc_thread_t blockedthread;
static atomic_bool is_running;
#ifdef WIN32
/*
* We need to remember which thread is the main thread...
*/
static isc_thread_t main_thread;
#endif
/*
* The application context of this module.
*/
#define APPCTX_MAGIC ISC_MAGIC('A', 'p', 'c', 'x')
#define VALID_APPCTX(c) ISC_MAGIC_VALID(c, APPCTX_MAGIC)
#ifdef WIN32
#define NUM_EVENTS 2
enum {
RELOAD_EVENT,
SHUTDOWN_EVENT
};
#endif /* WIN32 */
struct isc_appctx {
unsigned int magic;
isc_mem_t *mctx;
isc_mutex_t lock;
isc_eventlist_t on_run;
atomic_bool shutdown_requested;
atomic_bool running;
atomic_bool want_shutdown;
atomic_bool want_reload;
atomic_bool blocked;
#ifdef WIN32
HANDLE hEvents[NUM_EVENTS];
#else /* WIN32 */
isc_mutex_t readylock;
isc_condition_t ready;
#endif /* WIN32 */
};
static isc_appctx_t isc_g_appctx;
#ifndef WIN32
static void
handle_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__,
"handle_signal() %d setup: %s",
sig, strbuf);
}
}
#endif
isc_result_t
isc_app_ctxstart(isc_appctx_t *ctx) {
REQUIRE(VALID_APPCTX(ctx));
/*
* Start an ISC library application.
*/
isc_mutex_init(&ctx->lock);
#ifndef WIN32
isc_mutex_init(&ctx->readylock);
isc_condition_init(&ctx->ready);
#endif /* WIN32 */
ISC_LIST_INIT(ctx->on_run);
atomic_init(&ctx->shutdown_requested, false);
atomic_init(&ctx->running, false);
atomic_init(&ctx->want_shutdown, false);
atomic_init(&ctx->want_reload, false);
atomic_init(&ctx->blocked, false);
#ifdef WIN32
main_thread = GetCurrentThread();
/* Create the reload event in a non-signaled state */
ctx->hEvents[RELOAD_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL);
/* Create the shutdown event in a non-signaled state */
ctx->hEvents[SHUTDOWN_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL);
#else /* WIN32 */
int presult;
sigset_t sset;
char strbuf[ISC_STRERRORSIZE];
/*
* Always ignore SIGPIPE.
*/
handle_signal(SIGPIPE, SIG_IGN);
handle_signal(SIGHUP, SIG_DFL);
handle_signal(SIGTERM, SIG_DFL);
handle_signal(SIGINT, SIG_DFL);
/*
* Block SIGHUP, SIGINT, SIGTERM.
*
* If isc_app_start() is called from the main thread before any other
* threads have been created, then the pthread_sigmask() call below
* will result in all threads having SIGHUP, SIGINT and SIGTERM
* blocked by default, ensuring that only the thread that calls
* sigwait() for them will get those signals.
*/
if (sigemptyset(&sset) != 0 ||
sigaddset(&sset, SIGHUP) != 0 ||
sigaddset(&sset, SIGINT) != 0 ||
sigaddset(&sset, SIGTERM) != 0) {
strerror_r(errno, strbuf, sizeof(strbuf));
isc_error_fatal(__FILE__, __LINE__,
"isc_app_start() sigsetops: %s", strbuf);
}
presult = pthread_sigmask(SIG_BLOCK, &sset, NULL);
if (presult != 0) {
strerror_r(presult, strbuf, sizeof(strbuf));
isc_error_fatal(__FILE__, __LINE__,
"isc_app_start() pthread_sigmask: %s",
strbuf);
}
#endif /* WIN32 */
return (ISC_R_SUCCESS);
}
isc_result_t
isc_app_start(void) {
isc_g_appctx.magic = APPCTX_MAGIC;
isc_g_appctx.mctx = NULL;
/* The remaining members will be initialized in ctxstart() */
return (isc_app_ctxstart(&isc_g_appctx));
}
isc_result_t
isc_app_onrun(isc_mem_t *mctx, isc_task_t *task, isc_taskaction_t action,
void *arg)
{
return (isc_app_ctxonrun(&isc_g_appctx, mctx, task, action, arg));
}
isc_result_t
isc_app_ctxonrun(isc_appctx_t *ctx, isc_mem_t *mctx, isc_task_t *task,
isc_taskaction_t action, void *arg)
{
isc_event_t *event;
isc_task_t *cloned_task = NULL;
if (atomic_load_acquire(&ctx->running)) {
return (ISC_R_ALREADYRUNNING);
}
/*
* Note that we store the task to which we're going to send the event
* in the event's "sender" field.
*/
isc_task_attach(task, &cloned_task);
event = isc_event_allocate(mctx, cloned_task, ISC_APPEVENT_SHUTDOWN,
action, arg, sizeof(*event));
LOCK(&ctx->lock);
ISC_LINK_INIT(event, ev_link);
ISC_LIST_APPEND(ctx->on_run, event, ev_link);
UNLOCK(&ctx->lock);
return (ISC_R_SUCCESS);
}
isc_result_t
isc_app_ctxrun(isc_appctx_t *ctx) {
isc_event_t *event, *next_event;
isc_task_t *task;
bool exp_false = false;
bool exp_true = true;
REQUIRE(VALID_APPCTX(ctx));
#ifdef WIN32
REQUIRE(main_thread == GetCurrentThread());
#endif
if (atomic_compare_exchange_weak_acq_rel(
&ctx->running, &exp_false, true) == true)
{
/*
* Post any on-run events (in FIFO order).
*/
LOCK(&ctx->lock);
for (event = ISC_LIST_HEAD(ctx->on_run);
event != NULL;
event = next_event) {
next_event = ISC_LIST_NEXT(event, ev_link);
ISC_LIST_UNLINK(ctx->on_run, event, ev_link);
task = event->ev_sender;
event->ev_sender = NULL;
isc_task_sendanddetach(&task, &event);
}
UNLOCK(&ctx->lock);
}
#ifndef WIN32
/*
* BIND9 internal tools using multiple contexts do not
* rely on signal. */
if (isc_bind9 && ctx != &isc_g_appctx) {
return (ISC_R_SUCCESS);
}
#endif /* WIN32 */
/*
* There is no danger if isc_app_shutdown() is called before we
* wait for signals. Signals are blocked, so any such signal will
* simply be made pending and we will get it when we call
* sigwait().
*/
while (atomic_load_acquire(&ctx->want_shutdown) == false) {
#ifdef WIN32
DWORD dwWaitResult = \
WaitForMultipleObjects(NUM_EVENTS, ctx->hEvents,
FALSE, INFINITE);
/* See why we returned */
if (WaitSucceeded(dwWaitResult, NUM_EVENTS)) {
/*
* The return was due to one of the events
* being signaled
*/
switch (WaitSucceededIndex(dwWaitResult)) {
case RELOAD_EVENT:
atomic_store_release(&ctx->want_reload, true);
break;
case SHUTDOWN_EVENT:
atomic_store_release(&ctx->want_shutdown, true);
break;
}
}
#else /* WIN32 */
if (isc_bind9) {
sigset_t sset;
int sig;
/*
* BIND9 internal; single context:
* Wait for SIGHUP, SIGINT, or SIGTERM.
*/
if (sigemptyset(&sset) != 0 ||
sigaddset(&sset, SIGHUP) != 0 ||
sigaddset(&sset, SIGINT) != 0 ||
sigaddset(&sset, SIGTERM) != 0) {
char strbuf[ISC_STRERRORSIZE];
strerror_r(errno, strbuf, sizeof(strbuf));
isc_error_fatal(__FILE__, __LINE__,
"isc_app_run() sigsetops: %s",
strbuf);
}
if (sigwait(&sset, &sig) == 0) {
switch (sig) {
case SIGINT:
case SIGTERM:
atomic_store_release(
&ctx->want_shutdown, true);
break;
case SIGHUP:
atomic_store_release(
&ctx->want_reload, true);
break;
default:
INSIST(0);
ISC_UNREACHABLE();
}
}
} else {
/*
* External, or BIND9 using multiple contexts:
* wait until woken up.
*/
if (atomic_load_acquire(&ctx->want_shutdown)) {
break;
}
if (!atomic_load_acquire(&ctx->want_reload)) {
LOCK(&ctx->readylock);
WAIT(&ctx->ready, &ctx->readylock);
UNLOCK(&ctx->readylock);
}
}
#endif /* WIN32 */
exp_true = true;
if (atomic_compare_exchange_weak_acq_rel(&ctx->want_reload,
&exp_true,
false))
{
return (ISC_R_RELOAD);
}
if (atomic_load_acquire(&ctx->want_shutdown) &&
atomic_load_acquire(&ctx->blocked)) {
exit(1);
}
}
return (ISC_R_SUCCESS);
}
isc_result_t
isc_app_run(void) {
isc_result_t result;
bool exp_false = false;
REQUIRE(atomic_compare_exchange_weak_acq_rel(
&is_running, &exp_false, true) == true);
result = isc_app_ctxrun(&isc_g_appctx);
atomic_store_release(&is_running, false);
return (result);
}
bool
isc_app_isrunning() {
return (atomic_load_acquire(&is_running));
}
void
isc_app_ctxshutdown(isc_appctx_t *ctx) {
bool exp_false = false;
REQUIRE(VALID_APPCTX(ctx));
REQUIRE(atomic_load_acquire(&ctx->running));
/* If ctx->shutdown_requested == true, we are already shutting
* down and we want to just bail out.
*/
if (atomic_compare_exchange_weak_acq_rel(
&ctx->shutdown_requested,
&exp_false,
true))
{
#ifdef WIN32
SetEvent(ctx->hEvents[SHUTDOWN_EVENT]);
#else /* WIN32 */
if (isc_bind9 && ctx != &isc_g_appctx) {
/* BIND9 internal, but using multiple contexts */
atomic_store_release(&ctx->want_shutdown, true);
} else if (isc_bind9) {
/* BIND9 internal, single context */
if (kill(getpid(), SIGTERM) < 0) {
char strbuf[ISC_STRERRORSIZE];
strerror_r(errno,
strbuf, sizeof(strbuf));
isc_error_fatal(__FILE__, __LINE__,
"isc_app_shutdown() "
"kill: %s", strbuf);
}
} else {
/* External, multiple contexts */
atomic_store_release(&ctx->want_shutdown, true);
SIGNAL(&ctx->ready);
}
#endif /* WIN32 */
}
}
void
isc_app_shutdown(void) {
isc_app_ctxshutdown(&isc_g_appctx);
}
void
isc_app_ctxsuspend(isc_appctx_t *ctx) {
REQUIRE(VALID_APPCTX(ctx));
REQUIRE(atomic_load(&ctx->running));
/*
* Don't send the reload signal if we're shutting down.
*/
if (atomic_load_acquire(&ctx->shutdown_requested) == false) {
#ifdef WIN32
SetEvent(ctx->hEvents[RELOAD_EVENT]);
#else /* WIN32 */
if (isc_bind9 && ctx != &isc_g_appctx) {
/* BIND9 internal, but using multiple contexts */
atomic_store_release(&ctx->want_reload, true);
} else if (isc_bind9) {
/* BIND9 internal, single context */
if (kill(getpid(), SIGHUP) < 0) {
char strbuf[ISC_STRERRORSIZE];
strerror_r(errno,
strbuf, sizeof(strbuf));
isc_error_fatal(__FILE__, __LINE__,
"isc_app_reload() "
"kill: %s", strbuf);
}
} else {
/* External, multiple contexts */
atomic_store_release(&ctx->want_reload, true);
SIGNAL(&ctx->ready);
}
#endif /* WIN32 */
}
}
void
isc_app_reload(void) {
isc_app_ctxsuspend(&isc_g_appctx);
}
void
isc_app_ctxfinish(isc_appctx_t *ctx) {
REQUIRE(VALID_APPCTX(ctx));
isc_mutex_destroy(&ctx->lock);
#ifndef WIN32
isc_mutex_destroy(&ctx->readylock);
isc_condition_destroy(&ctx->ready);
#endif /* WIN32 */
}
void
isc_app_finish(void) {
isc_app_ctxfinish(&isc_g_appctx);
}
void
isc_app_block(void) {
bool exp_false = false;
REQUIRE(atomic_load_acquire(&isc_g_appctx.running));
REQUIRE(atomic_compare_exchange_weak_acq_rel(&isc_g_appctx.blocked,
&exp_false, true));
#ifdef WIN32
blockedthread = GetCurrentThread();
#else /* WIN32 */
sigset_t sset;
blockedthread = pthread_self();
RUNTIME_CHECK(sigemptyset(&sset) == 0 &&
sigaddset(&sset, SIGINT) == 0 &&
sigaddset(&sset, SIGTERM) == 0);
RUNTIME_CHECK(pthread_sigmask(SIG_UNBLOCK, &sset, NULL) == 0);
#endif /* WIN32 */
}
void
isc_app_unblock(void) {
bool exp_true = true;
REQUIRE(atomic_load_acquire(&isc_g_appctx.running));
REQUIRE(atomic_compare_exchange_weak_acq_rel(
&isc_g_appctx.blocked,
&exp_true,
false));
#ifdef WIN32
REQUIRE(blockedthread == GetCurrentThread());
#else /* WIN32 */
REQUIRE(blockedthread == pthread_self());
sigset_t sset;
RUNTIME_CHECK(sigemptyset(&sset) == 0 &&
sigaddset(&sset, SIGINT) == 0 &&
sigaddset(&sset, SIGTERM) == 0);
RUNTIME_CHECK(pthread_sigmask(SIG_BLOCK, &sset, NULL) == 0);
#endif /* WIN32 */
}
isc_result_t
isc_appctx_create(isc_mem_t *mctx, isc_appctx_t **ctxp) {
isc_appctx_t *ctx;
REQUIRE(mctx != NULL);
REQUIRE(ctxp != NULL && *ctxp == NULL);
ctx = isc_mem_get(mctx, sizeof(*ctx));
ctx->magic = APPCTX_MAGIC;
ctx->mctx = NULL;
isc_mem_attach(mctx, &ctx->mctx);
*ctxp = ctx;
return (ISC_R_SUCCESS);
}
void
isc_appctx_destroy(isc_appctx_t **ctxp) {
isc_appctx_t *ctx;
REQUIRE(ctxp != NULL);
ctx = *ctxp;
*ctxp = NULL;
REQUIRE(VALID_APPCTX(ctx));
ctx->magic = 0;
isc_mem_putanddetach(&ctx->mctx, ctx, sizeof(*ctx));
}