app.c revision 1.2
1/* $NetBSD: app.c,v 1.2 2020/05/24 19:46:26 christos Exp $ */ 2 3/* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * This Source Code Form is subject to the terms of the Mozilla Public 7 * License, v. 2.0. If a copy of the MPL was not distributed with this 8 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * See the COPYRIGHT file distributed with this work for additional 11 * information regarding copyright ownership. 12 */ 13 14/*! \file */ 15 16#include <errno.h> 17#include <stdbool.h> 18#include <stddef.h> 19#include <stdlib.h> 20#include <sys/types.h> 21#include <unistd.h> 22 23#ifndef WIN32 24#include <inttypes.h> 25#include <signal.h> 26#include <sys/time.h> 27#endif /* WIN32 */ 28 29#include <isc/app.h> 30#include <isc/atomic.h> 31#include <isc/condition.h> 32#include <isc/event.h> 33#include <isc/mem.h> 34#include <isc/mutex.h> 35#include <isc/platform.h> 36#include <isc/strerr.h> 37#include <isc/string.h> 38#include <isc/task.h> 39#include <isc/thread.h> 40#include <isc/time.h> 41#include <isc/util.h> 42 43#ifdef WIN32 44#include <process.h> 45#else /* WIN32 */ 46#include <pthread.h> 47#endif /* WIN32 */ 48 49/*% 50 * For BIND9 internal applications built with threads, we use a single app 51 * context and let multiple worker, I/O, timer threads do actual jobs. 52 */ 53 54static isc_thread_t blockedthread; 55static atomic_bool is_running; 56 57#ifdef WIN32 58/* 59 * We need to remember which thread is the main thread... 60 */ 61static isc_thread_t main_thread; 62#endif /* ifdef WIN32 */ 63 64/* 65 * The application context of this module. 66 */ 67#define APPCTX_MAGIC ISC_MAGIC('A', 'p', 'c', 'x') 68#define VALID_APPCTX(c) ISC_MAGIC_VALID(c, APPCTX_MAGIC) 69 70#ifdef WIN32 71#define NUM_EVENTS 2 72 73enum { RELOAD_EVENT, SHUTDOWN_EVENT }; 74#endif /* WIN32 */ 75 76struct isc_appctx { 77 unsigned int magic; 78 isc_mem_t *mctx; 79 isc_mutex_t lock; 80 isc_eventlist_t on_run; 81 atomic_bool shutdown_requested; 82 atomic_bool running; 83 atomic_bool want_shutdown; 84 atomic_bool want_reload; 85 atomic_bool blocked; 86#ifdef WIN32 87 HANDLE hEvents[NUM_EVENTS]; 88#else /* WIN32 */ 89 isc_mutex_t readylock; 90 isc_condition_t ready; 91#endif /* WIN32 */ 92}; 93 94static isc_appctx_t isc_g_appctx; 95 96#ifndef WIN32 97static void 98handle_signal(int sig, void (*handler)(int)) { 99 struct sigaction sa; 100 101 memset(&sa, 0, sizeof(sa)); 102 sa.sa_handler = handler; 103 104 if (sigfillset(&sa.sa_mask) != 0 || sigaction(sig, &sa, NULL) < 0) { 105 char strbuf[ISC_STRERRORSIZE]; 106 strerror_r(errno, strbuf, sizeof(strbuf)); 107 isc_error_fatal(__FILE__, __LINE__, 108 "handle_signal() %d setup: %s", sig, strbuf); 109 } 110} 111#endif /* ifndef WIN32 */ 112 113isc_result_t 114isc_app_ctxstart(isc_appctx_t *ctx) { 115 REQUIRE(VALID_APPCTX(ctx)); 116 117 /* 118 * Start an ISC library application. 119 */ 120 121 isc_mutex_init(&ctx->lock); 122 123#ifndef WIN32 124 isc_mutex_init(&ctx->readylock); 125 isc_condition_init(&ctx->ready); 126#endif /* WIN32 */ 127 128 ISC_LIST_INIT(ctx->on_run); 129 130 atomic_init(&ctx->shutdown_requested, false); 131 atomic_init(&ctx->running, false); 132 atomic_init(&ctx->want_shutdown, false); 133 atomic_init(&ctx->want_reload, false); 134 atomic_init(&ctx->blocked, false); 135 136#ifdef WIN32 137 main_thread = GetCurrentThread(); 138 139 /* Create the reload event in a non-signaled state */ 140 ctx->hEvents[RELOAD_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL); 141 142 /* Create the shutdown event in a non-signaled state */ 143 ctx->hEvents[SHUTDOWN_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL); 144#else /* WIN32 */ 145 int presult; 146 sigset_t sset; 147 char strbuf[ISC_STRERRORSIZE]; 148 149 /* 150 * Always ignore SIGPIPE. 151 */ 152 handle_signal(SIGPIPE, SIG_IGN); 153 154 handle_signal(SIGHUP, SIG_DFL); 155 handle_signal(SIGTERM, SIG_DFL); 156 handle_signal(SIGINT, SIG_DFL); 157 158 /* 159 * Block SIGHUP, SIGINT, SIGTERM. 160 * 161 * If isc_app_start() is called from the main thread before any other 162 * threads have been created, then the pthread_sigmask() call below 163 * will result in all threads having SIGHUP, SIGINT and SIGTERM 164 * blocked by default, ensuring that only the thread that calls 165 * sigwait() for them will get those signals. 166 */ 167 if (sigemptyset(&sset) != 0 || sigaddset(&sset, SIGHUP) != 0 || 168 sigaddset(&sset, SIGINT) != 0 || sigaddset(&sset, SIGTERM) != 0) 169 { 170 strerror_r(errno, strbuf, sizeof(strbuf)); 171 isc_error_fatal(__FILE__, __LINE__, 172 "isc_app_start() sigsetops: %s", strbuf); 173 } 174 presult = pthread_sigmask(SIG_BLOCK, &sset, NULL); 175 if (presult != 0) { 176 strerror_r(presult, strbuf, sizeof(strbuf)); 177 isc_error_fatal(__FILE__, __LINE__, 178 "isc_app_start() pthread_sigmask: %s", strbuf); 179 } 180 181#endif /* WIN32 */ 182 183 return (ISC_R_SUCCESS); 184} 185 186isc_result_t 187isc_app_start(void) { 188 isc_g_appctx.magic = APPCTX_MAGIC; 189 isc_g_appctx.mctx = NULL; 190 /* The remaining members will be initialized in ctxstart() */ 191 192 return (isc_app_ctxstart(&isc_g_appctx)); 193} 194 195isc_result_t 196isc_app_onrun(isc_mem_t *mctx, isc_task_t *task, isc_taskaction_t action, 197 void *arg) { 198 return (isc_app_ctxonrun(&isc_g_appctx, mctx, task, action, arg)); 199} 200 201isc_result_t 202isc_app_ctxonrun(isc_appctx_t *ctx, isc_mem_t *mctx, isc_task_t *task, 203 isc_taskaction_t action, void *arg) { 204 isc_event_t *event; 205 isc_task_t *cloned_task = NULL; 206 207 if (atomic_load_acquire(&ctx->running)) { 208 return (ISC_R_ALREADYRUNNING); 209 } 210 211 /* 212 * Note that we store the task to which we're going to send the event 213 * in the event's "sender" field. 214 */ 215 isc_task_attach(task, &cloned_task); 216 event = isc_event_allocate(mctx, cloned_task, ISC_APPEVENT_SHUTDOWN, 217 action, arg, sizeof(*event)); 218 219 LOCK(&ctx->lock); 220 ISC_LINK_INIT(event, ev_link); 221 ISC_LIST_APPEND(ctx->on_run, event, ev_link); 222 UNLOCK(&ctx->lock); 223 224 return (ISC_R_SUCCESS); 225} 226 227isc_result_t 228isc_app_ctxrun(isc_appctx_t *ctx) { 229 isc_event_t *event, *next_event; 230 isc_task_t *task; 231 232 REQUIRE(VALID_APPCTX(ctx)); 233 234#ifdef WIN32 235 REQUIRE(main_thread == GetCurrentThread()); 236#endif /* ifdef WIN32 */ 237 238 if (atomic_compare_exchange_strong_acq_rel( 239 &ctx->running, &(bool){ false }, true) == true) 240 { 241 /* 242 * Post any on-run events (in FIFO order). 243 */ 244 LOCK(&ctx->lock); 245 for (event = ISC_LIST_HEAD(ctx->on_run); event != NULL; 246 event = next_event) { 247 next_event = ISC_LIST_NEXT(event, ev_link); 248 ISC_LIST_UNLINK(ctx->on_run, event, ev_link); 249 task = event->ev_sender; 250 event->ev_sender = NULL; 251 isc_task_sendanddetach(&task, &event); 252 } 253 UNLOCK(&ctx->lock); 254 } 255 256#ifndef WIN32 257 /* 258 * BIND9 internal tools using multiple contexts do not 259 * rely on signal. */ 260 if (isc_bind9 && ctx != &isc_g_appctx) { 261 return (ISC_R_SUCCESS); 262 } 263#endif /* WIN32 */ 264 265 /* 266 * There is no danger if isc_app_shutdown() is called before we 267 * wait for signals. Signals are blocked, so any such signal will 268 * simply be made pending and we will get it when we call 269 * sigwait(). 270 */ 271 while (atomic_load_acquire(&ctx->want_shutdown) == false) { 272#ifdef WIN32 273 DWORD dwWaitResult = WaitForMultipleObjects( 274 NUM_EVENTS, ctx->hEvents, FALSE, INFINITE); 275 276 /* See why we returned */ 277 278 if (WaitSucceeded(dwWaitResult, NUM_EVENTS)) { 279 /* 280 * The return was due to one of the events 281 * being signaled 282 */ 283 switch (WaitSucceededIndex(dwWaitResult)) { 284 case RELOAD_EVENT: 285 atomic_store_release(&ctx->want_reload, true); 286 287 break; 288 289 case SHUTDOWN_EVENT: 290 atomic_store_release(&ctx->want_shutdown, true); 291 break; 292 } 293 } 294#else /* WIN32 */ 295 if (isc_bind9) { 296 sigset_t sset; 297 int sig; 298 /* 299 * BIND9 internal; single context: 300 * Wait for SIGHUP, SIGINT, or SIGTERM. 301 */ 302 if (sigemptyset(&sset) != 0 || 303 sigaddset(&sset, SIGHUP) != 0 || 304 sigaddset(&sset, SIGINT) != 0 || 305 sigaddset(&sset, SIGTERM) != 0) 306 { 307 char strbuf[ISC_STRERRORSIZE]; 308 strerror_r(errno, strbuf, sizeof(strbuf)); 309 isc_error_fatal(__FILE__, __LINE__, 310 "isc_app_run() sigsetops: %s", 311 strbuf); 312 } 313 314 if (sigwait(&sset, &sig) == 0) { 315 switch (sig) { 316 case SIGINT: 317 case SIGTERM: 318 atomic_store_release( 319 &ctx->want_shutdown, true); 320 break; 321 case SIGHUP: 322 atomic_store_release(&ctx->want_reload, 323 true); 324 break; 325 default: 326 INSIST(0); 327 ISC_UNREACHABLE(); 328 } 329 } 330 } else { 331 /* 332 * External, or BIND9 using multiple contexts: 333 * wait until woken up. 334 */ 335 if (atomic_load_acquire(&ctx->want_shutdown)) { 336 break; 337 } 338 if (!atomic_load_acquire(&ctx->want_reload)) { 339 LOCK(&ctx->readylock); 340 WAIT(&ctx->ready, &ctx->readylock); 341 UNLOCK(&ctx->readylock); 342 } 343 } 344#endif /* WIN32 */ 345 if (atomic_compare_exchange_strong_acq_rel( 346 &ctx->want_reload, &(bool){ true }, false)) 347 { 348 return (ISC_R_RELOAD); 349 } 350 351 if (atomic_load_acquire(&ctx->want_shutdown) && 352 atomic_load_acquire(&ctx->blocked)) 353 { 354 exit(1); 355 } 356 } 357 358 return (ISC_R_SUCCESS); 359} 360 361isc_result_t 362isc_app_run(void) { 363 isc_result_t result; 364 365 REQUIRE(atomic_compare_exchange_strong_acq_rel( 366 &is_running, &(bool){ false }, true) == true); 367 result = isc_app_ctxrun(&isc_g_appctx); 368 atomic_store_release(&is_running, false); 369 370 return (result); 371} 372 373bool 374isc_app_isrunning() { 375 return (atomic_load_acquire(&is_running)); 376} 377 378void 379isc_app_ctxshutdown(isc_appctx_t *ctx) { 380 REQUIRE(VALID_APPCTX(ctx)); 381 382 REQUIRE(atomic_load_acquire(&ctx->running)); 383 384 /* If ctx->shutdown_requested == true, we are already shutting 385 * down and we want to just bail out. 386 */ 387 if (atomic_compare_exchange_strong_acq_rel(&ctx->shutdown_requested, 388 &(bool){ false }, true)) 389 { 390#ifdef WIN32 391 SetEvent(ctx->hEvents[SHUTDOWN_EVENT]); 392#else /* WIN32 */ 393 if (isc_bind9 && ctx != &isc_g_appctx) { 394 /* BIND9 internal, but using multiple contexts */ 395 atomic_store_release(&ctx->want_shutdown, true); 396 } else if (isc_bind9) { 397 /* BIND9 internal, single context */ 398 if (kill(getpid(), SIGTERM) < 0) { 399 char strbuf[ISC_STRERRORSIZE]; 400 strerror_r(errno, strbuf, sizeof(strbuf)); 401 isc_error_fatal(__FILE__, __LINE__, 402 "isc_app_shutdown() " 403 "kill: %s", 404 strbuf); 405 } 406 } else { 407 /* External, multiple contexts */ 408 atomic_store_release(&ctx->want_shutdown, true); 409 SIGNAL(&ctx->ready); 410 } 411#endif /* WIN32 */ 412 } 413} 414 415void 416isc_app_shutdown(void) { 417 isc_app_ctxshutdown(&isc_g_appctx); 418} 419 420void 421isc_app_ctxsuspend(isc_appctx_t *ctx) { 422 REQUIRE(VALID_APPCTX(ctx)); 423 424 REQUIRE(atomic_load(&ctx->running)); 425 426 /* 427 * Don't send the reload signal if we're shutting down. 428 */ 429 if (atomic_load_acquire(&ctx->shutdown_requested) == false) { 430#ifdef WIN32 431 SetEvent(ctx->hEvents[RELOAD_EVENT]); 432#else /* WIN32 */ 433 if (isc_bind9 && ctx != &isc_g_appctx) { 434 /* BIND9 internal, but using multiple contexts */ 435 atomic_store_release(&ctx->want_reload, true); 436 } else if (isc_bind9) { 437 /* BIND9 internal, single context */ 438 if (kill(getpid(), SIGHUP) < 0) { 439 char strbuf[ISC_STRERRORSIZE]; 440 strerror_r(errno, strbuf, sizeof(strbuf)); 441 isc_error_fatal(__FILE__, __LINE__, 442 "isc_app_reload() " 443 "kill: %s", 444 strbuf); 445 } 446 } else { 447 /* External, multiple contexts */ 448 atomic_store_release(&ctx->want_reload, true); 449 SIGNAL(&ctx->ready); 450 } 451#endif /* WIN32 */ 452 } 453} 454 455void 456isc_app_reload(void) { 457 isc_app_ctxsuspend(&isc_g_appctx); 458} 459 460void 461isc_app_ctxfinish(isc_appctx_t *ctx) { 462 REQUIRE(VALID_APPCTX(ctx)); 463 464 isc_mutex_destroy(&ctx->lock); 465#ifndef WIN32 466 isc_mutex_destroy(&ctx->readylock); 467 isc_condition_destroy(&ctx->ready); 468#endif /* WIN32 */ 469} 470 471void 472isc_app_finish(void) { 473 isc_app_ctxfinish(&isc_g_appctx); 474} 475 476void 477isc_app_block(void) { 478 REQUIRE(atomic_load_acquire(&isc_g_appctx.running)); 479 REQUIRE(atomic_compare_exchange_strong_acq_rel(&isc_g_appctx.blocked, 480 &(bool){ false }, true)); 481 482#ifdef WIN32 483 blockedthread = GetCurrentThread(); 484#else /* WIN32 */ 485 sigset_t sset; 486 blockedthread = pthread_self(); 487 RUNTIME_CHECK(sigemptyset(&sset) == 0 && 488 sigaddset(&sset, SIGINT) == 0 && 489 sigaddset(&sset, SIGTERM) == 0); 490 RUNTIME_CHECK(pthread_sigmask(SIG_UNBLOCK, &sset, NULL) == 0); 491#endif /* WIN32 */ 492} 493 494void 495isc_app_unblock(void) { 496 REQUIRE(atomic_load_acquire(&isc_g_appctx.running)); 497 REQUIRE(atomic_compare_exchange_strong_acq_rel(&isc_g_appctx.blocked, 498 &(bool){ true }, false)); 499 500#ifdef WIN32 501 REQUIRE(blockedthread == GetCurrentThread()); 502#else /* WIN32 */ 503 REQUIRE(blockedthread == pthread_self()); 504 505 sigset_t sset; 506 RUNTIME_CHECK(sigemptyset(&sset) == 0 && 507 sigaddset(&sset, SIGINT) == 0 && 508 sigaddset(&sset, SIGTERM) == 0); 509 RUNTIME_CHECK(pthread_sigmask(SIG_BLOCK, &sset, NULL) == 0); 510#endif /* WIN32 */ 511} 512 513isc_result_t 514isc_appctx_create(isc_mem_t *mctx, isc_appctx_t **ctxp) { 515 isc_appctx_t *ctx; 516 517 REQUIRE(mctx != NULL); 518 REQUIRE(ctxp != NULL && *ctxp == NULL); 519 520 ctx = isc_mem_get(mctx, sizeof(*ctx)); 521 522 ctx->magic = APPCTX_MAGIC; 523 524 ctx->mctx = NULL; 525 isc_mem_attach(mctx, &ctx->mctx); 526 527 *ctxp = ctx; 528 529 return (ISC_R_SUCCESS); 530} 531 532void 533isc_appctx_destroy(isc_appctx_t **ctxp) { 534 isc_appctx_t *ctx; 535 536 REQUIRE(ctxp != NULL); 537 ctx = *ctxp; 538 *ctxp = NULL; 539 REQUIRE(VALID_APPCTX(ctx)); 540 541 ctx->magic = 0; 542 543 isc_mem_putanddetach(&ctx->mctx, ctx, sizeof(*ctx)); 544} 545