1 /* $NetBSD: loop.c,v 1.4 2026/08/29 14:55:18 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 #include <stdlib.h> 17 #include <sys/types.h> 18 #include <unistd.h> 19 20 #include <isc/async.h> 21 #include <isc/atomic.h> 22 #include <isc/barrier.h> 23 #include <isc/condition.h> 24 #include <isc/job.h> 25 #include <isc/list.h> 26 #include <isc/log.h> 27 #include <isc/loop.h> 28 #include <isc/magic.h> 29 #include <isc/mem.h> 30 #include <isc/mutex.h> 31 #include <isc/refcount.h> 32 #include <isc/result.h> 33 #include <isc/signal.h> 34 #include <isc/strerr.h> 35 #include <isc/thread.h> 36 #include <isc/tid.h> 37 #include <isc/time.h> 38 #include <isc/urcu.h> 39 #include <isc/util.h> 40 #include <isc/uv.h> 41 #include <isc/work.h> 42 43 #include "async_p.h" 44 #include "job_p.h" 45 #include "loop_p.h" 46 47 /** 48 * Private 49 */ 50 51 thread_local isc_loop_t *isc__loop_local = NULL; 52 53 static void 54 ignore_signal(int sig, void (*handler)(int)) { 55 struct sigaction sa = { .sa_handler = handler }; 56 57 if (sigfillset(&sa.sa_mask) != 0 || sigaction(sig, &sa, NULL) < 0) { 58 FATAL_SYSERROR(errno, "ignore_signal(%d)", sig); 59 } 60 } 61 62 void 63 isc_loopmgr_shutdown(isc_loopmgr_t *loopmgr) { 64 if (!atomic_compare_exchange_strong(&loopmgr->shuttingdown, 65 &(bool){ false }, true)) 66 { 67 return; 68 } 69 70 for (size_t i = 0; i < loopmgr->nloops; i++) { 71 isc_loop_t *loop = &loopmgr->loops[i]; 72 int r; 73 74 r = uv_async_send(&loop->shutdown_trigger); 75 UV_RUNTIME_CHECK(uv_async_send, r); 76 } 77 } 78 79 static void 80 isc__loopmgr_signal(void *arg, int signum) { 81 isc_loopmgr_t *loopmgr = (isc_loopmgr_t *)arg; 82 83 switch (signum) { 84 case SIGINT: 85 case SIGTERM: 86 isc_loopmgr_shutdown(loopmgr); 87 break; 88 default: 89 UNREACHABLE(); 90 } 91 } 92 93 static void 94 pause_loop(isc_loop_t *loop) { 95 isc_loopmgr_t *loopmgr = loop->loopmgr; 96 97 /* Quiesce this loop's own workers before going to the barrier. */ 98 for (isc_worklane_t lane = 0; lane < ISC_WORKLANE_COUNT; lane++) { 99 isc__workthread_pause(loop->workthreads[lane]); 100 } 101 102 rcu_thread_offline(); 103 104 loop->paused = true; 105 (void)isc_barrier_wait(&loopmgr->pausing); 106 } 107 108 static void 109 resume_loop(isc_loop_t *loop) { 110 isc_loopmgr_t *loopmgr = loop->loopmgr; 111 112 (void)isc_barrier_wait(&loopmgr->resuming); 113 loop->paused = false; 114 115 rcu_thread_online(); 116 117 for (isc_worklane_t lane = 0; lane < ISC_WORKLANE_COUNT; lane++) { 118 isc__workthread_resume(loop->workthreads[lane]); 119 } 120 } 121 122 static void 123 pauseresume_cb(uv_async_t *handle) { 124 isc_loop_t *loop = uv_handle_get_data(handle); 125 126 pause_loop(loop); 127 resume_loop(loop); 128 } 129 130 #define XX(uc, lc) \ 131 case UV_##uc: \ 132 fprintf(stderr, "%s, %s: dangling %p: %p.type = %s\n", \ 133 __func__, (char *)arg, handle->loop, handle, #lc); \ 134 break; 135 136 static void 137 loop_walk_cb(uv_handle_t *handle, void *arg) { 138 if (uv_is_closing(handle)) { 139 return; 140 } 141 142 switch (handle->type) { 143 UV_HANDLE_TYPE_MAP(XX) 144 default: 145 fprintf(stderr, "%s, %s: dangling %p: %p.type = %s\n", __func__, 146 (char *)arg, &handle->loop, handle, "unknown"); 147 } 148 } 149 150 static void 151 shutdown_trigger_close_cb(uv_handle_t *handle) { 152 isc_loop_t *loop = uv_handle_get_data(handle); 153 154 isc_loop_detach(&loop); 155 } 156 157 static void 158 destroy_cb(uv_async_t *handle) { 159 isc_loop_t *loop = uv_handle_get_data(handle); 160 161 /* Again, the first close callback here is called last */ 162 uv_close(&loop->async_trigger, isc__async_close); 163 uv_close(&loop->run_trigger, isc__job_close); 164 uv_close(&loop->destroy_trigger, NULL); 165 uv_close(&loop->pause_trigger, NULL); 166 uv_close(&loop->quiescent, NULL); 167 168 uv_walk(&loop->loop, loop_walk_cb, (char *)"destroy_cb"); 169 } 170 171 static void 172 shutdown_cb(uv_async_t *handle) { 173 isc_loop_t *loop = uv_handle_get_data(handle); 174 isc_loopmgr_t *loopmgr = loop->loopmgr; 175 176 /* Make sure, we can't be called again */ 177 uv_close(&loop->shutdown_trigger, shutdown_trigger_close_cb); 178 179 /* Mark this loop as shutting down */ 180 loop->shuttingdown = true; 181 182 if (DEFAULT_LOOP(loopmgr) == CURRENT_LOOP(loopmgr)) { 183 /* Stop the signal handlers */ 184 isc_signal_stop(loopmgr->sigterm); 185 isc_signal_stop(loopmgr->sigint); 186 187 /* Free the signal handlers */ 188 isc_signal_destroy(&loopmgr->sigterm); 189 isc_signal_destroy(&loopmgr->sigint); 190 } 191 192 for (isc_worklane_t lane = 0; lane < ISC_WORKLANE_COUNT; lane++) { 193 isc__workthread_shutdown(loop->workthreads[lane]); 194 } 195 196 enum cds_wfcq_ret ret = __cds_wfcq_splice_blocking( 197 &loop->async_jobs.head, &loop->async_jobs.tail, 198 &loop->teardown_jobs.head, &loop->teardown_jobs.tail); 199 INSIST(ret != CDS_WFCQ_RET_WOULDBLOCK); 200 int r = uv_async_send(&loop->async_trigger); 201 UV_RUNTIME_CHECK(uv_async_send, r); 202 } 203 204 static void 205 loop_init(isc_loop_t *loop, isc_loopmgr_t *loopmgr, uint32_t tid, 206 const char *kind) { 207 *loop = (isc_loop_t){ 208 .tid = tid, 209 .loopmgr = loopmgr, 210 .run_jobs = ISC_LIST_INITIALIZER, 211 }; 212 213 __cds_wfcq_init(&loop->async_jobs.head, &loop->async_jobs.tail); 214 __cds_wfcq_init(&loop->setup_jobs.head, &loop->setup_jobs.tail); 215 __cds_wfcq_init(&loop->teardown_jobs.head, &loop->teardown_jobs.tail); 216 217 int r = uv_loop_init(&loop->loop); 218 UV_RUNTIME_CHECK(uv_loop_init, r); 219 220 r = uv_async_init(&loop->loop, &loop->pause_trigger, pauseresume_cb); 221 UV_RUNTIME_CHECK(uv_async_init, r); 222 uv_handle_set_data(&loop->pause_trigger, loop); 223 224 r = uv_async_init(&loop->loop, &loop->shutdown_trigger, shutdown_cb); 225 UV_RUNTIME_CHECK(uv_async_init, r); 226 uv_handle_set_data(&loop->shutdown_trigger, loop); 227 228 r = uv_async_init(&loop->loop, &loop->async_trigger, isc__async_cb); 229 UV_RUNTIME_CHECK(uv_async_init, r); 230 uv_handle_set_data(&loop->async_trigger, loop); 231 232 r = uv_idle_init(&loop->loop, &loop->run_trigger); 233 UV_RUNTIME_CHECK(uv_idle_init, r); 234 uv_handle_set_data(&loop->run_trigger, loop); 235 236 r = uv_async_init(&loop->loop, &loop->destroy_trigger, destroy_cb); 237 UV_RUNTIME_CHECK(uv_async_init, r); 238 uv_handle_set_data(&loop->destroy_trigger, loop); 239 240 r = uv_prepare_init(&loop->loop, &loop->quiescent); 241 UV_RUNTIME_CHECK(uv_prepare_init, r); 242 uv_handle_set_data(&loop->quiescent, loop); 243 244 char name[16]; 245 snprintf(name, sizeof(name), "%s-%08" PRIx32, kind, tid); 246 isc_mem_create(&loop->mctx); 247 isc_mem_setname(loop->mctx, name); 248 249 isc_refcount_init(&loop->references, 1); 250 251 loop->magic = LOOP_MAGIC; 252 } 253 254 static void 255 quiescent_cb(uv_prepare_t *handle) { 256 UNUSED(handle); 257 258 #if defined(RCU_QSBR) 259 /* safe memory reclamation */ 260 rcu_quiescent_state(); 261 262 /* mark the thread offline when polling */ 263 rcu_thread_offline(); 264 #else 265 INSIST(!rcu_read_ongoing()); 266 #endif 267 } 268 269 static void 270 loop_close(isc_loop_t *loop) { 271 int r = uv_loop_close(&loop->loop); 272 UV_RUNTIME_CHECK(uv_loop_close, r); 273 274 INSIST(cds_wfcq_empty(&loop->async_jobs.head, &loop->async_jobs.tail)); 275 INSIST(ISC_LIST_EMPTY(loop->run_jobs)); 276 277 loop->magic = 0; 278 279 isc_mem_detach(&loop->mctx); 280 } 281 282 static void * 283 loop_thread(void *arg) { 284 isc_loop_t *loop = (isc_loop_t *)arg; 285 isc_loopmgr_t *loopmgr = loop->loopmgr; 286 /* Initialize the thread_local variables*/ 287 288 REQUIRE(isc__loop_local == NULL || isc__loop_local == loop); 289 isc__loop_local = loop; 290 291 isc__tid_init(loop->tid); 292 293 int r = uv_prepare_start(&loop->quiescent, quiescent_cb); 294 UV_RUNTIME_CHECK(uv_prepare_start, r); 295 296 for (isc_worklane_t lane = 0; lane < ISC_WORKLANE_COUNT; lane++) { 297 loop->workthreads[lane] = isc__workthread_create(loop, lane); 298 } 299 300 isc_barrier_wait(&loopmgr->starting); 301 302 enum cds_wfcq_ret ret = __cds_wfcq_splice_blocking( 303 &loop->async_jobs.head, &loop->async_jobs.tail, 304 &loop->setup_jobs.head, &loop->setup_jobs.tail); 305 INSIST(ret != CDS_WFCQ_RET_WOULDBLOCK); 306 307 r = uv_async_send(&loop->async_trigger); 308 UV_RUNTIME_CHECK(uv_async_send, r); 309 310 r = uv_run(&loop->loop, UV_RUN_DEFAULT); 311 UV_RUNTIME_CHECK(uv_run, r); 312 313 isc__loop_local = NULL; 314 315 /* Invalidate the loop early */ 316 loop->magic = 0; 317 318 isc_barrier_wait(&loopmgr->stopping); 319 320 for (isc_worklane_t lane = 0; lane < ISC_WORKLANE_COUNT; lane++) { 321 isc__workthread_destroy(&loop->workthreads[lane]); 322 } 323 324 return NULL; 325 } 326 327 /** 328 * Public 329 */ 330 331 static void 332 loop_destroy(isc_loop_t *loop) { 333 int r = uv_async_send(&loop->destroy_trigger); 334 UV_RUNTIME_CHECK(uv_async_send, r); 335 } 336 337 #if ISC_LOOP_TRACE 338 ISC_REFCOUNT_TRACE_IMPL(isc_loop, loop_destroy) 339 #else 340 ISC_REFCOUNT_IMPL(isc_loop, loop_destroy); 341 #endif 342 343 void 344 isc_loopmgr_create(isc_mem_t *mctx, uint32_t nloops, isc_loopmgr_t **loopmgrp) { 345 isc_loopmgr_t *loopmgr = NULL; 346 347 REQUIRE(loopmgrp != NULL && *loopmgrp == NULL); 348 REQUIRE(nloops > 0); 349 350 isc__tid_initcount(nloops); 351 352 loopmgr = isc_mem_get(mctx, sizeof(*loopmgr)); 353 *loopmgr = (isc_loopmgr_t){ 354 .nloops = nloops, 355 }; 356 357 isc_mem_attach(mctx, &loopmgr->mctx); 358 359 /* We need to double the number for loops */ 360 isc_barrier_init(&loopmgr->pausing, loopmgr->nloops); 361 isc_barrier_init(&loopmgr->resuming, loopmgr->nloops); 362 isc_barrier_init(&loopmgr->starting, 363 loopmgr->nloops * (1 + ISC_WORKLANE_COUNT)); 364 isc_barrier_init(&loopmgr->stopping, 365 loopmgr->nloops * (1 + ISC_WORKLANE_COUNT)); 366 367 loopmgr->loops = isc_mem_cget(loopmgr->mctx, loopmgr->nloops, 368 sizeof(loopmgr->loops[0])); 369 for (size_t i = 0; i < loopmgr->nloops; i++) { 370 isc_loop_t *loop = &loopmgr->loops[i]; 371 loop_init(loop, loopmgr, i, "loop"); 372 } 373 374 loopmgr->sigint = isc_signal_new(loopmgr, isc__loopmgr_signal, loopmgr, 375 SIGINT); 376 loopmgr->sigterm = isc_signal_new(loopmgr, isc__loopmgr_signal, loopmgr, 377 SIGTERM); 378 379 isc_signal_start(loopmgr->sigint); 380 isc_signal_start(loopmgr->sigterm); 381 382 loopmgr->magic = LOOPMGR_MAGIC; 383 384 *loopmgrp = loopmgr; 385 } 386 387 isc_job_t * 388 isc_loop_setup(isc_loop_t *loop, isc_job_cb cb, void *cbarg) { 389 REQUIRE(VALID_LOOP(loop)); 390 REQUIRE(cb != NULL); 391 392 isc_loopmgr_t *loopmgr = loop->loopmgr; 393 isc_job_t *job = isc_mem_get(loop->mctx, sizeof(*job)); 394 *job = (isc_job_t){ 395 .cb = cb, 396 .cbarg = cbarg, 397 }; 398 399 cds_wfcq_node_init(&job->wfcq_node); 400 401 REQUIRE(loop->tid == isc_tid() || !atomic_load(&loopmgr->running) || 402 atomic_load(&loopmgr->paused)); 403 404 cds_wfcq_enqueue(&loop->setup_jobs.head, &loop->setup_jobs.tail, 405 &job->wfcq_node); 406 407 return job; 408 } 409 410 isc_job_t * 411 isc_loop_teardown(isc_loop_t *loop, isc_job_cb cb, void *cbarg) { 412 REQUIRE(VALID_LOOP(loop)); 413 414 isc_loopmgr_t *loopmgr = loop->loopmgr; 415 isc_job_t *job = isc_mem_get(loop->mctx, sizeof(*job)); 416 *job = (isc_job_t){ 417 .cb = cb, 418 .cbarg = cbarg, 419 }; 420 cds_wfcq_node_init(&job->wfcq_node); 421 422 REQUIRE(loop->tid == isc_tid() || !atomic_load(&loopmgr->running) || 423 atomic_load(&loopmgr->paused)); 424 425 cds_wfcq_enqueue(&loop->teardown_jobs.head, &loop->teardown_jobs.tail, 426 &job->wfcq_node); 427 428 return job; 429 } 430 431 void 432 isc_loopmgr_setup(isc_loopmgr_t *loopmgr, isc_job_cb cb, void *cbarg) { 433 REQUIRE(VALID_LOOPMGR(loopmgr)); 434 REQUIRE(!atomic_load(&loopmgr->running) || 435 atomic_load(&loopmgr->paused)); 436 437 for (size_t i = 0; i < loopmgr->nloops; i++) { 438 isc_loop_t *loop = &loopmgr->loops[i]; 439 (void)isc_loop_setup(loop, cb, cbarg); 440 } 441 } 442 443 void 444 isc_loopmgr_teardown(isc_loopmgr_t *loopmgr, isc_job_cb cb, void *cbarg) { 445 REQUIRE(VALID_LOOPMGR(loopmgr)); 446 REQUIRE(!atomic_load(&loopmgr->running) || 447 atomic_load(&loopmgr->paused)); 448 449 for (size_t i = 0; i < loopmgr->nloops; i++) { 450 isc_loop_t *loop = &loopmgr->loops[i]; 451 (void)isc_loop_teardown(loop, cb, cbarg); 452 } 453 } 454 455 void 456 isc_loopmgr_run(isc_loopmgr_t *loopmgr) { 457 REQUIRE(VALID_LOOPMGR(loopmgr)); 458 RUNTIME_CHECK(atomic_compare_exchange_strong(&loopmgr->running, 459 &(bool){ false }, true)); 460 461 /* 462 * Always ignore SIGPIPE. 463 */ 464 ignore_signal(SIGPIPE, SIG_IGN); 465 466 /* 467 * The thread 0 is this one. 468 */ 469 for (size_t i = 1; i < loopmgr->nloops; i++) { 470 char name[32]; 471 isc_loop_t *loop = &loopmgr->loops[i]; 472 473 isc_thread_create(loop_thread, loop, &loop->thread); 474 475 snprintf(name, sizeof(name), "isc-loop-%04zu", i); 476 isc_thread_setname(loop->thread, name); 477 } 478 479 isc_thread_main(loop_thread, &loopmgr->loops[0]); 480 } 481 482 void 483 isc_loopmgr_pause(isc_loopmgr_t *loopmgr) { 484 REQUIRE(VALID_LOOPMGR(loopmgr)); 485 REQUIRE(isc_tid() != ISC_TID_UNKNOWN); 486 487 if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) { 488 isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, 489 ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1), 490 "loop exclusive mode: starting"); 491 } 492 493 for (size_t i = 0; i < loopmgr->nloops; i++) { 494 isc_loop_t *loop = &loopmgr->loops[i]; 495 496 /* Skip current loop */ 497 if (i == isc_tid()) { 498 continue; 499 } 500 501 int r = uv_async_send(&loop->pause_trigger); 502 UV_RUNTIME_CHECK(uv_async_send, r); 503 } 504 505 RUNTIME_CHECK(atomic_compare_exchange_strong(&loopmgr->paused, 506 &(bool){ false }, true)); 507 pause_loop(CURRENT_LOOP(loopmgr)); 508 509 if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) { 510 isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, 511 ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1), 512 "loop exclusive mode: started"); 513 } 514 } 515 516 void 517 isc_loopmgr_resume(isc_loopmgr_t *loopmgr) { 518 REQUIRE(VALID_LOOPMGR(loopmgr)); 519 520 if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) { 521 isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, 522 ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1), 523 "loop exclusive mode: ending"); 524 } 525 526 RUNTIME_CHECK(atomic_compare_exchange_strong(&loopmgr->paused, 527 &(bool){ true }, false)); 528 resume_loop(CURRENT_LOOP(loopmgr)); 529 530 if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) { 531 isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, 532 ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1), 533 "loop exclusive mode: ended"); 534 } 535 } 536 537 void 538 isc_loopmgr_destroy(isc_loopmgr_t **loopmgrp) { 539 isc_loopmgr_t *loopmgr = NULL; 540 541 REQUIRE(loopmgrp != NULL); 542 REQUIRE(VALID_LOOPMGR(*loopmgrp)); 543 544 loopmgr = *loopmgrp; 545 *loopmgrp = NULL; 546 547 RUNTIME_CHECK(atomic_compare_exchange_strong(&loopmgr->running, 548 &(bool){ true }, false)); 549 550 /* First wait for all loops to finish */ 551 for (size_t i = 1; i < loopmgr->nloops; i++) { 552 isc_loop_t *loop = &loopmgr->loops[i]; 553 isc_thread_join(loop->thread, NULL); 554 } 555 556 loopmgr->magic = 0; 557 558 for (size_t i = 0; i < loopmgr->nloops; i++) { 559 isc_loop_t *loop = &loopmgr->loops[i]; 560 loop_close(loop); 561 } 562 isc_mem_cput(loopmgr->mctx, loopmgr->loops, loopmgr->nloops, 563 sizeof(loopmgr->loops[0])); 564 565 isc_barrier_destroy(&loopmgr->starting); 566 isc_barrier_destroy(&loopmgr->stopping); 567 isc_barrier_destroy(&loopmgr->resuming); 568 isc_barrier_destroy(&loopmgr->pausing); 569 570 isc_mem_putanddetach(&loopmgr->mctx, loopmgr, sizeof(*loopmgr)); 571 } 572 573 uint32_t 574 isc_loopmgr_nloops(isc_loopmgr_t *loopmgr) { 575 REQUIRE(VALID_LOOPMGR(loopmgr)); 576 577 return loopmgr->nloops; 578 } 579 580 isc_mem_t * 581 isc_loop_getmctx(isc_loop_t *loop) { 582 REQUIRE(VALID_LOOP(loop)); 583 584 return loop->mctx; 585 } 586 587 isc_loop_t * 588 isc_loop_main(isc_loopmgr_t *loopmgr) { 589 REQUIRE(VALID_LOOPMGR(loopmgr)); 590 591 return DEFAULT_LOOP(loopmgr); 592 } 593 594 isc_loop_t * 595 isc_loop_get(isc_loopmgr_t *loopmgr, uint32_t tid) { 596 REQUIRE(VALID_LOOPMGR(loopmgr)); 597 REQUIRE(tid < loopmgr->nloops); 598 599 return LOOP(loopmgr, tid); 600 } 601 602 void 603 isc_loopmgr_blocking(isc_loopmgr_t *loopmgr) { 604 REQUIRE(VALID_LOOPMGR(loopmgr)); 605 606 isc_signal_stop(loopmgr->sigterm); 607 isc_signal_stop(loopmgr->sigint); 608 } 609 610 void 611 isc_loopmgr_nonblocking(isc_loopmgr_t *loopmgr) { 612 REQUIRE(VALID_LOOPMGR(loopmgr)); 613 614 isc_signal_start(loopmgr->sigint); 615 isc_signal_start(loopmgr->sigterm); 616 } 617 618 isc_loopmgr_t * 619 isc_loop_getloopmgr(isc_loop_t *loop) { 620 REQUIRE(VALID_LOOP(loop)); 621 622 return loop->loopmgr; 623 } 624 625 isc_time_t 626 isc_loop_now(isc_loop_t *loop) { 627 REQUIRE(VALID_LOOP(loop)); 628 629 uint64_t msec = uv_now(&loop->loop); 630 isc_time_t t = { 631 .seconds = msec / MS_PER_SEC, 632 .nanoseconds = (msec % MS_PER_SEC) * NS_PER_MS, 633 }; 634 635 return t; 636 } 637 638 bool 639 isc_loop_shuttingdown(isc_loop_t *loop) { 640 REQUIRE(VALID_LOOP(loop)); 641 REQUIRE(loop->tid == isc_tid()); 642 643 return loop->shuttingdown; 644 } 645 646 isc__workthread_t * 647 isc__loopmgr_workthread(isc_loop_t *loop, isc_worklane_t lane) { 648 REQUIRE(VALID_LOOP(loop)); 649 REQUIRE(lane < ISC_WORKLANE_COUNT); 650 651 return loop->workthreads[lane]; 652 } 653 654 void 655 isc__loopmgr_starting(isc_loop_t *loop) { 656 isc_barrier_wait(&loop->loopmgr->starting); 657 } 658 659 void 660 isc__loopmgr_stopping(isc_loop_t *loop) { 661 isc_barrier_wait(&loop->loopmgr->stopping); 662 } 663