Update isc_timer to use isc_loopmgr

* isc_timer was rewritten using the uv_timer, and isc_timermgr_t was
  completely removed; isc_timer objects are now directly created on the
  isc_loop event loops.

* the isc_timer API has been simplified. the "inactive" timer type has
  been removed; timers are now stopped by calling isc_timer_stop()
  instead of resetting to inactive.

* isc_manager now creates a loop manager rather than a timer manager.

* modules and applications using isc_timer have been updated to use the
  new API.
This commit is contained in:
Ondřej Surý
2022-07-26 13:03:40 +02:00
parent 84c90e223f
commit 49b149f5fd
48 changed files with 953 additions and 1930 deletions

View File

@@ -169,7 +169,6 @@ libisc_la_SOURCES = \
mutexblock.c \
net.c \
netaddr.c \
netmgr_p.h \
netscope.c \
nonce.c \
openssl_shim.c \
@@ -199,12 +198,10 @@ libisc_la_SOURCES = \
symtab.c \
syslog.c \
task.c \
task_p.h \
thread.c \
tid.c \
time.c \
timer.c \
timer_p.h \
tls.c \
tls_p.h \
tm.c \

View File

@@ -22,9 +22,9 @@ typedef struct isc_managers isc_managers_t;
isc_result_t
isc_managers_create(isc_mem_t *mctx, size_t workers, size_t quantum,
isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp,
isc_timermgr_t **timermgrp);
isc_loopmgr_t **loopmgrp, isc_nm_t **netmgrp,
isc_taskmgr_t **taskmgrp);
void
isc_managers_destroy(isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp,
isc_timermgr_t **timermgrp);
isc_managers_destroy(isc_loopmgr_t **loopmgr, isc_nm_t **netmgrp,
isc_taskmgr_t **taskmgrp);

View File

@@ -31,6 +31,7 @@
#include <stdbool.h>
#include <isc/lang.h>
#include <isc/loop.h>
#include <isc/time.h>
#include <isc/types.h>
@@ -41,13 +42,12 @@ ISC_LANG_BEGINDECLS
*****/
isc_result_t
isc_ratelimiter_create(isc_mem_t *mctx, isc_timermgr_t *timermgr,
isc_task_t *task, isc_ratelimiter_t **ratelimiterp);
isc_ratelimiter_create(isc_loop_t *loop, isc_ratelimiter_t **ratelimiterp);
/*%<
* Create a rate limiter. The execution interval is initially undefined.
*/
isc_result_t
void
isc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval);
/*!<
* Set the minimum interval between event executions.
@@ -110,37 +110,19 @@ isc_ratelimiter_shutdown(isc_ratelimiter_t *ratelimiter);
*
* Ensures:
*\li All events that have not yet been
* dispatched to the task are dispatched immediately with
* dispatched to the task are dispatched immediately with
* the #ISC_EVENTATTR_CANCELED bit set in ev_attributes.
*
*\li Further attempts to enqueue events will fail with
* #ISC_R_SHUTTINGDOWN.
* #ISC_R_SHUTTINGDOWN.
*
*\li The rate limiter is no longer attached to its task.
*/
void
isc_ratelimiter_attach(isc_ratelimiter_t *source, isc_ratelimiter_t **target);
isc_ratelimiter_destroy(isc_ratelimiter_t **ratelimiterp);
/*%<
* Attach to a rate limiter.
*/
void
isc_ratelimiter_detach(isc_ratelimiter_t **ratelimiterp);
/*%<
* Detach from a rate limiter.
*/
isc_result_t
isc_ratelimiter_stall(isc_ratelimiter_t *rl);
/*%<
* Stall event processing.
*/
isc_result_t
isc_ratelimiter_release(isc_ratelimiter_t *rl);
/*%<
* Release a stalled rate limiter.
* Destroy the rate limiter.
*/
ISC_LANG_ENDDECLS

View File

@@ -20,19 +20,13 @@
/*! \file isc/timer.h
* \brief Provides timers which are event sources in the task system.
*
* Three types of timers are supported:
* Two types of timers are supported:
*
*\li 'ticker' timers generate a periodic tick event.
*
*\li 'once' timers generate an timeout event if the time reaches
* the set interval.
*
*\li 'inactive' timers generate no events.
*
* Timers can change type. It is typical to create a timer as
* an 'inactive' timer and then change it into a 'ticker' or
* 'once' timer.
*
*\li MP:
* The module ensures appropriate synchronization of data structures it
* creates and manipulates.
@@ -61,8 +55,7 @@
#include <stdbool.h>
#include <isc/event.h>
#include <isc/eventclass.h>
#include <isc/job.h>
#include <isc/lang.h>
#include <isc/time.h>
#include <isc/types.h>
@@ -78,20 +71,8 @@ typedef enum {
isc_timertype_undefined = -1, /*%< Undefined */
isc_timertype_ticker = 0, /*%< Ticker */
isc_timertype_once = 1, /*%< Once */
isc_timertype_inactive = 3 /*%< Inactive */
} isc_timertype_t;
typedef struct isc_timerevent isc_timerevent_t;
struct isc_timerevent {
struct isc_event common;
isc_time_t due;
ISC_LINK(isc_timerevent_t) ev_timerlink;
};
#define ISC_TIMEREVENT_TICK (ISC_EVENTCLASS_TIMER + 0)
#define ISC_TIMEREVENT_ONCE (ISC_EVENTCLASS_TIMER + 1)
/***
*** Timer and Timer Manager Functions
***
@@ -100,55 +81,40 @@ struct isc_timerevent {
***/
void
isc_timer_create(isc_timermgr_t *manager, isc_task_t *task,
isc_taskaction_t action, void *arg, isc_timer_t **timerp);
isc_timer_create(isc_loop_t *loop, isc_job_cb cb, void *cbarg,
isc_timer_t **timerp);
/*%<
* Create a new 'type' timer managed by 'manager'. The timers parameters
* are specified by 'expires' and 'interval'. Events will be posted to
* 'task' and when dispatched 'action' will be called with 'arg' as the
* arg value. The new timer is returned in 'timerp'.
* Create a new 'type' timer managed by 'loop'. The timers parameters are
* specified by 'expires' and 'interval'. Events will be posted on the isc
* event loop and when dispatched 'cb' will be called with 'cbarg' as the arg
* value. The new timer is returned in 'timerp'.
*
* Requires:
*
*\li 'manager' is a valid manager
*
*\li 'task' is a valid task
*
*\li 'action' is a valid action
*
*\li 'expires' points to a valid time, or is NULL.
*
*\li 'interval' points to a valid interval, or is NULL.
*
*\li type == isc_timertype_inactive ||
* ('expires' and 'interval' are not both 0)
*
*\li 'loop' is a valid manager
*\li 'cb' is a valid job
*\li 'timerp' is a valid pointer, and *timerp == NULL
*
* Ensures:
*
*\li '*timerp' is attached to the newly created timer
*
*\li The timer is attached to the task
*
*\li An idle timeout will not be generated until at least Now + the
* timer's interval if 'timer' is a once timer with a non-zero
* interval.
*
* Returns:
*
*\li Success
*\li No memory
*\li Unexpected error
*/
isc_result_t
isc_timer_reset(isc_timer_t *timer, isc_timertype_t type,
const isc_interval_t *interval, bool purge);
void
isc_timer_stop(isc_timer_t *timer);
/*%<
* Change the timer's type, and interval values to the given
* values. If 'purge' is TRUE, any pending events from this timer
* are purged from its task's event queue.
* Stop the timer.
*
* Requires:
*
*\li 'timer' is a valid timer
*/
void
isc_timer_start(isc_timer_t *timer, isc_timertype_t type,
const isc_interval_t *interval);
/*%<
* Start the timer.
*
* Notes:
*
@@ -156,35 +122,28 @@ isc_timer_reset(isc_timer_t *timer, isc_timertype_t type,
* 'interval' seconds.
*
*\li For once timers, 'interval' specifies how long the timer
* can be idle before it generates an idle timeout. If 0, then no
* idle timeout will be generated.
* can be idle before it generates an idle timeout. If 0, then
* the timer will be run immediately.
*
*\li If 'interval' is NULL, the zero interval will be used.
*
* Requires:
*
*\li 'timer' is a valid timer
*
*\li 'type' is either 'isc_timertype_ticker' or 'isc_timertype_once'
*\li 'interval' points to a valid interval, or is NULL.
*
*
* Ensures:
*
*\li An idle timeout will not be generated until at least Now + the
* timer's interval if 'timer' is a once timer with a non-zero
* interval.
*
* Returns:
*
*\li Success
*\li No memory
*\li Unexpected error
*/
void
isc_timer_destroy(isc_timer_t **timerp);
/*%<
* Destroy *timerp.
* Destroy the timer *timerp.
*
* Requires:
*
@@ -193,33 +152,6 @@ isc_timer_destroy(isc_timer_t **timerp);
* Ensures:
*
*\li *timerp is NULL.
*
*\code
* The timer will be shutdown
*
* The timer will detach from its task
*
* All resources used by the timer have been freed
*
* Any events already posted by the timer will be purged.
* Therefore, if isc_timer_destroy() is called in the context
* of the timer's task, it is guaranteed that no more
* timer event callbacks will run after the call.
*
* If this function is called from the timer event callback
* the event itself must be destroyed before the timer
* itself.
*\endcode
*/
isc_timertype_t
isc_timer_gettype(isc_timer_t *timer);
/*%<
* Return the timer type.
*
* Requires:
*
*\li 'timer' to be a valid timer.
*/
ISC_LANG_ENDDECLS

View File

@@ -11,21 +11,20 @@
* information regarding copyright ownership.
*/
#include <isc/loop.h>
#include <isc/managers.h>
#include <isc/util.h>
#include "netmgr_p.h"
#include "task_p.h"
#include "timer_p.h"
isc_result_t
isc_managers_create(isc_mem_t *mctx, size_t workers, size_t quantum,
isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp,
isc_timermgr_t **timermgrp) {
isc_loopmgr_t **loopmgrp, isc_nm_t **netmgrp,
isc_taskmgr_t **taskmgrp) {
isc_result_t result;
isc_nm_t *netmgr = NULL;
isc_taskmgr_t *taskmgr = NULL;
isc_timermgr_t *timermgr = NULL;
REQUIRE(netmgrp != NULL && *netmgrp == NULL);
isc__netmgr_create(mctx, workers, &netmgr);
@@ -45,28 +44,18 @@ isc_managers_create(isc_mem_t *mctx, size_t workers, size_t quantum,
*taskmgrp = taskmgr;
}
REQUIRE(timermgrp == NULL || *timermgrp == NULL);
if (timermgrp != NULL) {
result = isc__timermgr_create(mctx, &timermgr);
if (result != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__,
"isc_timermgr_create() failed: %s",
isc_result_totext(result));
goto fail;
}
*timermgrp = timermgr;
}
isc_loopmgr_create(mctx, workers, loopmgrp);
return (ISC_R_SUCCESS);
fail:
isc_managers_destroy(netmgrp, taskmgrp, timermgrp);
isc_managers_destroy(loopmgrp, netmgrp, taskmgrp);
return (result);
}
void
isc_managers_destroy(isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp,
isc_timermgr_t **timermgrp) {
isc_managers_destroy(isc_loopmgr_t **loopmgrp, isc_nm_t **netmgrp,
isc_taskmgr_t **taskmgrp) {
/*
* If we have a taskmgr to clean up, then we must also have a netmgr.
*/
@@ -109,11 +98,5 @@ isc_managers_destroy(isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp,
isc__netmgr_destroy(netmgrp);
}
/*
* 5. Clean up the remaining managers.
*/
if (timermgrp != NULL) {
INSIST(*timermgrp != NULL);
isc__timermgr_destroy(timermgrp);
}
isc_loopmgr_destroy(loopmgrp);
}

View File

@@ -16,6 +16,9 @@
#include <inttypes.h>
#include <stdbool.h>
#include <isc/async.h>
#include <isc/event.h>
#include <isc/loop.h>
#include <isc/mem.h>
#include <isc/ratelimiter.h>
#include <isc/refcount.h>
@@ -25,74 +28,53 @@
#include <isc/util.h>
typedef enum {
isc_ratelimiter_stalled = 0,
isc_ratelimiter_ratelimited = 1,
isc_ratelimiter_idle = 2,
isc_ratelimiter_shuttingdown = 3
isc_ratelimiter_ratelimited = 0,
isc_ratelimiter_idle = 1,
isc_ratelimiter_shuttingdown = 2
} isc_ratelimiter_state_t;
struct isc_ratelimiter {
isc_mem_t *mctx;
isc_mutex_t lock;
isc_refcount_t references;
isc_task_t *task;
isc_loopmgr_t *loopmgr;
isc_timer_t *timer;
isc_interval_t interval;
uint32_t pertic;
bool pushpop;
isc_ratelimiter_state_t state;
isc_event_t shutdownevent;
ISC_LIST(isc_event_t) pending;
};
#define ISC_RATELIMITEREVENT_SHUTDOWN (ISC_EVENTCLASS_RATELIMITER + 1)
static void
ratelimiter_tick(isc_task_t *task, isc_event_t *event);
static void
ratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event);
ratelimiter_tick(void *arg);
isc_result_t
isc_ratelimiter_create(isc_mem_t *mctx, isc_timermgr_t *timermgr,
isc_task_t *task, isc_ratelimiter_t **ratelimiterp) {
isc_ratelimiter_t *rl;
isc_ratelimiter_create(isc_loop_t *loop, isc_ratelimiter_t **ratelimiterp) {
isc_ratelimiter_t *rl = NULL;
isc_mem_t *mctx = isc_loop_getmctx(loop);
INSIST(ratelimiterp != NULL && *ratelimiterp == NULL);
rl = isc_mem_get(mctx, sizeof(*rl));
*rl = (isc_ratelimiter_t){
.mctx = mctx,
.task = task,
.pertic = 1,
.state = isc_ratelimiter_idle,
};
isc_refcount_init(&rl->references, 1);
isc_mem_attach(mctx, &rl->mctx);
isc_interval_set(&rl->interval, 0, 0);
ISC_LIST_INIT(rl->pending);
isc_mutex_init(&rl->lock);
isc_timer_create(timermgr, rl->task, ratelimiter_tick, rl, &rl->timer);
/*
* Increment the reference count to indicate that we may
* (soon) have events outstanding.
*/
isc_refcount_increment(&rl->references);
ISC_EVENT_INIT(&rl->shutdownevent, sizeof(isc_event_t), 0,
ISC_RATELIMITEREVENT_SHUTDOWN,
ratelimiter_shutdowncomplete, rl, rl, NULL, NULL);
isc_timer_create(loop, ratelimiter_tick, rl, &rl->timer);
*ratelimiterp = rl;
return (ISC_R_SUCCESS);
}
isc_result_t
void
isc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval) {
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(rl != NULL);
REQUIRE(interval != NULL);
@@ -102,63 +84,63 @@ isc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval) {
* If the timer is currently running, change its rate.
*/
if (rl->state == isc_ratelimiter_ratelimited) {
result = isc_timer_reset(rl->timer, isc_timertype_ticker,
&rl->interval, false);
isc_timer_start(rl->timer, isc_timertype_ticker, &rl->interval);
}
UNLOCK(&rl->lock);
return (result);
}
void
isc_ratelimiter_setpertic(isc_ratelimiter_t *rl, uint32_t pertic) {
REQUIRE(rl != NULL);
REQUIRE(pertic > 0);
if (pertic == 0) {
pertic = 1;
}
LOCK(&rl->lock);
rl->pertic = pertic;
UNLOCK(&rl->lock);
}
void
isc_ratelimiter_setpushpop(isc_ratelimiter_t *rl, bool pushpop) {
REQUIRE(rl != NULL);
LOCK(&rl->lock);
rl->pushpop = pushpop;
UNLOCK(&rl->lock);
}
isc_result_t
isc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_task_t *task,
isc_event_t **eventp) {
isc_result_t result = ISC_R_SUCCESS;
isc_event_t *ev;
isc_event_t *event;
REQUIRE(rl != NULL);
REQUIRE(task != NULL);
REQUIRE(eventp != NULL && *eventp != NULL);
ev = *eventp;
REQUIRE(ev->ev_sender == NULL);
event = *eventp;
REQUIRE(event->ev_sender == NULL);
LOCK(&rl->lock);
if (rl->state == isc_ratelimiter_ratelimited ||
rl->state == isc_ratelimiter_stalled)
{
ev->ev_sender = task;
switch (rl->state) {
case isc_ratelimiter_shuttingdown:
result = ISC_R_SHUTTINGDOWN;
break;
case isc_ratelimiter_ratelimited:
event->ev_sender = task;
*eventp = NULL;
if (rl->pushpop) {
ISC_LIST_PREPEND(rl->pending, ev, ev_ratelink);
ISC_LIST_PREPEND(rl->pending, event, ev_ratelink);
} else {
ISC_LIST_APPEND(rl->pending, ev, ev_ratelink);
ISC_LIST_APPEND(rl->pending, event, ev_ratelink);
}
} else if (rl->state == isc_ratelimiter_idle) {
result = isc_timer_reset(rl->timer, isc_timertype_ticker,
&rl->interval, false);
if (result == ISC_R_SUCCESS) {
ev->ev_sender = task;
rl->state = isc_ratelimiter_ratelimited;
}
} else {
INSIST(rl->state == isc_ratelimiter_shuttingdown);
result = ISC_R_SHUTTINGDOWN;
break;
case isc_ratelimiter_idle:
isc_timer_start(rl->timer, isc_timertype_ticker, &rl->interval);
event->ev_sender = task;
rl->state = isc_ratelimiter_ratelimited;
break;
default:
UNREACHABLE();
}
UNLOCK(&rl->lock);
if (*eventp != NULL && result == ISC_R_SUCCESS) {
@@ -181,114 +163,73 @@ isc_ratelimiter_dequeue(isc_ratelimiter_t *rl, isc_event_t *event) {
} else {
result = ISC_R_NOTFOUND;
}
if (ISC_LIST_EMPTY(rl->pending)) {
/* No work left to do. Stop the timer. */
isc_timer_stop(rl->timer);
rl->state = isc_ratelimiter_idle;
}
UNLOCK(&rl->lock);
return (result);
}
static void
ratelimiter_tick(isc_task_t *task, isc_event_t *event) {
isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg;
isc_event_t *p;
ratelimiter_tick(void *arg) {
isc_ratelimiter_t *rl = (isc_ratelimiter_t *)arg;
isc_event_t *event;
uint32_t pertic;
UNUSED(task);
isc_event_free(&event);
pertic = rl->pertic;
while (pertic != 0) {
pertic--;
LOCK(&rl->lock);
p = ISC_LIST_HEAD(rl->pending);
if (p != NULL) {
event = ISC_LIST_HEAD(rl->pending);
if (event != NULL) {
/*
* There is work to do. Let's do it after unlocking.
*/
ISC_LIST_UNLINK(rl->pending, p, ev_ratelink);
ISC_LIST_UNLINK(rl->pending, event, ev_ratelink);
} else {
/*
* No work left to do. Stop the timer so that we don't
* waste resources by having it fire periodically.
*/
isc_result_t result = isc_timer_reset(
rl->timer, isc_timertype_inactive, NULL, false);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
/* No work left to do. Stop the timer. */
isc_timer_stop(rl->timer);
rl->state = isc_ratelimiter_idle;
pertic = 0; /* Force the loop to exit. */
}
UNLOCK(&rl->lock);
if (p != NULL) {
isc_task_t *evtask = p->ev_sender;
isc_task_send(evtask, &p);
if (event != NULL) {
isc_task_send(event->ev_sender, &event);
}
INSIST(p == NULL);
INSIST(event == NULL);
}
}
void
isc_ratelimiter_shutdown(isc_ratelimiter_t *rl) {
isc_event_t *ev;
isc_task_t *task;
isc_result_t result;
isc_event_t *event;
REQUIRE(rl != NULL);
LOCK(&rl->lock);
rl->state = isc_ratelimiter_shuttingdown;
(void)isc_timer_reset(rl->timer, isc_timertype_inactive, NULL, false);
while ((ev = ISC_LIST_HEAD(rl->pending)) != NULL) {
task = ev->ev_sender;
ISC_LIST_UNLINK(rl->pending, ev, ev_ratelink);
ev->ev_attributes |= ISC_EVENTATTR_CANCELED;
isc_task_send(task, &ev);
}
task = NULL;
isc_task_attach(rl->task, &task);
result = isc_timer_reset(rl->timer, isc_timertype_inactive, NULL,
false);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
isc_timer_stop(rl->timer);
isc_timer_destroy(&rl->timer);
/*
* Send an event to our task. The delivery of this event
* indicates that no more timer events will be delivered.
*/
ev = &rl->shutdownevent;
isc_task_send(rl->task, &ev);
while ((event = ISC_LIST_HEAD(rl->pending)) != NULL) {
ISC_LIST_UNLINK(rl->pending, event, ev_ratelink);
UNLOCK(&rl->lock);
event->ev_attributes |= ISC_EVENTATTR_CANCELED;
isc_task_send(event->ev_sender, &event);
LOCK(&rl->lock);
}
UNLOCK(&rl->lock);
}
static void
ratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event) {
isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg;
UNUSED(task);
isc_ratelimiter_detach(&rl);
isc_task_detach(&task);
}
static void
ratelimiter_free(isc_ratelimiter_t *rl) {
isc_refcount_destroy(&rl->references);
isc_mutex_destroy(&rl->lock);
isc_mem_put(rl->mctx, rl, sizeof(*rl));
}
void
isc_ratelimiter_attach(isc_ratelimiter_t *source, isc_ratelimiter_t **target) {
REQUIRE(source != NULL);
REQUIRE(target != NULL && *target == NULL);
isc_refcount_increment(&source->references);
*target = source;
}
void
isc_ratelimiter_detach(isc_ratelimiter_t **rlp) {
isc_ratelimiter_destroy(isc_ratelimiter_t **rlp) {
isc_ratelimiter_t *rl;
REQUIRE(rlp != NULL && *rlp != NULL);
@@ -296,63 +237,10 @@ isc_ratelimiter_detach(isc_ratelimiter_t **rlp) {
rl = *rlp;
*rlp = NULL;
if (isc_refcount_decrement(&rl->references) == 1) {
ratelimiter_free(rl);
}
}
isc_result_t
isc_ratelimiter_stall(isc_ratelimiter_t *rl) {
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(rl != NULL);
LOCK(&rl->lock);
switch (rl->state) {
case isc_ratelimiter_shuttingdown:
result = ISC_R_SHUTTINGDOWN;
break;
case isc_ratelimiter_ratelimited:
result = isc_timer_reset(rl->timer, isc_timertype_inactive,
NULL, false);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
FALLTHROUGH;
case isc_ratelimiter_idle:
case isc_ratelimiter_stalled:
rl->state = isc_ratelimiter_stalled;
break;
}
REQUIRE(rl->state == isc_ratelimiter_shuttingdown);
UNLOCK(&rl->lock);
return (result);
}
isc_result_t
isc_ratelimiter_release(isc_ratelimiter_t *rl) {
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(rl != NULL);
LOCK(&rl->lock);
switch (rl->state) {
case isc_ratelimiter_shuttingdown:
result = ISC_R_SHUTTINGDOWN;
break;
case isc_ratelimiter_stalled:
if (!ISC_LIST_EMPTY(rl->pending)) {
result = isc_timer_reset(rl->timer,
isc_timertype_ticker,
&rl->interval, false);
if (result == ISC_R_SUCCESS) {
rl->state = isc_ratelimiter_ratelimited;
}
} else {
rl->state = isc_ratelimiter_idle;
}
break;
case isc_ratelimiter_ratelimited:
case isc_ratelimiter_idle:
break;
}
UNLOCK(&rl->lock);
return (result);
isc_mutex_destroy(&rl->lock);
isc_mem_putanddetach(&rl->mctx, rl, sizeof(*rl));
}

View File

@@ -15,9 +15,10 @@
#include <stdbool.h>
#include <isc/app.h>
#include <isc/async.h>
#include <isc/condition.h>
#include <isc/heap.h>
#include <isc/job.h>
#include <isc/log.h>
#include <isc/magic.h>
#include <isc/mem.h>
@@ -29,552 +30,198 @@
#include <isc/time.h>
#include <isc/timer.h>
#include <isc/util.h>
#include <isc/uv.h>
#include "timer_p.h"
#ifdef ISC_TIMER_TRACE
#define XTRACE(s) fprintf(stderr, "%s\n", (s))
#define XTRACEID(s, t) fprintf(stderr, "%s %p\n", (s), (t))
#define XTRACETIME(s, d) \
fprintf(stderr, "%s %u.%09u\n", (s), (d).seconds, (d).nanoseconds)
#define XTRACETIME2(s, d, n) \
fprintf(stderr, "%s %u.%09u %u.%09u\n", (s), (d).seconds, \
(d).nanoseconds, (n).seconds, (n).nanoseconds)
#define XTRACETIMER(s, t, d) \
fprintf(stderr, "%s %p %u.%09u\n", (s), (t), (d).seconds, \
(d).nanoseconds)
#else /* ifdef ISC_TIMER_TRACE */
#define XTRACE(s)
#define XTRACEID(s, t)
#define XTRACETIME(s, d)
#define XTRACETIME2(s, d, n)
#define XTRACETIMER(s, t, d)
#endif /* ISC_TIMER_TRACE */
#include "loop_p.h"
#define TIMER_MAGIC ISC_MAGIC('T', 'I', 'M', 'R')
#define VALID_TIMER(t) ISC_MAGIC_VALID(t, TIMER_MAGIC)
struct isc_timer {
/*! Not locked. */
unsigned int magic;
isc_timermgr_t *manager;
isc_mutex_t lock;
/*! Locked by timer lock. */
isc_time_t idle;
ISC_LIST(isc_timerevent_t) active;
/*! Locked by manager lock. */
isc_timertype_t type;
isc_interval_t interval;
isc_task_t *task;
isc_taskaction_t action;
void *arg;
unsigned int index;
isc_time_t due;
LINK(isc_timer_t) link;
};
#define TIMER_MANAGER_MAGIC ISC_MAGIC('T', 'I', 'M', 'M')
#define VALID_MANAGER(m) ISC_MAGIC_VALID(m, TIMER_MANAGER_MAGIC)
struct isc_timermgr {
/* Not locked. */
unsigned int magic;
isc_mem_t *mctx;
isc_mutex_t lock;
/* Locked by manager lock. */
bool done;
LIST(isc_timer_t) timers;
unsigned int nscheduled;
isc_time_t due;
isc_condition_t wakeup;
isc_thread_t thread;
isc_heap_t *heap;
};
static isc_result_t
schedule(isc_timer_t *timer, isc_time_t *now, bool signal_ok) {
isc_timermgr_t *manager;
isc_time_t due;
isc_result_t result = ISC_R_SUCCESS;
/*!
* Note: the caller must ensure locking.
*/
manager = timer->manager;
isc_refcount_t references;
isc_loop_t *loop;
uv_timer_t timer;
isc_job_cb cb;
void *cbarg;
/*
* Compute the new due time.
* We are locking the values here for now, but this needs to go away
* when the timers are pinned to the respective loops.
*/
switch (timer->type) {
case isc_timertype_ticker:
result = isc_time_add(now, &timer->interval, &due);
if (result != ISC_R_SUCCESS) {
return (result);
}
break;
isc_mutex_t lock;
uint64_t timeout;
uint64_t repeat;
};
static void
isc__timer_detach(isc_timer_t **timerp);
void
isc_timer_create(isc_loop_t *loop, isc_job_cb cb, void *cbarg,
isc_timer_t **timerp) {
int r;
isc_timer_t *timer = NULL;
isc_loopmgr_t *loopmgr = NULL;
REQUIRE(cb != NULL);
REQUIRE(timerp != NULL && *timerp == NULL);
REQUIRE(VALID_LOOP(loop));
loopmgr = loop->loopmgr;
REQUIRE(VALID_LOOPMGR(loopmgr));
REQUIRE(loop == isc_loop_current(loopmgr) ||
!atomic_load(&loopmgr->running) ||
atomic_load(&loopmgr->paused) > 0);
timer = isc_mem_get(loop->mctx, sizeof(*timer));
*timer = (isc_timer_t){
.cb = cb,
.cbarg = cbarg,
};
isc_loop_attach(loop, &timer->loop);
isc_refcount_init(&timer->references, 1);
isc_mutex_init(&timer->lock);
timer->magic = TIMER_MAGIC;
r = uv_timer_init(&timer->loop->loop, &timer->timer);
UV_RUNTIME_CHECK(uv_timer_init, r);
uv_handle_set_data(&timer->timer, timer);
*timerp = timer;
}
static void
isc__timer_stop(void *arg) {
isc_timer_t *timer = (isc_timer_t *)arg;
uv_timer_stop(&timer->timer);
isc__timer_detach(&timer);
}
void
isc_timer_stop(isc_timer_t *timer) {
REQUIRE(VALID_TIMER(timer));
isc_refcount_increment(&timer->references);
if (timer->loop == isc_loop_current(timer->loop->loopmgr)) {
isc__timer_stop(timer);
} else {
isc_async_run(timer->loop, isc__timer_stop, timer);
}
}
static void
timer_cb(uv_timer_t *handle) {
isc_timer_t *timer = uv_handle_get_data(handle);
REQUIRE(VALID_TIMER(timer));
timer->cb(timer->cbarg);
}
static void
isc__timer_start(void *arg) {
isc_timer_t *timer = (isc_timer_t *)arg;
LOCK(&timer->lock);
int r = uv_timer_start(&timer->timer, timer_cb, timer->timeout,
timer->repeat);
UV_RUNTIME_CHECK(uv_timer_start, r);
UNLOCK(&timer->lock);
isc__timer_detach(&timer);
}
void
isc_timer_start(isc_timer_t *timer, isc_timertype_t type,
const isc_interval_t *interval) {
isc_loopmgr_t *loopmgr = NULL;
isc_loop_t *loop = NULL;
REQUIRE(VALID_TIMER(timer));
REQUIRE(type == isc_timertype_ticker || type == isc_timertype_once);
loop = timer->loop;
REQUIRE(VALID_LOOP(loop));
loopmgr = loop->loopmgr;
REQUIRE(VALID_LOOPMGR(loopmgr));
LOCK(&timer->lock);
switch (type) {
case isc_timertype_once:
due = timer->idle;
timer->timeout = isc_interval_ms(interval);
timer->repeat = 0;
break;
case isc_timertype_ticker:
timer->timeout = timer->repeat = isc_interval_ms(interval);
break;
default:
UNREACHABLE();
}
/*
* Schedule the timer.
*/
if (timer->index > 0) {
/*
* Already scheduled.
*/
int cmp = isc_time_compare(&due, &timer->due);
timer->due = due;
switch (cmp) {
case -1:
isc_heap_increased(manager->heap, timer->index);
break;
case 1:
isc_heap_decreased(manager->heap, timer->index);
break;
case 0:
/* Nothing to do. */
break;
}
} else {
timer->due = due;
isc_heap_insert(manager->heap, timer);
manager->nscheduled++;
}
XTRACETIMER("schedule", timer, due);
/*
* If this timer is at the head of the queue, we need to ensure
* that we won't miss it if it has a more recent due time than
* the current "next" timer. We do this either by waking up the
* run thread, or explicitly setting the value in the manager.
*/
if (timer->index == 1 && signal_ok) {
XTRACE("signal (schedule)");
SIGNAL(&manager->wakeup);
}
return (result);
}
static void
deschedule(isc_timer_t *timer) {
isc_timermgr_t *manager;
/*
* The caller must ensure locking.
*/
manager = timer->manager;
if (timer->index > 0) {
bool need_wakeup = false;
if (timer->index == 1) {
need_wakeup = true;
}
isc_heap_delete(manager->heap, timer->index);
timer->index = 0;
INSIST(manager->nscheduled > 0);
manager->nscheduled--;
if (need_wakeup) {
XTRACE("signal (deschedule)");
SIGNAL(&manager->wakeup);
}
}
}
static void
timerevent_unlink(isc_timer_t *timer, isc_timerevent_t *event) {
REQUIRE(ISC_LINK_LINKED(event, ev_timerlink));
ISC_LIST_UNLINK(timer->active, event, ev_timerlink);
}
static void
timerevent_destroy(isc_event_t *event0) {
isc_timer_t *timer = event0->ev_destroy_arg;
isc_timerevent_t *event = (isc_timerevent_t *)event0;
LOCK(&timer->lock);
if (ISC_LINK_LINKED(event, ev_timerlink)) {
/* The event was unlinked via timer_purge() */
timerevent_unlink(timer, event);
}
UNLOCK(&timer->lock);
isc_mem_put(timer->manager->mctx, event, event0->ev_size);
isc_refcount_increment(&timer->references);
if (timer->loop == isc_loop_current(timer->loop->loopmgr)) {
isc__timer_start(timer);
} else {
isc_async_run(timer->loop, isc__timer_start, timer);
}
}
static void
timer_purge(isc_timer_t *timer) {
isc_timerevent_t *event = NULL;
while ((event = ISC_LIST_HEAD(timer->active)) != NULL) {
timerevent_unlink(timer, event);
UNLOCK(&timer->lock);
(void)isc_task_purgeevent(timer->task, (isc_event_t *)event);
LOCK(&timer->lock);
}
}
void
isc_timer_create(isc_timermgr_t *manager, isc_task_t *task,
isc_taskaction_t action, void *arg, isc_timer_t **timerp) {
REQUIRE(VALID_MANAGER(manager));
REQUIRE(task != NULL);
REQUIRE(action != NULL);
isc_timer_t *timer;
isc_time_t now;
REQUIRE(timerp != NULL && *timerp == NULL);
/*
* Get current time.
*/
TIME_NOW(&now);
timer = isc_mem_get(manager->mctx, sizeof(*timer));
*timer = (isc_timer_t){
.manager = manager,
.type = isc_timertype_inactive,
.interval = *isc_interval_zero,
.action = action,
.arg = arg,
};
isc_time_settoepoch(&timer->idle);
isc_task_attach(task, &timer->task);
isc_mutex_init(&timer->lock);
ISC_LINK_INIT(timer, link);
ISC_LIST_INIT(timer->active);
timer->magic = TIMER_MAGIC;
/*
* Note we don't have to lock the timer like we normally would because
* there are no external references to it yet.
*/
*timerp = timer;
LOCK(&manager->lock);
APPEND(manager->timers, timer, link);
UNLOCK(&manager->lock);
}
isc_result_t
isc_timer_reset(isc_timer_t *timer, isc_timertype_t type,
const isc_interval_t *interval, bool purge) {
isc_time_t now;
isc_timermgr_t *manager;
isc_result_t result;
/*
* Change the timer's type, expires, and interval values to the given
* values. If 'purge' is true, any pending events from this timer
* are purged from its task's event queue.
*/
REQUIRE(VALID_TIMER(timer));
manager = timer->manager;
REQUIRE(VALID_MANAGER(manager));
if (interval == NULL) {
interval = isc_interval_zero;
}
REQUIRE(type == isc_timertype_inactive ||
!isc_interval_iszero(interval));
/*
* Get current time.
*/
if (type != isc_timertype_inactive) {
TIME_NOW(&now);
} else {
/*
* We don't have to do this, but it keeps the compiler from
* complaining about "now" possibly being used without being
* set, even though it will never actually happen.
*/
isc_time_settoepoch(&now);
}
LOCK(&manager->lock);
LOCK(&timer->lock);
if (purge) {
timer_purge(timer);
}
timer->type = type;
timer->interval = *interval;
if (type == isc_timertype_once && !isc_interval_iszero(interval)) {
result = isc_time_add(&now, interval, &timer->idle);
} else {
isc_time_settoepoch(&timer->idle);
result = ISC_R_SUCCESS;
}
if (result == ISC_R_SUCCESS) {
if (type == isc_timertype_inactive) {
deschedule(timer);
result = ISC_R_SUCCESS;
} else {
result = schedule(timer, &now, true);
}
}
UNLOCK(&timer->lock);
UNLOCK(&manager->lock);
return (result);
}
isc_timertype_t
isc_timer_gettype(isc_timer_t *timer) {
isc_timertype_t t;
timer_destroy(uv_handle_t *handle) {
isc_timer_t *timer = uv_handle_get_data(handle);
isc_loop_t *loop;
REQUIRE(VALID_TIMER(timer));
LOCK(&timer->lock);
t = timer->type;
UNLOCK(&timer->lock);
loop = timer->loop;
return (t);
isc_refcount_destroy(&timer->references);
isc_mutex_destroy(&timer->lock);
isc_mem_put(loop->mctx, timer, sizeof(*timer));
isc_loop_detach(&loop);
}
void
isc_timer_destroy(isc_timer_t **timerp) {
static void
isc__timer_destroy(void *arg) {
isc_timer_t *timer = (isc_timer_t *)arg;
uv_timer_stop(&timer->timer);
uv_close(&timer->timer, timer_destroy);
}
static void
isc__timer_detach(isc_timer_t **timerp) {
isc_timer_t *timer = NULL;
isc_timermgr_t *manager = NULL;
REQUIRE(timerp != NULL && VALID_TIMER(*timerp));
timer = *timerp;
*timerp = NULL;
manager = timer->manager;
LOCK(&manager->lock);
LOCK(&timer->lock);
timer_purge(timer);
deschedule(timer);
UNLOCK(&timer->lock);
UNLINK(manager->timers, timer, link);
UNLOCK(&manager->lock);
isc_task_detach(&timer->task);
isc_mutex_destroy(&timer->lock);
timer->magic = 0;
isc_mem_put(manager->mctx, timer, sizeof(*timer));
}
static void
post_event(isc_timermgr_t *manager, isc_timer_t *timer, isc_eventtype_t type) {
isc_timerevent_t *event;
XTRACEID("posting", timer);
event = (isc_timerevent_t *)isc_event_allocate(
manager->mctx, timer, type, timer->action, timer->arg,
sizeof(*event));
ISC_LINK_INIT(event, ev_timerlink);
((isc_event_t *)event)->ev_destroy = timerevent_destroy;
((isc_event_t *)event)->ev_destroy_arg = timer;
event->due = timer->due;
LOCK(&timer->lock);
ISC_LIST_APPEND(timer->active, event, ev_timerlink);
UNLOCK(&timer->lock);
isc_task_send(timer->task, ISC_EVENT_PTR(&event));
}
static void
dispatch(isc_timermgr_t *manager, isc_time_t *now) {
bool need_schedule;
isc_eventtype_t type = 0;
isc_timer_t *timer;
isc_result_t result;
/*!
* The caller must be holding the manager lock.
*/
while (manager->nscheduled > 0) {
timer = isc_heap_element(manager->heap, 1);
INSIST(timer != NULL && timer->type != isc_timertype_inactive);
if (isc_time_compare(now, &timer->due) < 0) {
manager->due = timer->due;
break;
}
switch (timer->type) {
case isc_timertype_ticker:
type = ISC_TIMEREVENT_TICK;
post_event(manager, timer, type);
need_schedule = true;
break;
case isc_timertype_once:
type = ISC_TIMEREVENT_ONCE;
post_event(manager, timer, type);
need_schedule = false;
break;
default:
UNREACHABLE();
}
timer->index = 0;
isc_heap_delete(manager->heap, 1);
manager->nscheduled--;
if (need_schedule) {
result = schedule(timer, now, false);
if (result != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__, "%s: %u",
"couldn't schedule "
"timer",
result);
}
}
}
}
static isc_threadresult_t
run(void *uap) {
isc_timermgr_t *manager = uap;
isc_time_t now;
isc_result_t result;
LOCK(&manager->lock);
while (!manager->done) {
TIME_NOW(&now);
XTRACETIME("running", now);
dispatch(manager, &now);
if (manager->nscheduled > 0) {
XTRACETIME2("waituntil", manager->due, now);
result = WAITUNTIL(&manager->wakeup, &manager->lock,
&manager->due);
INSIST(result == ISC_R_SUCCESS ||
result == ISC_R_TIMEDOUT);
if (isc_refcount_decrement(&timer->references) == 1) {
if (timer->loop == isc_loop_current(timer->loop->loopmgr)) {
isc__timer_destroy(timer);
} else {
XTRACETIME("wait", now);
WAIT(&manager->wakeup, &manager->lock);
isc_async_run(timer->loop, isc__timer_destroy, timer);
}
XTRACE("wakeup");
}
UNLOCK(&manager->lock);
return ((isc_threadresult_t)0);
}
static bool
sooner(void *v1, void *v2) {
isc_timer_t *t1, *t2;
t1 = v1;
t2 = v2;
REQUIRE(VALID_TIMER(t1));
REQUIRE(VALID_TIMER(t2));
if (isc_time_compare(&t1->due, &t2->due) < 0) {
return (true);
}
return (false);
}
static void
set_index(void *what, unsigned int index) {
isc_timer_t *timer;
REQUIRE(VALID_TIMER(what));
timer = what;
timer->index = index;
}
isc_result_t
isc__timermgr_create(isc_mem_t *mctx, isc_timermgr_t **managerp) {
isc_timermgr_t *manager;
/*
* Create a timer manager.
*/
REQUIRE(managerp != NULL && *managerp == NULL);
manager = isc_mem_get(mctx, sizeof(*manager));
manager->magic = TIMER_MANAGER_MAGIC;
manager->mctx = NULL;
manager->done = false;
INIT_LIST(manager->timers);
manager->nscheduled = 0;
isc_time_settoepoch(&manager->due);
manager->heap = NULL;
isc_heap_create(mctx, sooner, set_index, 0, &manager->heap);
isc_mutex_init(&manager->lock);
isc_mem_attach(mctx, &manager->mctx);
isc_condition_init(&manager->wakeup);
isc_thread_create(run, manager, &manager->thread);
isc_thread_setname(manager->thread, "isc-timer");
*managerp = manager;
return (ISC_R_SUCCESS);
}
void
isc__timermgr_destroy(isc_timermgr_t **managerp) {
isc_timermgr_t *manager;
/*
* Destroy a timer manager.
*/
REQUIRE(managerp != NULL);
manager = *managerp;
REQUIRE(VALID_MANAGER(manager));
LOCK(&manager->lock);
REQUIRE(EMPTY(manager->timers));
manager->done = true;
XTRACE("signal (destroy)");
SIGNAL(&manager->wakeup);
UNLOCK(&manager->lock);
/*
* Wait for thread to exit.
*/
isc_thread_join(manager->thread, NULL);
/*
* Clean up.
*/
isc_condition_destroy(&manager->wakeup);
isc_mutex_destroy(&manager->lock);
isc_heap_destroy(&manager->heap);
manager->magic = 0;
isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager));
*managerp = NULL;
isc_timer_destroy(isc_timer_t **timerp) {
isc__timer_detach(timerp);
}

View File

@@ -1,67 +0,0 @@
/*
* 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/mem.h>
#include <isc/result.h>
#include <isc/timer.h>
isc_result_t
isc__timermgr_create(isc_mem_t *mctx, isc_timermgr_t **managerp);
/*%<
* Create a timer manager.
*
* Notes:
*
*\li All memory will be allocated in memory context 'mctx'.
*
* Requires:
*
*\li 'mctx' is a valid memory context.
*
*\li 'managerp' points to a NULL isc_timermgr_t.
*
* Ensures:
*
*\li '*managerp' is a valid isc_timermgr_t.
*
* Returns:
*
*\li Success
*\li No memory
*\li Unexpected error
*/
void
isc__timermgr_destroy(isc_timermgr_t **managerp);
/*%<
* Destroy a timer manager.
*
* Notes:
*
*\li This routine blocks until there are no timers left in the manager,
* so if the caller holds any timer references using the manager, it
* must detach them before calling isc_timermgr_destroy() or it will
* block forever.
*
* Requires:
*
*\li '*managerp' is a valid isc_timermgr_t.
*
* Ensures:
*
*\li *managerp == NULL
*
*\li All resources used by the manager have been freed.
*/