diff --git a/lib/isc/Makefile.in b/lib/isc/Makefile.in index b10c99b7b1..4ca051c8f6 100644 --- a/lib/isc/Makefile.in +++ b/lib/isc/Makefile.in @@ -47,7 +47,8 @@ OBJS = @ISC_EXTRA_OBJS@ \ assertions.@O@ base64.@O@ bitstring.@O@ buffer.@O@ \ bufferlist.@O@ commandline.@O@ error.@O@ event.@O@ heap.@O@ \ lex.@O@ lib.@O@ log.@O@ \ - mem.@O@ mutexblock.@O@ random.@O@ result.@O@ rwlock.@O@ \ + mem.@O@ mutexblock.@O@ random.@O@ \ + ratelimiter.@O@ result.@O@ rwlock.@O@ \ serial.@O@ sockaddr.@O@ str.@O@ symtab.@O@ \ task.@O@ taskpool.@O@ timer.@O@ version.@O@ \ ${UNIXOBJS} ${NLSOBJS} ${PTHREADOBJS} @@ -57,7 +58,8 @@ SRCS = @ISC_EXTRA_SRCS@ \ assertions.c base64.c bitstring.c buffer.c \ bufferlist.c commandline.c error.c event.c heap.c \ lex.c lib.c log.c \ - mem.c mutexblock.c random.c result.c rwlock.c \ + mem.c mutexblock.c random.c \ + ratelimiter.c result.c rwlock.c \ serial.c sockaddr.c str.c symtab.c \ task.c taskpool.c timer.c version.c diff --git a/lib/isc/include/isc/ratelimiter.h b/lib/isc/include/isc/ratelimiter.h new file mode 100644 index 0000000000..e31de77f91 --- /dev/null +++ b/lib/isc/include/isc/ratelimiter.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 1999 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +#ifndef ISC_RATELIMITER_H +#define ISC_RATELIMITER_H 1 + +/***** + ***** Module Info + *****/ + +/* + * A rate limiter is a mechanism for dispatching events at a limited + * rate. This is intended to be used when sending zone maintenance + * SOA queries, NOTIFY messages, etc. + */ + +/*** + *** Imports. + ***/ + +#include +#include + +ISC_LANG_BEGINDECLS + +/***** + ***** Types. + *****/ + +typedef struct isc_ratelimiter isc_ratelimiter_t; + +typedef enum { + isc_ratelimiter_ratelimited, + isc_ratelimiter_worklimited +} isc_ratelimiter_state_t; + +/***** + ***** Functions. + *****/ + +isc_result_t +isc_ratelimiter_create(isc_mem_t *mctx, isc_timermgr_t *timermgr, + isc_task_t *task, isc_ratelimiter_t **ratelimiterp); +/* + * Create a rate limiter. It will execute events in the context + * of 'task' with a guaranteed minimum interval, initially zero. + */ + +isc_result_t +isc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval); +/* + * Set the mininum interval between event executions. + * The interval value is copied, so the caller need not preserve it. + */ + +isc_result_t +isc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_event_t **eventp); +/* + * Queue an event for rate-limited execution. This is similar + * to doing an isc_task_send() to the rate limiter's task, except + * that the execution may be delayed to achieve the desired rate + * of execution. + */ + +void +isc_ratelimiter_destroy(isc_ratelimiter_t **ratelimiterp); +/* + * Destroy a rate limiter. All events that have not yet been + * dispatched to the task are freed immedately. + * Does not destroy the task or events already queued on it. + */ + +#endif /* ISC_RATELIMITER_H */ diff --git a/lib/isc/ratelimiter.c b/lib/isc/ratelimiter.c new file mode 100644 index 0000000000..5890f74e3d --- /dev/null +++ b/lib/isc/ratelimiter.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 1999 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +#include + +#include +#include +#include +#include + +#include "util.h" + +struct isc_ratelimiter { + isc_mem_t * mctx; + isc_mutex_t lock; + isc_task_t * task; + isc_timer_t * timer; + isc_interval_t interval; + isc_ratelimiter_state_t state; + ISC_LIST(isc_event_t) pending; +}; + +static void ratelimiter_tick(isc_task_t *task, isc_event_t *event); + +isc_result_t +isc_ratelimiter_create(isc_mem_t *mctx, isc_timermgr_t *timermgr, + isc_task_t *task, isc_ratelimiter_t **ratelimiterp) +{ + isc_result_t result; + isc_ratelimiter_t *rl; + INSIST(ratelimiterp != NULL && *ratelimiterp == NULL); + + rl = isc_mem_get(mctx, sizeof(*rl)); + if (rl == NULL) + return ISC_R_NOMEMORY; + rl->mctx = mctx; + rl->task = task; + isc_interval_set(&rl->interval, 0, 0); + rl->timer = NULL; + rl->state = isc_ratelimiter_worklimited; + ISC_LIST_INIT(rl->pending); + + result = isc_mutex_init(&rl->lock); + if (result != ISC_R_SUCCESS) + goto free_mem; + result = isc_timer_create(timermgr, isc_timertype_inactive, + NULL, NULL, rl->task, ratelimiter_tick, + rl, &rl->timer); + if (result != ISC_R_SUCCESS) + goto free_mutex; + + *ratelimiterp = rl; + return (ISC_R_SUCCESS); + +free_mutex: + isc_mutex_destroy(&rl->lock); +free_mem: + isc_mem_put(mctx, rl, sizeof(*rl)); + return (result); +} + +isc_result_t +isc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval) +{ + isc_result_t result = ISC_R_SUCCESS; + LOCK(&rl->lock); + rl->interval = *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, NULL, + &rl->interval, ISC_FALSE); + } + UNLOCK(&rl->lock); + return (result); +} + + +isc_result_t +isc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_event_t **eventp) +{ + isc_result_t result = ISC_R_SUCCESS; + INSIST(eventp != NULL && *eventp != NULL); + LOCK(&rl->lock); + if (rl->state == isc_ratelimiter_ratelimited) { + isc_event_t *ev = *eventp; + ISC_LIST_APPEND(rl->pending, ev, link); + *eventp = NULL; + } else { + result = isc_timer_reset(rl->timer, isc_timertype_ticker, NULL, + &rl->interval, ISC_FALSE); + if (result == ISC_R_SUCCESS) + rl->state = isc_ratelimiter_ratelimited; + } + UNLOCK(&rl->lock); + if (*eventp != NULL) + isc_task_send(rl->task, eventp); + ENSURE(*eventp == NULL); + return (result); +} + +static void +ratelimiter_tick(isc_task_t *task, isc_event_t *event) +{ + isc_result_t result = ISC_R_SUCCESS; + isc_ratelimiter_t *rl = (isc_ratelimiter_t *) event->arg; + isc_event_t *p; + (void) task; /* Unused */ + LOCK(&rl->lock); + p = ISC_LIST_HEAD(rl->pending); + if (p != NULL) { + /* + * There is work to do. Let's do it after unlocking. + */ + ISC_LIST_UNLINK(rl->pending, p, link); + } else { + /* + * No work left to do. Stop the timer so that we don't + * waste resources by having it fire periodically. + */ + result = isc_timer_reset(rl->timer, isc_timertype_inactive, + NULL, NULL, ISC_FALSE); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + rl->state = isc_ratelimiter_worklimited; + } + UNLOCK(&rl->lock); + isc_event_free(&event); + /* + * If we have an event, dispatch it. + * There is potential for optimization here since + * we are already executing in the context of "task". + */ + if (p != NULL) + isc_task_send(rl->task, &p); + INSIST(p == NULL); +} + +void +isc_ratelimiter_destroy(isc_ratelimiter_t **ratelimiterp) +{ + isc_ratelimiter_t *rl = *ratelimiterp; + isc_event_t *p; + (void) isc_timer_reset(rl->timer, isc_timertype_inactive, + NULL, NULL, ISC_FALSE); + isc_timer_detach(&rl->timer); + while ((p = ISC_LIST_HEAD(rl->pending)) != NULL) { + ISC_LIST_UNLINK(rl->pending, p, link); + isc_event_free(&p); + } + isc_mutex_destroy(&rl->lock); + isc_mem_put(rl->mctx, rl, sizeof(*rl)); + *ratelimiterp = NULL; +}