]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
New event loop handling API
authorOndřej Surý <ondrej@isc.org>
Tue, 26 Jul 2022 11:03:22 +0000 (13:03 +0200)
committerOndřej Surý <ondrej@isc.org>
Thu, 25 Aug 2022 10:24:29 +0000 (12:24 +0200)
This commit introduces new APIs for applications and signal handling,
intended to replace isc_app for applications built on top of libisc.

* isc_app will be replaced with isc_loopmgr, which handles the
  starting and stopping of applications. In isc_loopmgr, the main
  thread is not blocked, but is part of the working thread set.
  The loop manager will start a number of threads, each with a
  uv_loop event loop running. Setup and teardown functions can be
  assigned which will run when the loop starts and stops, and
  jobs can be scheduled to run in the meantime. When
  isc_loopmgr_shutdown() is run from any the loops, all loops
  will shut down and the application can terminate.

* signal handling will now be handled with a separate isc_signal unit.
  isc_loopmgr only handles SIGTERM and SIGINT for application
  termination, but the application may install additional signal
  handlers, such as SIGHUP as a signal to reload configuration.

* new job running primitives, isc_job and isc_async, have been added.
  Both units schedule callbacks (specifying a callback function and
  argument) on an event loop. The difference is that isc_job unit is
  unlocked and not thread-safe, so it can be used to efficiently
  run jobs in the same thread, while isc_async is thread-safe and
  uses locking, so it can be used to pass jobs from one thread to
  another.

* isc_tid will be used to track the thread ID in isc_loop worker
  threads.

* unit tests have been added for the new APIs.

27 files changed:
doc/dev/loopmgr.md [new file with mode: 0644]
lib/isc/Makefile.am
lib/isc/async.c [new file with mode: 0644]
lib/isc/include/isc/async.h [new file with mode: 0644]
lib/isc/include/isc/job.h [new file with mode: 0644]
lib/isc/include/isc/loop.h [new file with mode: 0644]
lib/isc/include/isc/refcount.h
lib/isc/include/isc/signal.h [new file with mode: 0644]
lib/isc/include/isc/tid.h [new file with mode: 0644]
lib/isc/include/isc/types.h
lib/isc/include/isc/uv.h
lib/isc/include/isc/work.h [new file with mode: 0644]
lib/isc/job.c [new file with mode: 0644]
lib/isc/job_p.h [new file with mode: 0644]
lib/isc/loop.c [new file with mode: 0644]
lib/isc/loop_p.h [new file with mode: 0644]
lib/isc/signal.c [new file with mode: 0644]
lib/isc/tid.c [new file with mode: 0644]
lib/isc/trampoline.c
lib/isc/work.c [new file with mode: 0644]
tests/include/tests/isc.h
tests/isc/Makefile.am
tests/isc/async_test.c [new file with mode: 0644]
tests/isc/job_test.c [new file with mode: 0644]
tests/isc/loop_test.c [new file with mode: 0644]
tests/isc/work_test.c [new file with mode: 0644]
tests/libtest/isc.c

diff --git a/doc/dev/loopmgr.md b/doc/dev/loopmgr.md
new file mode 100644 (file)
index 0000000..9c45181
--- /dev/null
@@ -0,0 +1,106 @@
+<!--
+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.
+-->
+
+# Loop Manager
+
+This document aims to describe the design of the basic event loop handling in
+the BIND 9.
+
+Every application is expected to create and use a single ``isc_loopmgr_t``
+instance, but the ``isc_loopmgr`` API itself doesn't enforce this requirement.
+
+## Event Loops
+
+The loop manager creates *N* event loops (of type ``isc_loop_t``), where *N* is
+specified by the caller when creating the loop manager.  The number of event
+loops is usually same as the number of logical CPUs.  The minimum *N* is 1, and
+maximum is limited only by the machine resources.
+
+For each event loop, a thread is created by the loop manager.  The
+``isc_loop_t`` object itself is built on top of ``uv_loop_t``.  ``uv_loop_t``
+is not thread-safe, and this is also true for ``isc_loop_t``.  If you need to
+run an event on a different event loop, see below for the ``isc_async`` API.
+
+The application can get a reference to the current event loop using
+``isc_loop_current()``, to the main event loop (loop 0) that will always exist
+using ``isc_loop_main()`` and to an arbitrary event loop using
+``isc_loop_get()``.
+
+## Application Start and Stop
+
+Every application MUST add its initial events using ``isc_loopmgr_setup()`` to
+be run on all initialized event loops or ``isc_loop_setup()`` to be run on a
+selected event loop.
+
+Applications MAY also add events to be run when the application is shut down by
+calling ``isc_loopmgr_teardown()`` (or ``isc_loop_teardown()`` for a specific
+event loop).
+
+After the setup and teardown events have been configured, the application may
+be started via ``isc_loopmgr_run()``.  ``isc_loopmgr_run()`` will block for the
+caller while event loops are running.  When the work is done,
+``isc_loopmgr_shutdown()`` must be run from within one of the event loops; this
+will cause all loops to be shut down and ``isc_loopmgr_run()`` to return.
+
+The most notable change from the ``isc_app`` API is the lack of a blocked
+``main`` thread.  The loop manager starts the **main** event loop on the
+**main** thread when the application is started.
+
+This API now replaces the old ``isc_app`` API.
+
+## Signal Handling
+
+The loop manager itself takes care of handling the ``SIGTERM`` and ``SIGINT``
+signals, but the application MAY add more handlers via ``isc_signal`` API.  In
+``named``, for example, ``SIGHUP`` is used to trigger an application reload.
+
+## Event scheduling
+
+The application may add events to the event loop via ``isc_job_run()`` for jobs
+on the same event loop, or via ``isc_async_run()`` for jobs to be passed to
+other event loops.  Both functions take the event loop, the callback and the
+callback argument as parameters.
+
+Generally ``isc_job_run()`` is more direct, as it schedules the event directly
+on the event loop and doesn't use locking, and should be preferred unless you
+need to run the event on a different thread.
+
+``isc_async_run()`` is the only new thread-safe function provided by the loop
+manager, uses locked list to collect new jobs and uv_async() primitive to
+enqueue the collected jobs onto the event loop.
+
+## Tasks
+
+The ``isc_task`` API has been modified to run the tasks directly on the loop
+manager.  The new ``isc_job`` and ``isc_async`` APIs are preferred for simple
+events; the ``isc_task`` API is provided for backward-compatibility purposes
+and thus is also thread-safe because it uses locking and uv_async() to enqueue
+events onto the event loop.
+
+## Timers
+
+The ``isc_timer`` API is now built on top of the ``uv_timer_t`` object.  It has
+been changed to support only ``ticker`` and ``once`` timers, and now uses
+``isc_timer_start()`` and ``isc_timer_stop()`` instead of changing the timer
+type to ``inactive``.  The ``isc_timer_t`` object is not thread-safe.
+
+## Network Manager
+
+The network manager has been changed to use the loop manager event loops
+instead of managing its own event loops.
+
+The new network manager calls are not thread-safe; all connect/read/write
+functions MUST be called from the thread that created the network manager
+socket.
+
+The ``isc_nm_listen*()`` functions MUST be called from the ``main`` loop.
index b61c8c88f2e3b243c1ca4829a312eb14da671118..78fbddbc8c5932c9205b7e2f7449c2fa3af7cf68 100644 (file)
@@ -9,6 +9,7 @@ libisc_la_HEADERS =                     \
        include/isc/app.h               \
        include/isc/assertions.h        \
        include/isc/astack.h            \
+       include/isc/async.h             \
        include/isc/atomic.h            \
        include/isc/attributes.h        \
        include/isc/backtrace.h         \
@@ -42,10 +43,12 @@ libisc_la_HEADERS =                 \
        include/isc/httpd.h             \
        include/isc/interfaceiter.h     \
        include/isc/iterated_hash.h     \
+       include/isc/job.h               \
        include/isc/lang.h              \
        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                \
@@ -77,6 +80,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              \
@@ -90,6 +94,7 @@ libisc_la_HEADERS =                   \
        include/isc/syslog.h            \
        include/isc/task.h              \
        include/isc/thread.h            \
+       include/isc/tid.h               \
        include/isc/time.h              \
        include/isc/timer.h             \
        include/isc/tls.h               \
@@ -98,7 +103,8 @@ libisc_la_HEADERS =                  \
        include/isc/url.h               \
        include/isc/utf8.h              \
        include/isc/util.h              \
-       include/isc/uv.h
+       include/isc/uv.h                \
+       include/isc/work.h
 
 libisc_la_SOURCES =            \
        $(libisc_la_HEADERS)    \
@@ -114,6 +120,7 @@ libisc_la_SOURCES =         \
        app.c                   \
        assertions.c            \
        astack.c                \
+       async.c                 \
        backtrace.c             \
        base32.c                \
        base64.c                \
@@ -145,9 +152,13 @@ libisc_la_SOURCES =                \
        interfaceiter.c         \
        iterated_hash.c         \
        jemalloc_shim.h         \
+       job.c                   \
+       job_p.h                 \
        lex.c                   \
        lib.c                   \
        log.c                   \
+       loop.c                  \
+       loop_p.h                \
        managers.c              \
        md.c                    \
        mem.c                   \
@@ -178,6 +189,7 @@ libisc_la_SOURCES =         \
        rwlock.c                \
        safe.c                  \
        serial.c                \
+       signal.c                \
        siphash.c               \
        sockaddr.c              \
        stats.c                 \
@@ -189,6 +201,7 @@ libisc_la_SOURCES =         \
        task.c                  \
        task_p.h                \
        thread.c                \
+       tid.c                   \
        time.c                  \
        timer.c                 \
        timer_p.h               \
@@ -199,7 +212,8 @@ libisc_la_SOURCES =         \
        trampoline_p.h          \
        url.c                   \
        utf8.c                  \
-       uv.c
+       uv.c                    \
+       work.c
 
 libisc_la_CPPFLAGS =           \
        $(AM_CPPFLAGS)          \
diff --git a/lib/isc/async.c b/lib/isc/async.c
new file mode 100644 (file)
index 0000000..03e2cbd
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * 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 <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <isc/async.h>
+#include <isc/atomic.h>
+#include <isc/barrier.h>
+#include <isc/condition.h>
+#include <isc/job.h>
+#include <isc/list.h>
+#include <isc/loop.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/signal.h>
+#include <isc/strerr.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+#include <isc/uv.h>
+#include <isc/work.h>
+
+#include "job_p.h"
+#include "loop_p.h"
+
+void
+isc_async_run(isc_loop_t *loop, isc_job_cb cb, void *cbarg) {
+       int r;
+       isc_job_t *job = NULL;
+
+       REQUIRE(VALID_LOOP(loop));
+       REQUIRE(cb != NULL);
+
+       job = isc__job_new(loop, cb, cbarg);
+
+       /* Now send the half initialized job to loop queue */
+       LOCK(&loop->queue_lock);
+       ISC_LIST_APPEND(loop->queue_jobs, job, link);
+       UNLOCK(&loop->queue_lock);
+
+       r = uv_async_send(&loop->queue_trigger);
+       UV_RUNTIME_CHECK(uv_async_send, r);
+}
diff --git a/lib/isc/include/isc/async.h b/lib/isc/include/isc/async.h
new file mode 100644 (file)
index 0000000..a413f27
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+/*! \file isc/async.h
+ * \brief The isc_async unit provides a way to schedule jobs on any isc
+ * event loop (isc_loop unit)
+ *
+ * The unit is built around the uv_async_t primitive and locked list with
+ * isc_job_cb.  Jobs are first scheduled onto the locked list, then the
+ * uv_async_send() is called and the uv_async_t callback processes the enqueued
+ * jobs are scheduled to be run on the isc event loop.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+#include <isc/job.h>
+#include <isc/lang.h>
+#include <isc/loop.h>
+#include <isc/mem.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_async_run(isc_loop_t *loop, isc_job_cb cb, void *cbarg);
+/*%<
+ * Schedule the job callback 'cb' to be run on the 'loop' event loop.
+ *
+ * Requires:
+ *
+ *\li  'loop' is a valid isc event loop
+ *\li  'cb' is a callback function, must be non-NULL
+ *\li  'cbarg' is passed to the 'cb' as the only argument, may be NULL
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/job.h b/lib/isc/include/isc/job.h
new file mode 100644 (file)
index 0000000..2c75e30
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+/*! \file isc/job.h
+ * \brief The isc_job unit provides a way to schedule jobs on the
+ * currently running isc event loop. This is distinct from isc_async,
+ * which can be used to send events to another, specified loop.
+ *
+ * The unit is built around the uv_idle_t primitive and it directly schedules
+ * the callback to be run on the isc event loop.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/mem.h>
+#include <isc/refcount.h>
+#include <isc/types.h>
+
+typedef void (*isc_job_cb)(void *);
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_job_run(isc_loopmgr_t *loopmgr, isc_job_cb cb, void *cbarg);
+/*%<
+ * Schedule the job callback 'cb' to be run on the currently
+ * running event loop.
+ *
+ * Requires:
+ *
+ *\li  'loopmgr' is the active loop manager.
+ *\li  'cb' is a callback function, must be non-NULL
+ *\li  'cbarg' is passed to the 'cb' as the only argument, may be NULL
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/loop.h b/lib/isc/include/isc/loop.h
new file mode 100644 (file)
index 0000000..d02977e
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * 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 <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/mem.h>
+#include <isc/refcount.h>
+#include <isc/types.h>
+
+typedef void (*isc_job_cb)(void *);
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_loopmgr_create(isc_mem_t *mctx, uint32_t nloops, isc_loopmgr_t **loopmgrp);
+/*%<
+ * Create a loop manager supporting 'nloops' loops.
+ *
+ * Requires:
+ *\li  'nloops' is greater than 0.
+ */
+
+void
+isc_loopmgr_destroy(isc_loopmgr_t **loopmgrp);
+/*%<
+ * Destroy the loop manager pointed to by 'loopmgrp'.
+ *
+ * Requires:
+ *\li  'loopmgr' points to a valid loop manager.
+ */
+
+void
+isc_loopmgr_shutdown(isc_loopmgr_t *loopmgr);
+/*%<
+ * Request shutdown of the loop manager 'loopmgr'.
+ *
+ * This will stop all signal handlers and send shutdown events to
+ * all active loops. As a final action on shutting down, each loop
+ * will run the function (or functions) set by isc_loopmgr_teardown()
+ * or isc_loop_teardown().
+ *
+ * Requires:
+ *\li  'loopmgr' is a valid loop manager.
+ */
+
+void
+isc_loopmgr_run(isc_loopmgr_t *loopmgr);
+/*%<
+ * Run the loops in 'loopmgr'. Each loop will start by running the
+ * function (or functions) set by isc_loopmgr_setup() or isc_loop_setup().
+ *
+ * Requires:
+ *\li  'loopmgr' is a valid loop manager.
+ */
+
+void
+isc_loopmgr_pause(isc_loopmgr_t *loopmgr);
+/*%<
+ * Send pause events to all running loops in 'loopmgr' except the
+ * current one. This can only be called from a running loop.
+ * All the paused loops will wait until isc_loopmgr_resume() is
+ * run in the calling loop before continuing.
+ *
+ * Requires:
+ *\li  'loopmgr' is a valid loop manager.
+ *\li  We are in a running loop.
+ */
+
+void
+isc_loopmgr_resume(isc_loopmgr_t *loopmgr);
+/*%<
+ * Send resume events to all paused loops in 'loopmgr'. This can
+ * only be called by a running loop (which must therefore be the
+ * loop that called isc_loopmgr_pause()).
+ *
+ * Requires:
+ *\li  'loopmgr' is a valid loop manager.
+ *\li  We are in a running loop.
+ */
+
+uint32_t
+isc_loopmgr_nloops(isc_loopmgr_t *loopmgr);
+
+isc_job_t *
+isc_loop_setup(isc_loop_t *loop, isc_job_cb cb, void *cbarg);
+isc_job_t *
+isc_loop_teardown(isc_loop_t *loop, isc_job_cb cb, void *cbarg);
+/*%<
+ * Schedule actions to be run when starting, and when shutting down,
+ * one of the loops in a loop manager.
+ *
+ * Requires:
+ *\li  'loop' is a valid loop.
+ *\li  The loop manager associated with 'loop' is paused or has not
+ *     yet been started.
+ */
+
+void
+isc_loop_nosetup(isc_loop_t *loop, isc_job_t *job);
+void
+isc_loop_noteardown(isc_loop_t *loop, isc_job_t *job);
+
+void
+isc_loopmgr_setup(isc_loopmgr_t *loopmgr, isc_job_cb cb, void *cbarg);
+void
+isc_loopmgr_teardown(isc_loopmgr_t *loopmgr, isc_job_cb cb, void *cbarg);
+/*%<
+ * Schedule actions to be run when starting, and when shutting down,
+ * *all* of the loops in loopmgr.
+ *
+ * This is the same as running isc_loop_setup() or
+ * isc_loop_teardown() on each of the loops in turn.
+ *
+ * Requires:
+ *\li  'loopmgr' is a valid loop manager.
+ *\li  'loopmgr' is paused or has not yet been started.
+ */
+
+isc_mem_t *
+isc_loop_getmctx(isc_loop_t *loop);
+/*%<
+ * Return a pointer to the a memory context that was created for
+ * 'loop' when it was initialized.
+ *
+ * Requires:
+ *\li  'loop' is a valid loop.
+ */
+
+isc_loop_t *
+isc_loop_main(isc_loopmgr_t *loopmgr);
+/*%<
+ * Returns the main loop for the 'loopmgr' (which is 'loopmgr->loops[0]',
+ * regardless of how many loops there are).
+ *
+ * Requires:
+ *\li  'loopmgr' is a valid loop manager.
+ */
+
+isc_loop_t *
+isc_loop_current(isc_loopmgr_t *loopmgr);
+/*%<
+ * Returns the loop object from which the function has been called,
+ * or NULL if not called from a loop.
+ *
+ * Requires:
+ *\li  'loopmgr' is a valid loop manager.
+ */
+
+isc_loop_t *
+isc_loop_get(isc_loopmgr_t *loopmgr, uint32_t tid);
+/*%<
+ * Return the loop object associated with the 'tid' threadid
+ *
+ * Requires:
+ *\li  'loopmgr' is a valid loop manager.
+ *\li   'tid' is smaller than number of initialized loops
+ */
+
+void
+isc_loopmgr_blocking(isc_loopmgr_t *loopmgr);
+void
+isc_loopmgr_nonblocking(isc_loopmgr_t *loopmgr);
+/*%
+ * isc_loopmgr_blocking() stops the SIGINT and SIGTERM signal handlers
+ * during blocking operations, for example while waiting for user
+ * interaction; isc_loopmgr_nonblocking() restarts them.
+ *
+ * Requires:
+ *\li  'loopmgr' is a valid loop manager.
+ */
+ISC_LANG_ENDDECLS
index 37f05b9cddf3a6d52f5932d0f06b66e772ebbe89..bf0a66e32b8d83d07ae8c3b2c6b7f55ac71111f3 100644 (file)
@@ -149,4 +149,34 @@ isc_refcount_decrement(isc_refcount_t *target) {
                ISC_INSIST(_refs > 0);                                \
        } while (0)
 
+#define ISC_REFCOUNT_DECL(name)                             \
+       void name##_ref(name##_t *ptr);                     \
+       void name##_unref(name##_t *ptr);                   \
+       void name##_attach(name##_t *ptr, name##_t **ptrp); \
+       void name##_detach(name##_t **ptrp)
+
+#define ISC_REFCOUNT_IMPL(name, destroy)                             \
+       void name##_ref(name##_t *ptr) {                             \
+               REQUIRE(ptr != NULL);                                \
+               isc_refcount_increment(&ptr->references);            \
+       }                                                            \
+                                                                     \
+       void name##_unref(name##_t *ptr) {                           \
+               REQUIRE(ptr != NULL);                                \
+               if (isc_refcount_decrement(&ptr->references) == 1) { \
+                       destroy(ptr);                                \
+               }                                                    \
+       }                                                            \
+       void name##_attach(name##_t *ptr, name##_t **ptrp) {         \
+               REQUIRE(ptrp != NULL && *ptrp == NULL);              \
+               name##_ref(ptr);                                     \
+               *ptrp = ptr;                                         \
+       }                                                            \
+                                                                     \
+       void name##_detach(name##_t **ptrp) {                        \
+               name##_t *ptr = *ptrp;                               \
+               *ptrp = NULL;                                        \
+               name##_unref(ptr);                                   \
+       }
+
 ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/signal.h b/lib/isc/include/isc/signal.h
new file mode 100644 (file)
index 0000000..af13d72
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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 <stdlib.h>
+
+#include <isc/lang.h>
+#include <isc/loop.h>
+
+typedef void (*isc_signal_cb)(void *, int);
+
+ISC_LANG_BEGINDECLS
+
+isc_signal_t *
+isc_signal_new(isc_loopmgr_t *loopmgr, isc_signal_cb cb, void *cbarg,
+              int signum);
+/*%<
+ * Create a new signal handler for loop manager 'loopmgr', handling
+ * the signal value 'signum'.
+ *
+ * After isc_signal_start() is called on the returned signal handler,
+ * and until isc_signal_stop() is called, if the running process receives
+ * signal 'signum', 'cb' will be run with argument 'cbarg'.
+ */
+
+void
+isc_signal_destroy(isc_signal_t **signalp);
+/*%<
+ * Free the memory allocated by isc_signal_new().
+ */
+
+void
+isc_signal_start(isc_signal_t *signal);
+/*%<
+ * Start using the signal handler 'signal'.
+ */
+
+void
+isc_signal_stop(isc_signal_t *signal);
+/*%<
+ * Stop using the signal handler 'signal'. (It can be restarted with
+ * isc_signal_start().)
+ */
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/tid.h b/lib/isc/include/isc/tid.h
new file mode 100644 (file)
index 0000000..9868573
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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 <inttypes.h>
+
+#include <isc/lang.h>
+
+ISC_LANG_BEGINDECLS
+
+#define ISC_TID_UNKNOWN UINT32_MAX
+
+uint32_t
+isc_tid(void);
+/*%<
+ * Returns the thread ID of the currently-running loop.
+ */
+
+/* Private */
+
+void
+isc__tid_init(uint32_t tid);
+
+ISC_LANG_ENDDECLS
index d7d9bf0b7cf79724c4feb9d2e77a116649cd2275..fb951dc4c89fc90ea2bbd1d12bcec6991d250de1 100644 (file)
@@ -55,16 +55,19 @@ typedef struct isc_httpdurl isc_httpdurl_t;        /*%< HTTP URL */
 typedef void(isc_httpdondestroy_t)(void *); /*%< Callback on destroying httpd */
 typedef struct isc_interface    isc_interface_t;     /*%< Interface */
 typedef struct isc_interfaceiter isc_interfaceiter_t; /*%< Interface Iterator */
-typedef struct isc_lex          isc_lex_t;           /*%< Lex */
-typedef struct isc_log          isc_log_t;           /*%< Log */
-typedef struct isc_logcategory  isc_logcategory_t;   /*%< Log Category */
-typedef struct isc_logconfig    isc_logconfig_t;     /*%< Log Configuration */
-typedef struct isc_logmodule    isc_logmodule_t;     /*%< Log Module */
-typedef struct isc_mem          isc_mem_t;           /*%< Memory */
-typedef struct isc_mempool      isc_mempool_t;       /*%< Memory Pool */
-typedef struct isc_netaddr      isc_netaddr_t;       /*%< Net Address */
-typedef struct isc_netprefix    isc_netprefix_t;     /*%< Net Prefix */
-typedef struct isc_nm           isc_nm_t;            /*%< Network manager */
+typedef struct isc_job          isc_job_t;
+typedef struct isc_lex          isc_lex_t;         /*%< Lex */
+typedef struct isc_log          isc_log_t;         /*%< Log */
+typedef struct isc_logcategory  isc_logcategory_t; /*%< Log Category */
+typedef struct isc_logconfig    isc_logconfig_t;   /*%< Log Configuration */
+typedef struct isc_logmodule    isc_logmodule_t;   /*%< Log Module */
+typedef struct isc_loop                 isc_loop_t;        /*%< Event loop */
+typedef struct isc_loopmgr      isc_loopmgr_t;     /*%< Event loop manager */
+typedef struct isc_mem          isc_mem_t;         /*%< Memory */
+typedef struct isc_mempool      isc_mempool_t;     /*%< Memory Pool */
+typedef struct isc_netaddr      isc_netaddr_t;     /*%< Net Address */
+typedef struct isc_netprefix    isc_netprefix_t;   /*%< Net Prefix */
+typedef struct isc_nm           isc_nm_t;          /*%< Network manager */
 typedef struct isc_nmsocket     isc_nmsocket_t; /*%< Network manager socket */
 typedef struct isc_nmhandle     isc_nmhandle_t; /*%< Network manager handle */
 typedef struct isc_portset      isc_portset_t;  /*%< Port Set */
@@ -72,6 +75,7 @@ typedef struct isc_quota       isc_quota_t;    /*%< Quota */
 typedef struct isc_ratelimiter  isc_ratelimiter_t;   /*%< Rate Limiter */
 typedef struct isc_region       isc_region_t;        /*%< Region */
 typedef uint64_t                isc_resourcevalue_t; /*%< Resource Value */
+typedef struct isc_signal       isc_signal_t;        /*%< Signal handler */
 typedef struct isc_sockaddr     isc_sockaddr_t;      /*%< Socket Address */
 typedef ISC_LIST(isc_sockaddr_t) isc_sockaddrlist_t;  /*%< Socket Address List
                                                       * */
@@ -85,6 +89,8 @@ typedef struct isc_textregion isc_textregion_t; /*%< Text Region */
 typedef struct isc_time              isc_time_t;       /*%< Time */
 typedef struct isc_timer      isc_timer_t;     /*%< Timer */
 typedef struct isc_timermgr   isc_timermgr_t;  /*%< Timer Manager */
+typedef struct isc_work              isc_work_t;       /*%< Work offloaded to an
+                                                *   external thread */
 
 #if HAVE_LIBNGHTTP2
 typedef struct isc_nm_http_endpoints isc_nm_http_endpoints_t;
index c0f0d54154adc02a9b1911ffb4989be3ef8bd079..25602d5717fb5e016f4f4e1e2a3b1523703c4c8a 100644 (file)
@@ -17,6 +17,7 @@
 #include <uv.h>
 
 #include <isc/result.h>
+#include <isc/tid.h>
 
 /*
  * These functions were introduced in newer libuv, but we still
@@ -128,3 +129,38 @@ isc__uverr2result(int uverr, bool dolog, const char *file, unsigned int line,
        uv_handle_set_data((uv_handle_t *)(handle), (data))
 #define uv_handle_get_data(handle) uv_handle_get_data((uv_handle_t *)(handle))
 #define uv_close(handle, close_cb) uv_close((uv_handle_t *)handle, close_cb)
+
+#if UV_TRACE_INIT
+
+#define uv_idle_init(loop, idle)                                          \
+       ({                                                                \
+               int __r = uv_idle_init(loop, idle);                       \
+               fprintf(stderr, "%" PRIu32 ":%s_:uv_idle_init(%p, %p)\n", \
+                       isc_tid(), __func__, loop, idle);                 \
+               __r;                                                      \
+       })
+
+#define uv_timer_init(loop, timer)                                         \
+       ({                                                                 \
+               int __r = uv_timer_init(loop, timer);                      \
+               fprintf(stderr, "%" PRIu32 ":%s_:uv_timer_init(%p, %p)\n", \
+                       isc_tid(), __func__, loop, timer);                 \
+               __r;                                                       \
+       })
+
+#define uv_async_init(loop, async, async_cb)                                   \
+       ({                                                                     \
+               int __r = uv_async_init(loop, async, async_cb);                \
+               fprintf(stderr, "%" PRIu32 ":%s_:uv_timer_init(%p, %p, %p)\n", \
+                       isc_tid(), __func__, loop, async, async_cb);           \
+               __r;                                                           \
+       })
+
+#define uv_close(handle, close_cb)                                    \
+       ({                                                            \
+               uv_close(handle, close_cb);                           \
+               fprintf(stderr, "%" PRIu32 ":%s_:uv_close(%p, %p)\n", \
+                       isc_tid(), __func__, handle, close_cb);       \
+       })
+
+#endif
diff --git a/lib/isc/include/isc/work.h b/lib/isc/include/isc/work.h
new file mode 100644 (file)
index 0000000..df196e9
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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 <stdlib.h>
+
+#include <isc/lang.h>
+#include <isc/loop.h>
+
+typedef void (*isc_work_cb)(void *arg);
+typedef void (*isc_after_work_cb)(void *arg);
+typedef struct isc_work isc_work_t;
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_work_enqueue(isc_loop_t *loop, isc_work_cb work_cb,
+                isc_after_work_cb after_work_cb, void *cbarg);
+/*%<
+ * Schedules work to be handled by the libuv thread pool (see uv_work_t).
+ * The function specified in `work_cb` will be run by a thread in the
+ * thread pool; when complete, the `after_work_cb` function will run
+ * in 'loop' to inform the caller that the work was completed.
+ *
+ * Requires:
+ * \li 'loop' is a valid event loop.
+ * \li 'work_cb' and 'after_work_cb' are not NULL.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/job.c b/lib/isc/job.c
new file mode 100644 (file)
index 0000000..904b537
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * 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 <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <isc/atomic.h>
+#include <isc/barrier.h>
+#include <isc/condition.h>
+#include <isc/job.h>
+#include <isc/list.h>
+#include <isc/loop.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/signal.h>
+#include <isc/strerr.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+#include <isc/uv.h>
+#include <isc/work.h>
+
+#include "job_p.h"
+#include "loop_p.h"
+
+#define JOB_MAGIC    ISC_MAGIC('J', 'O', 'B', ' ')
+#define VALID_JOB(t) ISC_MAGIC_VALID(t, JOB_MAGIC)
+
+/*
+ * Private: static
+ */
+
+static void
+isc__job_close_cb(uv_handle_t *handle) {
+       isc_job_t *job = uv_handle_get_data(handle);
+       isc_loop_t *loop = job->loop;
+
+       REQUIRE(loop == isc_loop_current(job->loop->loopmgr));
+
+       isc_mem_put(loop->mctx, job, sizeof(*job));
+
+       isc_loop_detach(&loop);
+}
+
+static void
+isc__job_destroy(isc_job_t *job) {
+       REQUIRE(VALID_JOB(job));
+       REQUIRE(job->loop == isc_loop_current(job->loop->loopmgr));
+
+       job->magic = 0;
+
+       uv_close(&job->idle, isc__job_close_cb);
+}
+
+static void
+isc__job_cb(uv_idle_t *idle) {
+       isc_job_t *job = uv_handle_get_data(idle);
+       int r;
+
+       REQUIRE(job->loop == isc_loop_current(job->loop->loopmgr));
+
+       job->cb(job->cbarg);
+
+       r = uv_idle_stop(idle);
+       UV_RUNTIME_CHECK(uv_idle_stop, r);
+
+       isc__job_destroy(job);
+}
+
+/*
+ * Public: #include <isc/job.h>
+ */
+
+void
+isc_job_run(isc_loopmgr_t *loopmgr, isc_job_cb cb, void *cbarg) {
+       isc_loop_t *loop = isc_loop_current(loopmgr);
+       isc_job_t *job = isc__job_new(loop, cb, cbarg);
+       isc__job_init(loop, job);
+       isc__job_run(job);
+}
+
+/*
+ * Protected: #include <job_p.h>
+ */
+
+isc_job_t *
+isc__job_new(isc_loop_t *loop, isc_job_cb cb, void *cbarg) {
+       isc_job_t *job = NULL;
+
+       REQUIRE(VALID_LOOP(loop));
+       REQUIRE(cb != NULL);
+
+       job = isc_mem_get(loop->mctx, sizeof(*job));
+       *job = (isc_job_t){
+               .magic = JOB_MAGIC,
+               .cb = cb,
+               .cbarg = cbarg,
+       };
+
+       isc_loop_attach(loop, &job->loop);
+
+       ISC_LINK_INIT(job, link);
+
+       return (job);
+}
+
+void
+isc__job_init(isc_loop_t *loop, isc_job_t *job) {
+       int r = uv_idle_init(&loop->loop, &job->idle);
+       UV_RUNTIME_CHECK(uv_idle_init, r);
+       uv_handle_set_data(&job->idle, job);
+}
+
+void
+isc__job_run(isc_job_t *job) {
+       int r;
+
+       REQUIRE(VALID_JOB(job));
+       REQUIRE(job->loop == isc_loop_current(job->loop->loopmgr));
+
+       r = uv_idle_start(&job->idle, isc__job_cb);
+       UV_RUNTIME_CHECK(uv_idle_start, r);
+}
diff --git a/lib/isc/job_p.h b/lib/isc/job_p.h
new file mode 100644 (file)
index 0000000..b021975
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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 <isc/job.h>
+#include <isc/loop.h>
+
+isc_job_t *
+isc__job_new(isc_loop_t *loop, isc_job_cb cb, void *cbarg);
+
+void
+isc__job_init(isc_loop_t *loop, isc_job_t *job);
+
+void
+isc__job_run(isc_job_t *job);
diff --git a/lib/isc/loop.c b/lib/isc/loop.c
new file mode 100644 (file)
index 0000000..df414ee
--- /dev/null
@@ -0,0 +1,592 @@
+/*
+ * 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 <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <isc/async.h>
+#include <isc/atomic.h>
+#include <isc/barrier.h>
+#include <isc/condition.h>
+#include <isc/job.h>
+#include <isc/list.h>
+#include <isc/loop.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/signal.h>
+#include <isc/strerr.h>
+#include <isc/thread.h>
+#include <isc/tid.h>
+#include <isc/util.h>
+#include <isc/uv.h>
+#include <isc/work.h>
+
+#include "job_p.h"
+#include "loop_p.h"
+
+/**
+ * Private
+ */
+
+static void
+ignore_signal(int sig, void (*handler)(int)) {
+       struct sigaction sa;
+
+       sa = (struct sigaction){ .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);
+       }
+}
+
+void
+isc_loopmgr_shutdown(isc_loopmgr_t *loopmgr) {
+       if (!atomic_compare_exchange_strong(&loopmgr->shuttingdown,
+                                           &(bool){ false }, true))
+       {
+               return;
+       }
+
+       for (size_t i = 0; i < loopmgr->nloops; i++) {
+               isc_loop_t *loop = &loopmgr->loops[i];
+               int r;
+
+               REQUIRE(!atomic_load(&loop->finished));
+
+               r = uv_async_send(&loop->shutdown_trigger);
+               UV_RUNTIME_CHECK(uv_async_send, r);
+       }
+}
+
+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 = uv_handle_get_data(handle);
+
+       pause_loop(loop);
+       resume_loop(loop);
+}
+
+#define XX(uc, lc)                                                         \
+       case UV_##uc:                                                      \
+               fprintf(stderr, "%s, %s: dangling %p: %p.type = %s\n",     \
+                       __func__, (char *)arg, handle->loop, handle, #lc); \
+               break;
+
+static void
+loop_walk_cb(uv_handle_t *handle, void *arg) {
+       if (uv_is_closing(handle)) {
+               return;
+       }
+
+       switch (handle->type) {
+               UV_HANDLE_TYPE_MAP(XX)
+       default:
+               fprintf(stderr, "%s, %s: dangling %p: %p.type = %s\n", __func__,
+                       (char *)arg, &handle->loop, handle, "unknown");
+       }
+}
+
+static void
+shutdown_trigger_close_cb(uv_handle_t *handle) {
+       isc_loop_t *loop = uv_handle_get_data(handle);
+
+       isc_loop_detach(&loop);
+}
+
+static void
+destroy_cb(uv_async_t *handle) {
+       isc_loop_t *loop = uv_handle_get_data(handle);
+
+       uv_close(&loop->destroy_trigger, NULL);
+       uv_close(&loop->queue_trigger, NULL);
+       uv_close(&loop->pause_trigger, NULL);
+
+       uv_walk(&loop->loop, loop_walk_cb, (char *)"destroy_cb");
+}
+
+static void
+shutdown_cb(uv_async_t *handle) {
+       isc_job_t *job = NULL;
+       isc_loop_t *loop = uv_handle_get_data(handle);
+       isc_loopmgr_t *loopmgr = loop->loopmgr;
+
+       /* Make sure, we can't be called again */
+       uv_close(&loop->shutdown_trigger, shutdown_trigger_close_cb);
+
+       if (DEFAULT_LOOP(loopmgr) == CURRENT_LOOP(loopmgr)) {
+               /* Stop the signal handlers */
+               isc_signal_stop(loopmgr->sigterm);
+               isc_signal_stop(loopmgr->sigint);
+
+               /* Free the signal handlers */
+               isc_signal_destroy(&loopmgr->sigterm);
+               isc_signal_destroy(&loopmgr->sigint);
+       }
+
+       job = ISC_LIST_TAIL(loop->teardown_jobs);
+       while (job != NULL) {
+               isc_job_t *prev = ISC_LIST_PREV(job, link);
+               ISC_LIST_UNLINK(loop->teardown_jobs, job, link);
+
+               isc__job_run(job);
+
+               job = prev;
+       }
+}
+
+static void
+queue_cb(uv_async_t *handle) {
+       isc_loop_t *loop = uv_handle_get_data(handle);
+       isc_job_t *job = NULL;
+       ISC_LIST(isc_job_t) list;
+
+       REQUIRE(VALID_LOOP(loop));
+
+       ISC_LIST_INIT(list);
+
+       LOCK(&loop->queue_lock);
+       ISC_LIST_MOVE(list, loop->queue_jobs);
+       UNLOCK(&loop->queue_lock);
+
+       job = ISC_LIST_HEAD(list);
+       while (job != NULL) {
+               isc_job_t *next = ISC_LIST_NEXT(job, link);
+               ISC_LIST_UNLINK(list, job, link);
+
+               isc__job_init(loop, job);
+               isc__job_run(job);
+
+               job = next;
+       }
+}
+
+static void
+loop_init(isc_loop_t *loop, isc_loopmgr_t *loopmgr, size_t tid) {
+       *loop = (isc_loop_t){
+               .tid = tid,
+               .loopmgr = loopmgr,
+       };
+
+       int r = uv_loop_init(&loop->loop);
+       UV_RUNTIME_CHECK(uv_loop_init, r);
+
+       r = uv_async_init(&loop->loop, &loop->pause_trigger, pauseresume_cb);
+       UV_RUNTIME_CHECK(uv_async_init, r);
+       uv_handle_set_data(&loop->pause_trigger, loop);
+
+       r = uv_async_init(&loop->loop, &loop->shutdown_trigger, shutdown_cb);
+       UV_RUNTIME_CHECK(uv_async_init, r);
+       uv_handle_set_data(&loop->shutdown_trigger, loop);
+
+       r = uv_async_init(&loop->loop, &loop->queue_trigger, queue_cb);
+       UV_RUNTIME_CHECK(uv_async_init, r);
+       uv_handle_set_data(&loop->queue_trigger, loop);
+
+       r = uv_async_init(&loop->loop, &loop->destroy_trigger, destroy_cb);
+       UV_RUNTIME_CHECK(uv_async_init, r);
+       uv_handle_set_data(&loop->destroy_trigger, loop);
+
+       isc_mem_create(&loop->mctx);
+
+       isc_mutex_init(&loop->queue_lock);
+
+       ISC_LIST_INIT(loop->queue_jobs);
+       ISC_LIST_INIT(loop->setup_jobs);
+       ISC_LIST_INIT(loop->teardown_jobs);
+
+       isc_refcount_init(&loop->references, 1);
+
+       loop->magic = LOOP_MAGIC;
+}
+
+static void
+loop_run(isc_loop_t *loop) {
+       int r;
+       isc_job_t *job;
+
+       job = ISC_LIST_HEAD(loop->setup_jobs);
+       while (job != NULL) {
+               isc_job_t *next = ISC_LIST_NEXT(job, link);
+               ISC_LIST_UNLINK(loop->setup_jobs, job, link);
+
+               isc__job_run(job);
+
+               job = next;
+       }
+
+       isc_barrier_wait(&loop->loopmgr->starting);
+
+       r = uv_run(&loop->loop, UV_RUN_DEFAULT);
+       UV_RUNTIME_CHECK(uv_run, r);
+
+       isc_barrier_wait(&loop->loopmgr->stopping);
+}
+
+static void
+loop_close(isc_loop_t *loop) {
+       int r = uv_loop_close(&loop->loop);
+       UV_RUNTIME_CHECK(uv_loop_close, r);
+
+       isc_mutex_destroy(&loop->queue_lock);
+       INSIST(ISC_LIST_EMPTY(loop->queue_jobs));
+
+       loop->magic = 0;
+
+       isc_mem_detach(&loop->mctx);
+}
+
+static isc_threadresult_t
+loop_thread(isc_threadarg_t arg) {
+       isc_loop_t *loop = (isc_loop_t *)arg;
+
+       /* Initialize the thread_local variable */
+
+       isc__tid_init(loop->tid);
+
+       loop_run(loop);
+
+       return ((isc_threadresult_t)0);
+}
+
+void
+isc_loop_nosetup(isc_loop_t *loop, isc_job_t *job) {
+       ISC_LIST_DEQUEUE(loop->setup_jobs, job, link);
+}
+
+void
+isc_loop_noteardown(isc_loop_t *loop, isc_job_t *job) {
+       ISC_LIST_DEQUEUE(loop->teardown_jobs, job, link);
+}
+
+/**
+ * Public
+ */
+
+static void
+threadpool_initialize(uint32_t workers) {
+       char buf[11];
+       int r = uv_os_getenv("UV_THREADPOOL_SIZE", buf,
+                            &(size_t){ sizeof(buf) });
+       if (r == UV_ENOENT) {
+               snprintf(buf, sizeof(buf), "%" PRIu32, workers);
+               uv_os_setenv("UV_THREADPOOL_SIZE", buf);
+       }
+}
+
+static void
+loop_destroy(isc_loop_t *loop) {
+       int r = uv_async_send(&loop->destroy_trigger);
+       UV_RUNTIME_CHECK(uv_async_send, r);
+}
+
+ISC_REFCOUNT_IMPL(isc_loop, loop_destroy);
+
+void
+isc_loopmgr_create(isc_mem_t *mctx, uint32_t nloops, isc_loopmgr_t **loopmgrp) {
+       isc_loopmgr_t *loopmgr = NULL;
+
+       REQUIRE(loopmgrp != NULL && *loopmgrp == NULL);
+       REQUIRE(nloops > 0);
+
+       threadpool_initialize(nloops);
+
+       loopmgr = isc_mem_get(mctx, sizeof(*loopmgr));
+       *loopmgr = (isc_loopmgr_t){
+               .nloops = nloops,
+       };
+
+       isc_mem_attach(mctx, &loopmgr->mctx);
+
+       isc_barrier_init(&loopmgr->pausing, loopmgr->nloops);
+       isc_barrier_init(&loopmgr->resuming, loopmgr->nloops);
+       isc_barrier_init(&loopmgr->starting, loopmgr->nloops);
+       isc_barrier_init(&loopmgr->stopping, 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_init(loop, loopmgr, i);
+       }
+
+       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;
+
+       *loopmgrp = loopmgr;
+}
+
+isc_job_t *
+isc_loop_setup(isc_loop_t *loop, isc_job_cb cb, void *cbarg) {
+       isc_loopmgr_t *loopmgr = NULL;
+       isc_job_t *job = NULL;
+
+       REQUIRE(VALID_LOOP(loop));
+       REQUIRE(cb != NULL);
+
+       loopmgr = loop->loopmgr;
+
+       REQUIRE(loop->tid == isc_tid() || !atomic_load(&loopmgr->running) ||
+               atomic_load(&loopmgr->paused));
+
+       job = isc__job_new(loop, cb, cbarg);
+       isc__job_init(loop, job);
+
+       /*
+        * The ISC_LIST_PREPEND is counterintuitive here, but actually, the
+        * uv_idle_start() puts the item on the HEAD of the internal list, so we
+        * want to store items here in reverse order, so on the uv_loop, they
+        * are scheduled in the correct order
+        */
+       ISC_LIST_PREPEND(loop->setup_jobs, job, link);
+
+       return (job);
+}
+
+isc_job_t *
+isc_loop_teardown(isc_loop_t *loop, isc_job_cb cb, void *cbarg) {
+       isc_loopmgr_t *loopmgr = NULL;
+       isc_job_t *job = NULL;
+
+       REQUIRE(VALID_LOOP(loop));
+
+       loopmgr = loop->loopmgr;
+
+       REQUIRE(loop->tid == isc_tid() || !atomic_load(&loopmgr->running) ||
+               atomic_load(&loopmgr->paused));
+
+       job = isc__job_new(loop, cb, cbarg);
+       isc__job_init(loop, job);
+
+       /*
+        * The ISC_LIST_PREPEND is counterintuitive here, but actually, the
+        * uv_idle_start() puts the item on the HEAD of the internal list, so we
+        * want to store items here in reverse order, so on the uv_loop, they
+        * are scheduled in the correct order
+        */
+       ISC_LIST_PREPEND(loop->teardown_jobs, job, link);
+
+       return (job);
+}
+
+void
+isc_loopmgr_setup(isc_loopmgr_t *loopmgr, 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];
+               (void)isc_loop_setup(loop, cb, cbarg);
+       }
+}
+
+void
+isc_loopmgr_teardown(isc_loopmgr_t *loopmgr, 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];
+               (void)isc_loop_teardown(loop, 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++) {
+               char name[32];
+               isc_loop_t *loop = &loopmgr->loops[i];
+
+               isc_thread_create(loop_thread, loop, &loop->thread);
+
+               snprintf(name, sizeof(name), "isc-loop-%04zu", i);
+               isc_thread_setname(loop->thread, name);
+       }
+
+       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_tid()) {
+                       continue;
+               }
+
+               REQUIRE(!atomic_load(&loop->finished));
+               uv_async_send(&loop->pause_trigger);
+       }
+
+       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;
+
+       RUNTIME_CHECK(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);
+       }
+
+       loopmgr->magic = 0;
+
+       for (size_t i = 0; i < loopmgr->nloops; i++) {
+               isc_loop_t *loop = &loopmgr->loops[i];
+               loop_close(loop);
+       }
+       isc_mem_put(loopmgr->mctx, loopmgr->loops,
+                   loopmgr->nloops * sizeof(loopmgr->loops[0]));
+
+       isc_barrier_destroy(&loopmgr->starting);
+       isc_barrier_destroy(&loopmgr->stopping);
+       isc_barrier_destroy(&loopmgr->resuming);
+       isc_barrier_destroy(&loopmgr->pausing);
+
+       isc_mem_putanddetach(&loopmgr->mctx, loopmgr, sizeof(*loopmgr));
+}
+
+uint32_t
+isc_loopmgr_nloops(isc_loopmgr_t *loopmgr) {
+       REQUIRE(VALID_LOOPMGR(loopmgr));
+
+       return (loopmgr->nloops);
+}
+
+isc_mem_t *
+isc_loop_getmctx(isc_loop_t *loop) {
+       REQUIRE(VALID_LOOP(loop));
+
+       return (loop->mctx);
+}
+
+isc_loop_t *
+isc_loop_main(isc_loopmgr_t *loopmgr) {
+       REQUIRE(VALID_LOOPMGR(loopmgr));
+
+       return (DEFAULT_LOOP(loopmgr));
+}
+
+isc_loop_t *
+isc_loop_current(isc_loopmgr_t *loopmgr) {
+       REQUIRE(VALID_LOOPMGR(loopmgr));
+
+       return (CURRENT_LOOP(loopmgr));
+}
+
+isc_loop_t *
+isc_loop_get(isc_loopmgr_t *loopmgr, uint32_t tid) {
+       REQUIRE(VALID_LOOPMGR(loopmgr));
+       REQUIRE(tid < loopmgr->nloops);
+
+       return (LOOP(loopmgr, tid));
+}
+
+void
+isc_loopmgr_blocking(isc_loopmgr_t *loopmgr) {
+       REQUIRE(VALID_LOOPMGR(loopmgr));
+
+       isc_signal_stop(loopmgr->sigterm);
+       isc_signal_stop(loopmgr->sigint);
+}
+
+void
+isc_loopmgr_nonblocking(isc_loopmgr_t *loopmgr) {
+       REQUIRE(VALID_LOOPMGR(loopmgr));
+
+       isc_signal_start(loopmgr->sigint);
+       isc_signal_start(loopmgr->sigterm);
+}
diff --git a/lib/isc/loop_p.h b/lib/isc/loop_p.h
new file mode 100644 (file)
index 0000000..9e5fbb6
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * 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 <inttypes.h>
+
+#include <isc/barrier.h>
+#include <isc/lang.h>
+#include <isc/loop.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/signal.h>
+#include <isc/thread.h>
+#include <isc/types.h>
+#include <isc/uv.h>
+#include <isc/work.h>
+
+/*
+ * Per-thread loop
+ */
+#define LOOP_MAGIC    ISC_MAGIC('L', 'O', 'O', 'P')
+#define VALID_LOOP(t) ISC_MAGIC_VALID(t, LOOP_MAGIC)
+
+typedef ISC_LIST(isc_job_t) isc_joblist_t;
+
+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;
+       atomic_bool finished;
+       bool shuttingdown;
+
+       /* Async queue */
+       uv_async_t queue_trigger;
+       isc_mutex_t queue_lock;
+       isc_joblist_t queue_jobs;
+
+       /* Pause */
+       uv_async_t pause_trigger;
+
+       /* Shutdown */
+       uv_async_t shutdown_trigger;
+       isc_joblist_t setup_jobs;
+       isc_joblist_t teardown_jobs;
+
+       /* Destroy */
+       uv_async_t destroy_trigger;
+};
+
+/*
+ * 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_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;
+
+       /* start/stop */
+       isc_barrier_t starting;
+
+       /* stopping */
+       isc_barrier_t stopping;
+
+       /* per-thread objects */
+       isc_loop_t *loops;
+};
+
+/*
+ * Signal Handler
+ */
+struct isc_signal {
+       uv_signal_t signal;
+       isc_loop_t *loop;
+       isc_signal_cb cb;
+       void *cbarg;
+       int signum;
+};
+
+/*
+ * Job to be scheduled in an event loop
+ */
+#define JOB_MAGIC    ISC_MAGIC('J', 'O', 'B', ' ')
+#define VALID_JOB(t) ISC_MAGIC_VALID(t, JOB_MAGIC)
+
+struct isc_job {
+       int magic;
+       uv_idle_t idle;
+       isc_loop_t *loop;
+       isc_job_cb cb;
+       void *cbarg;
+       LINK(isc_job_t) link;
+};
+
+/*
+ * Work to be offloaded to an external thread.
+ */
+struct isc_work {
+       uv_work_t work;
+       isc_loop_t *loop;
+       isc_work_cb work_cb;
+       isc_after_work_cb after_work_cb;
+       void *cbarg;
+};
+
+#define DEFAULT_LOOP(loopmgr) (&(loopmgr)->loops[0])
+#define CURRENT_LOOP(loopmgr) (&(loopmgr)->loops[isc_tid()])
+#define LOOP(loopmgr, tid)    (&(loopmgr)->loops[tid])
+#define ON_LOOP(loop)        ((loop) == CURRENT_LOOP((loop)->loopmgr))
+
+ISC_REFCOUNT_DECL(isc_loop);
diff --git a/lib/isc/signal.c b/lib/isc/signal.c
new file mode 100644 (file)
index 0000000..69c7541
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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 <stdlib.h>
+
+#include <isc/loop.h>
+#include <isc/signal.h>
+#include <isc/uv.h>
+
+#include "loop_p.h"
+
+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;
+       int r;
+
+       loop = DEFAULT_LOOP(loopmgr);
+
+       signal = isc_mem_get(isc_loop_getmctx(loop), sizeof(*signal));
+       *signal = (isc_signal_t){
+               .cb = cb,
+               .cbarg = cbarg,
+               .signum = signum,
+       };
+
+       isc_loop_attach(loop, &signal->loop);
+
+       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_destroy_cb(uv_handle_t *handle) {
+       isc_signal_t *signal = uv_handle_get_data(handle);
+       isc_loop_t *loop = signal->loop;
+
+       isc_mem_put(loop->mctx, signal, sizeof(*signal));
+
+       isc_loop_detach(&loop);
+}
+
+void
+isc_signal_destroy(isc_signal_t **signalp) {
+       isc_signal_t *signal;
+
+       REQUIRE(signalp != NULL && *signalp != NULL);
+       signal = *signalp;
+       *signalp = NULL;
+
+       uv_close((uv_handle_t *)&signal->signal, isc__signal_destroy_cb);
+}
+
+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/tid.c b/lib/isc/tid.c
new file mode 100644 (file)
index 0000000..59c3928
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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 <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <isc/lang.h>
+#include <isc/tid.h>
+#include <isc/util.h>
+
+/**
+ * Private
+ */
+
+#define ISC_TID_UNKNOWN UINT32_MAX
+
+static thread_local uint32_t isc__tid_v = ISC_TID_UNKNOWN;
+
+/**
+ * Protected
+ */
+
+void
+isc__tid_init(uint32_t tid) {
+       REQUIRE(isc__tid_v == ISC_TID_UNKNOWN || isc__tid_v == tid);
+
+       isc__tid_v = tid;
+}
+
+/**
+ * Public
+ */
+
+uint32_t
+isc_tid(void) {
+       return (isc__tid_v);
+}
index 58171d913861f299817852e448d5477eb0fe9564..b724b7445062746d4efe9d087490affcc31f3c85 100644 (file)
 
 #include <inttypes.h>
 #include <stdlib.h>
-#include <uv.h>
 
 #include <isc/mem.h>
 #include <isc/once.h>
 #include <isc/thread.h>
 #include <isc/util.h>
+#include <isc/uv.h>
 
 #include "trampoline_p.h"
 
diff --git a/lib/isc/work.c b/lib/isc/work.c
new file mode 100644 (file)
index 0000000..e35a350
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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 <stdlib.h>
+
+#include <isc/job.h>
+#include <isc/loop.h>
+#include <isc/uv.h>
+#include <isc/work.h>
+
+#include "loop_p.h"
+
+static void
+isc__work_cb(uv_work_t *req) {
+       isc_work_t *work = uv_req_get_data((uv_req_t *)req);
+
+       work->work_cb(work->cbarg);
+}
+
+static void
+isc__after_work_cb(uv_work_t *req, int status) {
+       isc_work_t *work = uv_req_get_data((uv_req_t *)req);
+       isc_loop_t *loop = work->loop;
+
+       UV_RUNTIME_CHECK(uv_after_work_cb, status);
+
+       work->after_work_cb(work->cbarg);
+
+       isc_mem_put(loop->mctx, work, sizeof(*work));
+
+       isc_loop_detach(&loop);
+}
+
+void
+isc_work_enqueue(isc_loop_t *loop, isc_work_cb work_cb,
+                isc_after_work_cb after_work_cb, void *cbarg) {
+       isc_work_t *work = NULL;
+       int r;
+
+       REQUIRE(VALID_LOOP(loop));
+       REQUIRE(work_cb != NULL);
+       REQUIRE(after_work_cb != NULL);
+
+       work = isc_mem_get(loop->mctx, sizeof(*work));
+       *work = (isc_work_t){
+               .work_cb = work_cb,
+               .after_work_cb = after_work_cb,
+               .cbarg = cbarg,
+       };
+
+       isc_loop_attach(loop, &work->loop);
+
+       uv_req_set_data((uv_req_t *)&work->work, work);
+
+       r = uv_queue_work(&loop->loop, &work->work, isc__work_cb,
+                         isc__after_work_cb);
+       UV_RUNTIME_CHECK(uv_queue_work, r);
+}
index 6131c2018529007ab03d10621c3ce60008d632f8..b571938692e94dcbf085e96fd6aeb6d3af6207a4 100644 (file)
@@ -44,6 +44,8 @@
 
 extern isc_mem_t      *mctx;
 extern isc_nm_t              *netmgr;
+extern isc_loopmgr_t  *loopmgr;
+extern isc_loop_t     *mainloop;
 extern isc_taskmgr_t  *taskmgr;
 extern isc_timermgr_t *timermgr;
 extern unsigned int    workers;
@@ -51,6 +53,16 @@ extern isc_task_t     *maintask;
 
 #define isc_test_nap(ms) uv_sleep(ms)
 
+int
+setup_mctx(void **state);
+int
+teardown_mctx(void **state);
+
+int
+setup_loopmgr(void **state);
+int
+teardown_loopmgr(void **state);
+
 int
 setup_managers(void **state);
 int
index 5cfd90b20d7ef750f868f44fb8207b85eb68acaf..3e0324c1a20c67aed2d4ef6d0955fb7a1bce44fa 100644 (file)
@@ -11,6 +11,7 @@ LDADD +=                      \
 
 check_PROGRAMS =       \
        aes_test        \
+       async_test      \
        buffer_test     \
        counter_test    \
        crc64_test      \
@@ -20,7 +21,9 @@ check_PROGRAMS =      \
        heap_test       \
        hmac_test       \
        ht_test         \
+       job_test        \
        lex_test        \
+       loop_test       \
        md_test         \
        mem_test        \
        netaddr_test    \
@@ -38,7 +41,8 @@ check_PROGRAMS =      \
        symtab_test     \
        task_test       \
        time_test       \
-       timer_test
+       timer_test      \
+       work_test
 
 if HAVE_LIBNGHTTP2
 check_PROGRAMS +=      \
diff --git a/tests/isc/async_test.c b/tests/isc/async_test.c
new file mode 100644 (file)
index 0000000..138147b
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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 <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/async.h>
+#include <isc/atomic.h>
+#include <isc/loop.h>
+#include <isc/os.h>
+#include <isc/result.h>
+#include <isc/tid.h>
+#include <isc/util.h>
+
+#include "async.c"
+
+#include <tests/isc.h>
+
+static atomic_uint scheduled = 0;
+
+static void
+async_cb(void *arg) {
+       UNUSED(arg);
+       uint32_t tid = isc_tid();
+
+       atomic_fetch_add(&scheduled, 1);
+
+       if (tid > 0) {
+               isc_loop_t *loop = isc_loop_get(loopmgr, tid - 1);
+               isc_async_run(loop, async_cb, loopmgr);
+       } else {
+               isc_loopmgr_shutdown(loopmgr);
+       }
+}
+
+static void
+async_setup_cb(void *arg) {
+       UNUSED(arg);
+       uint32_t tid = isc_loopmgr_nloops(loopmgr) - 1;
+
+       isc_loop_t *loop = isc_loop_get(loopmgr, tid);
+
+       isc_async_run(loop, async_cb, loopmgr);
+}
+
+ISC_RUN_TEST_IMPL(isc_async_run) {
+       isc_loop_setup(isc_loop_main(loopmgr), async_setup_cb, loopmgr);
+       isc_loopmgr_run(loopmgr);
+       assert_int_equal(atomic_load(&scheduled), loopmgr->nloops);
+}
+
+ISC_TEST_LIST_START
+ISC_TEST_ENTRY_CUSTOM(isc_async_run, setup_loopmgr, teardown_loopmgr)
+ISC_TEST_LIST_END
+
+ISC_TEST_MAIN
diff --git a/tests/isc/job_test.c b/tests/isc/job_test.c
new file mode 100644 (file)
index 0000000..4ac4c0a
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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 <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/atomic.h>
+#include <isc/job.h>
+#include <isc/loop.h>
+#include <isc/os.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include "job.c"
+
+#include <tests/isc.h>
+
+static atomic_uint scheduled;
+static atomic_uint executed;
+
+#define MAX_EXECUTED 1000000
+
+static void
+shutdown_cb(void *arg) {
+       UNUSED(arg);
+
+       isc_loopmgr_shutdown(loopmgr);
+}
+
+static void
+job_cb(void *arg __attribute__((__unused__))) {
+       unsigned int n = atomic_fetch_add(&executed, 1);
+
+       if (n <= MAX_EXECUTED) {
+               atomic_fetch_add(&scheduled, 1);
+               isc_job_run(loopmgr, job_cb, loopmgr);
+       } else {
+               isc_job_run(loopmgr, shutdown_cb, loopmgr);
+       }
+}
+
+static void
+job_run_cb(void *arg __attribute__((__unused__))) {
+       atomic_fetch_add(&scheduled, 1);
+
+       isc_job_run(loopmgr, job_cb, loopmgr);
+}
+
+ISC_RUN_TEST_IMPL(isc_job_run) {
+       atomic_init(&scheduled, 0);
+       atomic_init(&executed, 0);
+
+       isc_loopmgr_setup(loopmgr, job_run_cb, loopmgr);
+
+       isc_loopmgr_run(loopmgr);
+
+       assert_int_equal(atomic_load(&scheduled), atomic_load(&executed));
+}
+
+ISC_TEST_LIST_START
+ISC_TEST_ENTRY_CUSTOM(isc_job_run, setup_loopmgr, teardown_loopmgr)
+ISC_TEST_LIST_END
+
+ISC_TEST_MAIN
diff --git a/tests/isc/loop_test.c b/tests/isc/loop_test.c
new file mode 100644 (file)
index 0000000..27f118e
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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 <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/atomic.h>
+#include <isc/loop.h>
+#include <isc/os.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include "loop.c"
+
+#include <tests/isc.h>
+
+static atomic_uint scheduled = 0;
+
+static void
+count(void *arg) {
+       UNUSED(arg);
+
+       atomic_fetch_add(&scheduled, 1);
+}
+
+static void
+shutdown_loopmgr(void *arg) {
+       UNUSED(arg);
+
+       while (atomic_load(&scheduled) != loopmgr->nloops) {
+               isc_thread_yield();
+       }
+
+       isc_loopmgr_shutdown(loopmgr);
+}
+
+ISC_RUN_TEST_IMPL(isc_loopmgr) {
+       atomic_store(&scheduled, 0);
+
+       isc_loopmgr_setup(loopmgr, count, loopmgr);
+       isc_loop_setup(mainloop, shutdown_loopmgr, loopmgr);
+
+       isc_loopmgr_run(loopmgr);
+
+       assert_int_equal(atomic_load(&scheduled), loopmgr->nloops);
+}
+
+static void
+runjob(void *arg __attribute__((__unused__))) {
+       if (isc_tid() == 0) {
+               isc_job_run(loopmgr, shutdown_loopmgr, loopmgr);
+       }
+
+       isc_job_run(loopmgr, count, loopmgr);
+}
+
+ISC_RUN_TEST_IMPL(isc_loopmgr_runjob) {
+       atomic_store(&scheduled, 0);
+
+       isc_loopmgr_setup(loopmgr, runjob, loopmgr);
+       isc_loopmgr_run(loopmgr);
+       assert_int_equal(atomic_load(&scheduled), loopmgr->nloops);
+}
+
+static void
+pause_loopmgr(void *arg) {
+       UNUSED(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);
+}
+
+ISC_RUN_TEST_IMPL(isc_loopmgr_pause) {
+       isc_loop_setup(mainloop, pause_loopmgr, loopmgr);
+       isc_loop_setup(mainloop, shutdown_loopmgr, loopmgr);
+       isc_loopmgr_run(loopmgr);
+}
+
+static void
+send_sigint(void *arg) {
+       UNUSED(arg);
+
+       kill(getpid(), SIGINT);
+}
+
+ISC_RUN_TEST_IMPL(isc_loopmgr_sigint) {
+       isc_loop_setup(mainloop, send_sigint, loopmgr);
+       isc_loopmgr_run(loopmgr);
+}
+
+static void
+send_sigterm(void *arg) {
+       UNUSED(arg);
+
+       kill(getpid(), SIGINT);
+}
+
+ISC_RUN_TEST_IMPL(isc_loopmgr_sigterm) {
+       isc_loop_setup(mainloop, send_sigterm, loopmgr);
+       isc_loopmgr_run(loopmgr);
+}
+
+ISC_TEST_LIST_START
+ISC_TEST_ENTRY_CUSTOM(isc_loopmgr, setup_loopmgr, teardown_loopmgr)
+ISC_TEST_ENTRY_CUSTOM(isc_loopmgr_pause, setup_loopmgr, teardown_loopmgr)
+ISC_TEST_ENTRY_CUSTOM(isc_loopmgr_runjob, setup_loopmgr, teardown_loopmgr)
+ISC_TEST_ENTRY_CUSTOM(isc_loopmgr_sigint, setup_loopmgr, teardown_loopmgr)
+ISC_TEST_ENTRY_CUSTOM(isc_loopmgr_sigterm, setup_loopmgr, teardown_loopmgr)
+ISC_TEST_LIST_END
+
+ISC_TEST_MAIN
diff --git a/tests/isc/work_test.c b/tests/isc/work_test.c
new file mode 100644 (file)
index 0000000..769508f
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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 <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/atomic.h>
+#include <isc/loop.h>
+#include <isc/os.h>
+#include <isc/result.h>
+#include <isc/tid.h>
+#include <isc/util.h>
+#include <isc/work.h>
+
+#include "work.c"
+
+#include <tests/isc.h>
+
+static atomic_uint scheduled = 0;
+
+static void
+work_cb(void *arg) {
+       UNUSED(arg);
+
+       atomic_fetch_add(&scheduled, 1);
+
+       assert_int_equal(isc_tid(), UINT32_MAX);
+}
+
+static void
+after_work_cb(void *arg) {
+       UNUSED(arg);
+
+       assert_int_equal(atomic_load(&scheduled), 1);
+       isc_loopmgr_shutdown(loopmgr);
+}
+
+static void
+work_enqueue_cb(void *arg) {
+       UNUSED(arg);
+       uint32_t tid = isc_loopmgr_nloops(loopmgr) - 1;
+
+       isc_loop_t *loop = isc_loop_get(loopmgr, tid);
+
+       isc_work_enqueue(loop, work_cb, after_work_cb, loopmgr);
+}
+
+ISC_RUN_TEST_IMPL(isc_work_enqueue) {
+       atomic_init(&scheduled, 0);
+
+       isc_loop_setup(isc_loop_main(loopmgr), work_enqueue_cb, loopmgr);
+
+       isc_loopmgr_run(loopmgr);
+
+       assert_int_equal(atomic_load(&scheduled), 1);
+}
+
+ISC_TEST_LIST_START
+ISC_TEST_ENTRY_CUSTOM(isc_work_enqueue, setup_loopmgr, teardown_loopmgr)
+ISC_TEST_LIST_END
+
+ISC_TEST_MAIN
index 61e29c2a34666b621eb324a9bf3ff05b6211267b..1163c4caa14819ee656208f5f47b26de7cc0e200 100644 (file)
@@ -21,6 +21,7 @@
 
 #include <isc/buffer.h>
 #include <isc/hash.h>
+#include <isc/loop.h>
 #include <isc/managers.h>
 #include <isc/mem.h>
 #include <isc/os.h>
 #include <tests/isc.h>
 
 isc_mem_t *mctx = NULL;
+isc_loopmgr_t *loopmgr = NULL;
+isc_loop_t *mainloop = NULL;
 isc_taskmgr_t *taskmgr = NULL;
 isc_timermgr_t *timermgr = NULL;
 isc_nm_t *netmgr = NULL;
 unsigned int workers = 0;
 isc_task_t *maintask = NULL;
 
+int
+setup_mctx(void **state __attribute__((__unused__))) {
+       isc_mem_debugging |= ISC_MEM_DEBUGRECORD;
+       isc_mem_create(&mctx);
+
+       return (0);
+}
+
+int
+teardown_mctx(void **state __attribute__((__unused__))) {
+       isc_mem_destroy(&mctx);
+
+       return (0);
+}
+
+int
+setup_loopmgr(void **state __attribute__((__unused__))) {
+       char *env_workers = NULL;
+
+       REQUIRE(mctx != NULL);
+
+       env_workers = getenv("ISC_TASK_WORKERS");
+       if (env_workers != NULL) {
+               workers = atoi(env_workers);
+       } else {
+               /* We always need at least two loops for some of the tests */
+               workers = isc_os_ncpus() + 1;
+       }
+       INSIST(workers != 0);
+
+       isc_loopmgr_create(mctx, workers, &loopmgr);
+       mainloop = isc_loop_main(loopmgr);
+
+       return (0);
+}
+
+int
+teardown_loopmgr(void **state __attribute__((__unused__))) {
+       REQUIRE(taskmgr == NULL);
+       REQUIRE(netmgr == NULL);
+
+       mainloop = NULL;
+       isc_loopmgr_destroy(&loopmgr);
+
+       return (0);
+}
+
 int
 setup_managers(void **state) {
        isc_result_t result;