Merge branch '3555-refactor-isc_ratelimiter-API' into 'main'

Refactor isc_ratelimiter API

Closes #3555

See merge request isc-projects/bind9!6842
This commit is contained in:
Ondřej Surý 2022-09-30 13:23:31 +00:00
commit 9beb68513b
7 changed files with 450 additions and 85 deletions

View file

@ -10008,6 +10008,11 @@ run_server(isc_task_t *task, isc_event_t *event) {
isc_event_free(&event);
CHECKFATAL(dns_zonemgr_create(named_g_mctx, named_g_loopmgr,
named_g_taskmgr, named_g_netmgr,
&server->zonemgr),
"dns_zonemgr_create");
CHECKFATAL(dns_dispatchmgr_create(named_g_mctx, named_g_netmgr,
&named_g_dispatchmgr),
"creating dispatch manager");
@ -10322,11 +10327,6 @@ named_server_create(isc_mem_t *mctx, named_server_t **serverp) {
server->sighup = isc_signal_new(
named_g_loopmgr, named_server_reloadwanted, server, SIGHUP);
CHECKFATAL(dns_zonemgr_create(named_g_mctx, named_g_loopmgr,
named_g_taskmgr, named_g_netmgr,
&server->zonemgr),
"dns_zonemgr_create");
CHECKFATAL(isc_stats_create(server->mctx, &server->sockstats,
isc_sockstatscounter_max),
"isc_stats_create");

View file

@ -18865,7 +18865,7 @@ dns_zonemgr_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr,
dns_zonemgr_t **zmgrp) {
dns_zonemgr_t *zmgr;
isc_result_t result;
isc_loop_t *mainloop = isc_loop_main(loopmgr);
isc_loop_t *loop = isc_loop_current(loopmgr);
REQUIRE(mctx != NULL);
REQUIRE(loopmgr != NULL);
@ -18899,35 +18899,11 @@ dns_zonemgr_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr,
/* Unreachable lock. */
isc_rwlock_init(&zmgr->urlock, 0, 0);
result = isc_ratelimiter_create(mainloop, &zmgr->checkdsrl);
INSIST(result == ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS) {
goto free_urlock;
}
result = isc_ratelimiter_create(mainloop, &zmgr->notifyrl);
INSIST(result == ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS) {
goto free_checkdsrl;
}
result = isc_ratelimiter_create(mainloop, &zmgr->refreshrl);
INSIST(result == ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS) {
goto free_notifyrl;
}
result = isc_ratelimiter_create(mainloop, &zmgr->startupnotifyrl);
INSIST(result == ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS) {
goto free_refreshrl;
}
result = isc_ratelimiter_create(mainloop, &zmgr->startuprefreshrl);
INSIST(result == ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS) {
goto free_startupnotifyrl;
}
isc_ratelimiter_create(loop, &zmgr->checkdsrl);
isc_ratelimiter_create(loop, &zmgr->notifyrl);
isc_ratelimiter_create(loop, &zmgr->refreshrl);
isc_ratelimiter_create(loop, &zmgr->startupnotifyrl);
isc_ratelimiter_create(loop, &zmgr->startuprefreshrl);
zmgr->zonetasks = isc_mem_get(
zmgr->mctx, zmgr->workers * sizeof(zmgr->zonetasks[0]));
@ -19012,19 +18988,15 @@ free_zonetasks:
isc_ratelimiter_shutdown(zmgr->startuprefreshrl);
isc_ratelimiter_detach(&zmgr->startuprefreshrl);
free_startupnotifyrl:
isc_ratelimiter_shutdown(zmgr->startupnotifyrl);
isc_ratelimiter_detach(&zmgr->startupnotifyrl);
free_refreshrl:
isc_ratelimiter_shutdown(zmgr->refreshrl);
isc_ratelimiter_detach(&zmgr->refreshrl);
free_notifyrl:
isc_ratelimiter_shutdown(zmgr->notifyrl);
isc_ratelimiter_detach(&zmgr->notifyrl);
free_checkdsrl:
isc_ratelimiter_shutdown(zmgr->checkdsrl);
isc_ratelimiter_detach(&zmgr->checkdsrl);
free_urlock:
isc_rwlock_destroy(&zmgr->urlock);
isc_rwlock_destroy(&zmgr->rwlock);
isc_mem_put(zmgr->mctx, zmgr, sizeof(*zmgr));

View file

@ -41,8 +41,8 @@ ISC_LANG_BEGINDECLS
***** Functions.
*****/
isc_result_t
isc_ratelimiter_create(isc_loop_t *loop, isc_ratelimiter_t **ratelimiterp);
void
isc_ratelimiter_create(isc_loop_t *loop, isc_ratelimiter_t **rlp);
/*%<
* Create a rate limiter. The execution interval is initially undefined.
*/

View file

@ -174,6 +174,7 @@ isc_refcount_decrement(isc_refcount_t *target) {
} \
\
void name##_detach(name##_t **ptrp) { \
REQUIRE(ptrp != NULL && *ptrp != NULL); \
name##_t *ptr = *ptrp; \
*ptrp = NULL; \
name##_unref(ptr); \

View file

@ -52,14 +52,23 @@ struct isc_ratelimiter {
};
static void
ratelimiter_tick(void *arg);
isc__ratelimiter_tick(void *arg);
isc_result_t
isc_ratelimiter_create(isc_loop_t *loop, isc_ratelimiter_t **ratelimiterp) {
static void
isc__ratelimiter_start(void *arg);
static void
isc__ratelimiter_doshutdown(void *arg);
void
isc_ratelimiter_create(isc_loop_t *loop, isc_ratelimiter_t **rlp) {
isc_ratelimiter_t *rl = NULL;
isc_mem_t *mctx = isc_loop_getmctx(loop);
isc_mem_t *mctx;
INSIST(ratelimiterp != NULL && *ratelimiterp == NULL);
REQUIRE(loop != NULL);
REQUIRE(rlp != NULL && *rlp == NULL);
mctx = isc_loop_getmctx(loop);
rl = isc_mem_get(mctx, sizeof(*rl));
*rl = (isc_ratelimiter_t){
@ -74,10 +83,11 @@ isc_ratelimiter_create(isc_loop_t *loop, isc_ratelimiter_t **ratelimiterp) {
isc_interval_set(&rl->interval, 0, 0);
ISC_LIST_INIT(rl->pending);
isc_timer_create(rl->loop, isc__ratelimiter_tick, rl, &rl->timer);
isc_mutex_init(&rl->lock);
*ratelimiterp = rl;
return (ISC_R_SUCCESS);
*rlp = rl;
}
void
@ -87,10 +97,7 @@ isc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval) {
LOCK(&rl->lock);
rl->interval = *interval;
/*
* If the timer is currently running, its rate will change during
* the next tick.
*/
/* The interval will be adjusted on the next tick */
UNLOCK(&rl->lock);
}
@ -113,6 +120,37 @@ isc_ratelimiter_setpushpop(isc_ratelimiter_t *rl, bool pushpop) {
UNLOCK(&rl->lock);
}
static void
isc__ratelimiter_start(void *arg) {
isc_ratelimiter_t *rl = arg;
isc_interval_t interval;
REQUIRE(VALID_RATELIMITER(rl));
LOCK(&rl->lock);
switch (rl->state) {
case isc_ratelimiter_ratelimited:
/* The first tick happens immediately */
isc_interval_set(&interval, 0, 0);
isc_timer_start(rl->timer, isc_timertype_once, &interval);
break;
case isc_ratelimiter_shuttingdown:
/* The ratelimiter is shutting down */
break;
case isc_ratelimiter_idle:
/*
* This could happen if we are changing the interval on the
* ratelimiter, but all the events were processed and the timer
* was stopped before the new interval could be applied.
*/
break;
default:
UNREACHABLE();
}
UNLOCK(&rl->lock);
isc_ratelimiter_detach(&rl);
}
isc_result_t
isc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_task_t *task,
isc_event_t **eventp) {
@ -133,9 +171,9 @@ isc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_task_t *task,
case isc_ratelimiter_idle:
/* Start the ratelimiter */
isc_ratelimiter_ref(rl);
isc_async_run(rl->loop, ratelimiter_tick, rl);
isc_async_run(rl->loop, isc__ratelimiter_start, rl);
rl->state = isc_ratelimiter_ratelimited;
/* FALLTHROUGH */
FALLTHROUGH;
case isc_ratelimiter_ratelimited:
event->ev_sender = task;
*eventp = NULL;
@ -172,11 +210,10 @@ isc_ratelimiter_dequeue(isc_ratelimiter_t *rl, isc_event_t *event) {
}
static void
ratelimiter_tick(void *arg) {
isc__ratelimiter_tick(void *arg) {
isc_ratelimiter_t *rl = (isc_ratelimiter_t *)arg;
isc_event_t *event;
uint32_t pertic;
bool do_destroy = false;
ISC_LIST(isc_event_t) pending;
REQUIRE(VALID_RATELIMITER(rl));
@ -184,68 +221,88 @@ ratelimiter_tick(void *arg) {
ISC_LIST_INIT(pending);
LOCK(&rl->lock);
REQUIRE(rl->timer != NULL);
if (rl->state == isc_ratelimiter_shuttingdown) {
UNLOCK(&rl->lock);
do_destroy = (rl->timer != NULL);
goto done;
INSIST(EMPTY(rl->pending));
goto unlock;
}
if (rl->timer == NULL) {
isc_timer_create(rl->loop, ratelimiter_tick, rl, &rl->timer);
}
/*
* If the timer was already running with a different rate,
* this updates it to the correct one.
*/
isc_timer_start(rl->timer, isc_timertype_ticker, &rl->interval);
pertic = rl->pertic;
while (pertic != 0) {
pertic--;
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, event, ev_ratelink);
ISC_LIST_APPEND(pending, event, ev_ratelink);
} else {
/* There's no more work to do, destroy the timer */
do_destroy = true;
/*
* We processed all the scheduled work, but there's a
* room for at least one more event (we haven't consumed
* all of the "pertick"), so we can stop the ratelimiter
* now, and don't worry about isc_ratelimiter_enqueue()
* sending an extra event immediately.
*/
rl->state = isc_ratelimiter_idle;
break;
}
pertic--;
}
if (rl->state != isc_ratelimiter_idle) {
/* Reschedule the timer */
isc_timer_start(rl->timer, isc_timertype_once, &rl->interval);
}
unlock:
UNLOCK(&rl->lock);
while ((event = ISC_LIST_HEAD(pending)) != NULL) {
ISC_LIST_UNLINK(pending, event, ev_ratelink);
isc_task_send(event->ev_sender, &event);
}
}
done:
/* No work left to do. Stop and destroy the timer. */
if (do_destroy) {
isc_timer_destroy(&rl->timer);
isc_ratelimiter_detach(&rl);
}
void
isc__ratelimiter_doshutdown(void *arg) {
isc_ratelimiter_t *rl = arg;
REQUIRE(VALID_RATELIMITER(rl));
LOCK(&rl->lock);
INSIST(rl->state == isc_ratelimiter_shuttingdown);
INSIST(EMPTY(rl->pending));
isc_timer_stop(rl->timer);
isc_timer_destroy(&rl->timer);
isc_loop_detach(&rl->loop);
UNLOCK(&rl->lock);
isc_ratelimiter_detach(&rl);
}
void
isc_ratelimiter_shutdown(isc_ratelimiter_t *rl) {
isc_event_t *event;
ISC_LIST(isc_event_t) pending;
REQUIRE(VALID_RATELIMITER(rl));
LOCK(&rl->lock);
rl->state = isc_ratelimiter_shuttingdown;
ISC_LIST_INIT(pending);
while ((event = ISC_LIST_HEAD(rl->pending)) != NULL) {
ISC_LIST_UNLINK(rl->pending, event, ev_ratelink);
LOCK(&rl->lock);
if (rl->state != isc_ratelimiter_shuttingdown) {
rl->state = isc_ratelimiter_shuttingdown;
ISC_LIST_MOVE(pending, rl->pending);
isc_ratelimiter_ref(rl);
isc_async_run(rl->loop, isc__ratelimiter_doshutdown, rl);
}
UNLOCK(&rl->lock);
while ((event = ISC_LIST_HEAD(pending)) != NULL) {
ISC_LIST_UNLINK(pending, event, ev_ratelink);
event->ev_attributes |= ISC_EVENTATTR_CANCELED;
isc_task_send(event->ev_sender, &event);
}
isc_loop_detach(&rl->loop);
UNLOCK(&rl->lock);
}
static void

View file

@ -32,6 +32,7 @@ check_PROGRAMS = \
quota_test \
radix_test \
random_test \
ratelimiter_test\
regex_test \
result_test \
safe_test \

View file

@ -0,0 +1,334 @@
/*
* 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 <inttypes.h>
#include <sched.h> /* IWYU pragma: keep */
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define UNIT_TESTING
#include <cmocka.h>
#include <isc/event.h>
#include <isc/job.h>
#include <isc/loop.h>
#include <isc/ratelimiter.h>
#include <isc/task.h>
#include "ratelimiter.c"
#include <tests/isc.h>
#define NS_PER_S 1000000000 /*%< Nanoseconds per second. */
isc_ratelimiter_t *rl = NULL;
ISC_LOOP_TEST_IMPL(ratelimiter_create) {
rl = NULL;
expect_assert_failure(isc_ratelimiter_create(NULL, &rl));
expect_assert_failure(isc_ratelimiter_create(mainloop, NULL));
rl = (isc_ratelimiter_t *)&rl;
expect_assert_failure(isc_ratelimiter_create(mainloop, &rl));
rl = NULL;
isc_ratelimiter_create(mainloop, &rl);
isc_ratelimiter_shutdown(rl);
isc_ratelimiter_detach(&rl);
isc_loopmgr_shutdown(loopmgr);
}
ISC_LOOP_TEST_IMPL(ratelimiter_shutdown) {
rl = NULL;
expect_assert_failure(isc_ratelimiter_shutdown(NULL));
expect_assert_failure(isc_ratelimiter_shutdown(rl));
isc_loopmgr_shutdown(loopmgr);
}
ISC_LOOP_TEST_IMPL(ratelimiter_detach) {
rl = NULL;
expect_assert_failure(isc_ratelimiter_detach(NULL));
expect_assert_failure(isc_ratelimiter_detach(&rl));
isc_loopmgr_shutdown(loopmgr);
}
static int ticks = 0;
static isc_task_t *rl_task = NULL;
static isc_time_t start_time;
static isc_time_t tick_time;
static void
tick(isc_task_t *task, isc_event_t *event) {
assert_ptr_equal(task, rl_task);
isc_event_free(&event);
ticks++;
assert_int_equal(isc_time_now(&tick_time), ISC_R_SUCCESS);
isc_loopmgr_shutdown(loopmgr);
isc_task_detach(&rl_task);
isc_ratelimiter_shutdown(rl);
isc_ratelimiter_detach(&rl);
}
ISC_LOOP_SETUP_IMPL(ratelimiter_common) {
isc_result_t result = isc_task_create(taskmgr, &rl_task, 0);
assert_int_equal(result, ISC_R_SUCCESS);
rl = NULL;
isc_time_set(&tick_time, 0, 0);
assert_int_equal(isc_time_now(&start_time), ISC_R_SUCCESS);
isc_ratelimiter_create(mainloop, &rl);
}
ISC_LOOP_SETUP_IMPL(ratelimiter_enqueue) {
ticks = 0;
setup_loop_ratelimiter_common(arg);
}
ISC_LOOP_TEARDOWN_IMPL(ratelimiter_enqueue) { assert_int_equal(ticks, 1); }
ISC_LOOP_TEST_SETUP_TEARDOWN_IMPL(ratelimiter_enqueue) {
isc_result_t result;
isc_event_t *event = NULL;
event = isc_event_allocate(mctx, NULL, ISC_TASKEVENT_TEST, tick, NULL,
sizeof(isc_event_t));
assert_non_null(event);
result = isc_ratelimiter_enqueue(rl, rl_task, &event);
assert_int_equal(result, ISC_R_SUCCESS);
}
ISC_LOOP_SETUP_IMPL(ratelimiter_enqueue_shutdown) {
ticks = 0;
setup_loop_ratelimiter_common(arg);
}
ISC_LOOP_TEARDOWN_IMPL(ratelimiter_enqueue_shutdown) {
assert_int_equal(ticks, 1);
}
ISC_LOOP_TEST_SETUP_TEARDOWN_IMPL(ratelimiter_enqueue_shutdown) {
isc_event_t *event = NULL;
event = isc_event_allocate(mctx, NULL, ISC_TASKEVENT_TEST, tick, NULL,
sizeof(isc_event_t));
assert_non_null(event);
expect_assert_failure(isc_ratelimiter_enqueue(NULL, rl_task, &event));
expect_assert_failure(isc_ratelimiter_enqueue(rl, NULL, &event));
expect_assert_failure(isc_ratelimiter_enqueue(rl, rl_task, NULL));
expect_assert_failure(
isc_ratelimiter_enqueue(rl, rl_task, &(isc_event_t *){ NULL }));
assert_int_equal(isc_ratelimiter_enqueue(rl, rl_task, &event),
ISC_R_SUCCESS);
isc_ratelimiter_shutdown(rl);
event = isc_event_allocate(mctx, NULL, ISC_TASKEVENT_TEST, tick, NULL,
sizeof(isc_event_t));
assert_non_null(event);
assert_int_equal(isc_ratelimiter_enqueue(rl, rl_task, &event),
ISC_R_SHUTTINGDOWN);
isc_event_free(&event);
}
ISC_LOOP_SETUP_IMPL(ratelimiter_dequeue) {
ticks = 0;
setup_loop_ratelimiter_common(arg);
}
ISC_LOOP_TEARDOWN_IMPL(ratelimiter_dequeue) { /* */
assert_int_equal(ticks, 1);
}
ISC_LOOP_TEST_SETUP_TEARDOWN_IMPL(ratelimiter_dequeue) {
isc_event_t *event = NULL;
event = isc_event_allocate(mctx, NULL, ISC_TASKEVENT_TEST, tick, NULL,
sizeof(isc_event_t));
assert_non_null(event);
assert_int_equal(
isc_ratelimiter_enqueue(rl, rl_task, &(isc_event_t *){ event }),
ISC_R_SUCCESS);
assert_int_equal(isc_ratelimiter_dequeue(rl, event), ISC_R_SUCCESS);
isc_event_free(&event);
assert_null(event);
/* This event didn't get scheduled */
event = isc_event_allocate(mctx, NULL, ISC_TASKEVENT_TEST, tick, NULL,
sizeof(isc_event_t));
assert_non_null(event);
assert_int_equal(isc_ratelimiter_dequeue(rl, event), ISC_R_NOTFOUND);
assert_int_equal(isc_ratelimiter_enqueue(rl, rl_task, &event),
ISC_R_SUCCESS);
assert_null(event);
}
static isc_time_t tock_time;
static void
tock(isc_task_t *task, isc_event_t *event) {
assert_ptr_equal(task, rl_task);
isc_event_free(&event);
ticks++;
assert_int_equal(isc_time_now(&tock_time), ISC_R_SUCCESS);
}
ISC_LOOP_SETUP_IMPL(ratelimiter_pertick_interval) {
ticks = 0;
isc_time_set(&tock_time, 0, 0);
setup_loop_ratelimiter_common(arg);
}
ISC_LOOP_TEARDOWN_IMPL(ratelimiter_pertick_interval) {
uint64_t t = isc_time_microdiff(&tick_time, &tock_time);
assert_int_equal(ticks, 2);
assert_true(t >= 1000000);
t = isc_time_microdiff(&tock_time, &start_time);
assert_true(t < 1000000);
}
ISC_LOOP_TEST_SETUP_TEARDOWN_IMPL(ratelimiter_pertick_interval) {
isc_event_t *event = NULL;
isc_interval_t interval;
isc_interval_set(&interval, 1, NS_PER_S / 10);
expect_assert_failure(isc_ratelimiter_setinterval(NULL, &interval));
expect_assert_failure(isc_ratelimiter_setinterval(rl, NULL));
expect_assert_failure(isc_ratelimiter_setpertic(NULL, 1));
expect_assert_failure(isc_ratelimiter_setpertic(rl, 0));
expect_assert_failure(isc_ratelimiter_setpushpop(NULL, false));
isc_ratelimiter_setinterval(rl, &interval);
isc_ratelimiter_setpertic(rl, 1);
isc_ratelimiter_setpushpop(rl, false);
event = isc_event_allocate(mctx, NULL, ISC_TASKEVENT_TEST, tock, NULL,
sizeof(isc_event_t));
assert_non_null(event);
assert_int_equal(isc_ratelimiter_enqueue(rl, rl_task, &event),
ISC_R_SUCCESS);
event = isc_event_allocate(mctx, NULL, ISC_TASKEVENT_TEST, tick, NULL,
sizeof(isc_event_t));
assert_non_null(event);
assert_int_equal(isc_ratelimiter_enqueue(rl, rl_task, &event),
ISC_R_SUCCESS);
}
ISC_LOOP_SETUP_IMPL(ratelimiter_pushpop) {
ticks = 0;
isc_time_set(&tock_time, 0, 0);
setup_loop_ratelimiter_common(arg);
}
ISC_LOOP_TEARDOWN_IMPL(ratelimiter_pushpop) {
uint64_t t = isc_time_microdiff(&tock_time, &tick_time);
assert_int_equal(ticks, 2);
assert_true(t < 1000000);
}
ISC_LOOP_TEST_SETUP_TEARDOWN_IMPL(ratelimiter_pushpop) {
isc_event_t *event = NULL;
isc_interval_t interval;
isc_interval_set(&interval, 1, NS_PER_S / 10);
isc_ratelimiter_setinterval(rl, &interval);
isc_ratelimiter_setpertic(rl, 2);
isc_ratelimiter_setpushpop(rl, true);
event = isc_event_allocate(mctx, NULL, ISC_TASKEVENT_TEST, tick, NULL,
sizeof(isc_event_t));
assert_non_null(event);
assert_int_equal(
isc_ratelimiter_enqueue(rl, rl_task, &(isc_event_t *){ event }),
ISC_R_SUCCESS);
event = isc_event_allocate(mctx, NULL, ISC_TASKEVENT_TEST, tock, NULL,
sizeof(isc_event_t));
assert_non_null(event);
assert_int_equal(
isc_ratelimiter_enqueue(rl, rl_task, &(isc_event_t *){ event }),
ISC_R_SUCCESS);
}
static int
setup_test(void **state) {
int r;
r = setup_loopmgr(state);
if (r != 0) {
return (r);
}
r = setup_taskmgr(state);
if (r != 0) {
return (r);
}
return (0);
}
static int
teardown_test(void **state) {
int r;
r = teardown_taskmgr(state);
if (r != 0) {
return (r);
}
r = teardown_loopmgr(state);
if (r != 0) {
return (r);
}
return (0);
}
ISC_TEST_LIST_START
ISC_TEST_ENTRY_CUSTOM(ratelimiter_create, setup_test, teardown_test)
ISC_TEST_ENTRY_CUSTOM(ratelimiter_shutdown, setup_test, teardown_test)
ISC_TEST_ENTRY_CUSTOM(ratelimiter_detach, setup_test, teardown_test)
ISC_TEST_ENTRY_CUSTOM(ratelimiter_enqueue, setup_test, teardown_test)
ISC_TEST_ENTRY_CUSTOM(ratelimiter_enqueue_shutdown, setup_test, teardown_test)
ISC_TEST_ENTRY_CUSTOM(ratelimiter_dequeue, setup_test, teardown_test)
ISC_TEST_ENTRY_CUSTOM(ratelimiter_pertick_interval, setup_test, teardown_test)
ISC_TEST_ENTRY_CUSTOM(ratelimiter_pushpop, setup_test, teardown_test)
ISC_TEST_LIST_END
ISC_TEST_MAIN