ratelimiter.c revision 254402
1249259Sdim/* 2249259Sdim * Copyright (C) 2004, 2005, 2007, 2012 Internet Systems Consortium, Inc. ("ISC") 3249259Sdim * Copyright (C) 1999-2002 Internet Software Consortium. 4249259Sdim * 5249259Sdim * Permission to use, copy, modify, and/or distribute this software for any 6249259Sdim * purpose with or without fee is hereby granted, provided that the above 7249259Sdim * copyright notice and this permission notice appear in all copies. 8249259Sdim * 9249259Sdim * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 10249259Sdim * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11249259Sdim * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 12249259Sdim * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13249259Sdim * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14249259Sdim * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15249259Sdim * PERFORMANCE OF THIS SOFTWARE. 16249259Sdim */ 17249259Sdim 18249259Sdim/* $Id: ratelimiter.c,v 1.25 2007/06/19 23:47:17 tbox Exp $ */ 19249259Sdim 20249259Sdim/*! \file */ 21249259Sdim 22249259Sdim#include <config.h> 23249259Sdim 24249259Sdim#include <isc/mem.h> 25249259Sdim#include <isc/ratelimiter.h> 26249259Sdim#include <isc/task.h> 27249259Sdim#include <isc/time.h> 28249259Sdim#include <isc/timer.h> 29249259Sdim#include <isc/util.h> 30249259Sdim 31249259Sdimtypedef enum { 32249259Sdim isc_ratelimiter_stalled = 0, 33249259Sdim isc_ratelimiter_ratelimited = 1, 34249259Sdim isc_ratelimiter_idle = 2, 35249259Sdim isc_ratelimiter_shuttingdown = 3 36249259Sdim} isc_ratelimiter_state_t; 37249259Sdim 38249259Sdimstruct isc_ratelimiter { 39249259Sdim isc_mem_t * mctx; 40249259Sdim isc_mutex_t lock; 41249259Sdim int refs; 42249259Sdim isc_task_t * task; 43249259Sdim isc_timer_t * timer; 44249259Sdim isc_interval_t interval; 45249259Sdim isc_uint32_t pertic; 46249259Sdim isc_ratelimiter_state_t state; 47249259Sdim isc_event_t shutdownevent; 48249259Sdim ISC_LIST(isc_event_t) pending; 49249259Sdim}; 50249259Sdim 51249259Sdim#define ISC_RATELIMITEREVENT_SHUTDOWN (ISC_EVENTCLASS_RATELIMITER + 1) 52249259Sdim 53249259Sdimstatic void 54249259Sdimratelimiter_tick(isc_task_t *task, isc_event_t *event); 55249259Sdim 56249259Sdimstatic void 57249259Sdimratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event); 58249259Sdim 59249259Sdimisc_result_t 60249259Sdimisc_ratelimiter_create(isc_mem_t *mctx, isc_timermgr_t *timermgr, 61249259Sdim isc_task_t *task, isc_ratelimiter_t **ratelimiterp) 62249259Sdim{ 63249259Sdim isc_result_t result; 64249259Sdim isc_ratelimiter_t *rl; 65249259Sdim INSIST(ratelimiterp != NULL && *ratelimiterp == NULL); 66249259Sdim 67249259Sdim rl = isc_mem_get(mctx, sizeof(*rl)); 68249259Sdim if (rl == NULL) 69249259Sdim return ISC_R_NOMEMORY; 70249259Sdim rl->mctx = mctx; 71249259Sdim rl->refs = 1; 72249259Sdim rl->task = task; 73249259Sdim isc_interval_set(&rl->interval, 0, 0); 74249259Sdim rl->timer = NULL; 75249259Sdim rl->pertic = 1; 76249259Sdim rl->state = isc_ratelimiter_idle; 77249259Sdim ISC_LIST_INIT(rl->pending); 78249259Sdim 79249259Sdim result = isc_mutex_init(&rl->lock); 80249259Sdim if (result != ISC_R_SUCCESS) 81249259Sdim goto free_mem; 82249259Sdim result = isc_timer_create(timermgr, isc_timertype_inactive, 83249259Sdim NULL, NULL, rl->task, ratelimiter_tick, 84249259Sdim rl, &rl->timer); 85249259Sdim if (result != ISC_R_SUCCESS) 86249259Sdim goto free_mutex; 87249259Sdim 88249259Sdim /* 89249259Sdim * Increment the reference count to indicate that we may 90249259Sdim * (soon) have events outstanding. 91249259Sdim */ 92249259Sdim rl->refs++; 93249259Sdim 94249259Sdim ISC_EVENT_INIT(&rl->shutdownevent, 95249259Sdim sizeof(isc_event_t), 96249259Sdim 0, NULL, ISC_RATELIMITEREVENT_SHUTDOWN, 97249259Sdim ratelimiter_shutdowncomplete, rl, rl, NULL, NULL); 98249259Sdim 99249259Sdim *ratelimiterp = rl; 100249259Sdim return (ISC_R_SUCCESS); 101249259Sdim 102249259Sdimfree_mutex: 103249259Sdim DESTROYLOCK(&rl->lock); 104249259Sdimfree_mem: 105249259Sdim isc_mem_put(mctx, rl, sizeof(*rl)); 106249259Sdim return (result); 107249259Sdim} 108249259Sdim 109249259Sdimisc_result_t 110249259Sdimisc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval) { 111249259Sdim isc_result_t result = ISC_R_SUCCESS; 112249259Sdim LOCK(&rl->lock); 113249259Sdim rl->interval = *interval; 114249259Sdim /* 115249259Sdim * If the timer is currently running, change its rate. 116249259Sdim */ 117249259Sdim if (rl->state == isc_ratelimiter_ratelimited) { 118249259Sdim result = isc_timer_reset(rl->timer, isc_timertype_ticker, NULL, 119249259Sdim &rl->interval, ISC_FALSE); 120249259Sdim } 121249259Sdim UNLOCK(&rl->lock); 122249259Sdim return (result); 123249259Sdim} 124249259Sdim 125249259Sdimvoid 126249259Sdimisc_ratelimiter_setpertic(isc_ratelimiter_t *rl, isc_uint32_t pertic) { 127249259Sdim if (pertic == 0) 128249259Sdim pertic = 1; 129249259Sdim rl->pertic = pertic; 130249259Sdim} 131249259Sdim 132249259Sdimisc_result_t 133249259Sdimisc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_task_t *task, 134249259Sdim isc_event_t **eventp) 135249259Sdim{ 136249259Sdim isc_result_t result = ISC_R_SUCCESS; 137249259Sdim isc_event_t *ev; 138249259Sdim 139249259Sdim REQUIRE(eventp != NULL && *eventp != NULL); 140249259Sdim REQUIRE(task != NULL); 141249259Sdim ev = *eventp; 142249259Sdim REQUIRE(ev->ev_sender == NULL); 143249259Sdim 144249259Sdim LOCK(&rl->lock); 145249259Sdim if (rl->state == isc_ratelimiter_ratelimited || 146249259Sdim rl->state == isc_ratelimiter_stalled) { 147249259Sdim isc_event_t *ev = *eventp; 148249259Sdim ev->ev_sender = task; 149249259Sdim ISC_LIST_APPEND(rl->pending, ev, ev_link); 150249259Sdim *eventp = NULL; 151249259Sdim } else if (rl->state == isc_ratelimiter_idle) { 152249259Sdim result = isc_timer_reset(rl->timer, isc_timertype_ticker, NULL, 153249259Sdim &rl->interval, ISC_FALSE); 154249259Sdim if (result == ISC_R_SUCCESS) { 155249259Sdim ev->ev_sender = task; 156249259Sdim rl->state = isc_ratelimiter_ratelimited; 157249259Sdim } 158249259Sdim } else { 159249259Sdim INSIST(rl->state == isc_ratelimiter_shuttingdown); 160249259Sdim result = ISC_R_SHUTTINGDOWN; 161249259Sdim } 162249259Sdim UNLOCK(&rl->lock); 163249259Sdim if (*eventp != NULL && result == ISC_R_SUCCESS) 164249259Sdim isc_task_send(task, eventp); 165249259Sdim return (result); 166249259Sdim} 167249259Sdim 168249259Sdimstatic void 169249259Sdimratelimiter_tick(isc_task_t *task, isc_event_t *event) { 170249259Sdim isc_result_t result = ISC_R_SUCCESS; 171249259Sdim isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg; 172249259Sdim isc_event_t *p; 173249259Sdim isc_uint32_t pertic; 174249259Sdim 175249259Sdim UNUSED(task); 176249259Sdim 177249259Sdim isc_event_free(&event); 178249259Sdim 179249259Sdim pertic = rl->pertic; 180249259Sdim while (pertic != 0) { 181249259Sdim pertic--; 182249259Sdim LOCK(&rl->lock); 183249259Sdim p = ISC_LIST_HEAD(rl->pending); 184249259Sdim if (p != NULL) { 185249259Sdim /* 186249259Sdim * There is work to do. Let's do it after unlocking. 187249259Sdim */ 188249259Sdim ISC_LIST_UNLINK(rl->pending, p, ev_link); 189249259Sdim } else { 190249259Sdim /* 191249259Sdim * No work left to do. Stop the timer so that we don't 192249259Sdim * waste resources by having it fire periodically. 193249259Sdim */ 194249259Sdim result = isc_timer_reset(rl->timer, 195249259Sdim isc_timertype_inactive, 196249259Sdim NULL, NULL, ISC_FALSE); 197249259Sdim RUNTIME_CHECK(result == ISC_R_SUCCESS); 198249259Sdim rl->state = isc_ratelimiter_idle; 199249259Sdim pertic = 0; /* Force the loop to exit. */ 200249259Sdim } 201249259Sdim UNLOCK(&rl->lock); 202249259Sdim if (p != NULL) { 203249259Sdim isc_task_t *evtask = p->ev_sender; 204249259Sdim isc_task_send(evtask, &p); 205249259Sdim } 206249259Sdim INSIST(p == NULL); 207249259Sdim } 208249259Sdim} 209249259Sdim 210249259Sdimvoid 211249259Sdimisc_ratelimiter_shutdown(isc_ratelimiter_t *rl) { 212249259Sdim isc_event_t *ev; 213249259Sdim isc_task_t *task; 214249259Sdim LOCK(&rl->lock); 215249259Sdim rl->state = isc_ratelimiter_shuttingdown; 216249259Sdim (void)isc_timer_reset(rl->timer, isc_timertype_inactive, 217249259Sdim NULL, NULL, ISC_FALSE); 218249259Sdim while ((ev = ISC_LIST_HEAD(rl->pending)) != NULL) { 219249259Sdim ISC_LIST_UNLINK(rl->pending, ev, ev_link); 220249259Sdim ev->ev_attributes |= ISC_EVENTATTR_CANCELED; 221249259Sdim task = ev->ev_sender; 222249259Sdim isc_task_send(task, &ev); 223249259Sdim } 224249259Sdim isc_timer_detach(&rl->timer); 225249259Sdim /* 226249259Sdim * Send an event to our task. The delivery of this event 227249259Sdim * indicates that no more timer events will be delivered. 228249259Sdim */ 229249259Sdim ev = &rl->shutdownevent; 230249259Sdim isc_task_send(rl->task, &ev); 231249259Sdim 232249259Sdim UNLOCK(&rl->lock); 233249259Sdim} 234249259Sdim 235249259Sdimstatic void 236249259Sdimratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event) { 237249259Sdim isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg; 238249259Sdim 239249259Sdim UNUSED(task); 240249259Sdim 241249259Sdim isc_ratelimiter_detach(&rl); 242249259Sdim} 243249259Sdim 244249259Sdimstatic void 245249259Sdimratelimiter_free(isc_ratelimiter_t *rl) { 246249259Sdim DESTROYLOCK(&rl->lock); 247249259Sdim isc_mem_put(rl->mctx, rl, sizeof(*rl)); 248249259Sdim} 249249259Sdim 250249259Sdimvoid 251249259Sdimisc_ratelimiter_attach(isc_ratelimiter_t *source, isc_ratelimiter_t **target) { 252249259Sdim REQUIRE(source != NULL); 253249259Sdim REQUIRE(target != NULL && *target == NULL); 254249259Sdim 255249259Sdim LOCK(&source->lock); 256249259Sdim REQUIRE(source->refs > 0); 257249259Sdim source->refs++; 258249259Sdim INSIST(source->refs > 0); 259249259Sdim UNLOCK(&source->lock); 260249259Sdim *target = source; 261249259Sdim} 262249259Sdim 263249259Sdimvoid 264249259Sdimisc_ratelimiter_detach(isc_ratelimiter_t **rlp) { 265249259Sdim isc_ratelimiter_t *rl = *rlp; 266249259Sdim isc_boolean_t free_now = ISC_FALSE; 267249259Sdim 268249259Sdim LOCK(&rl->lock); 269249259Sdim REQUIRE(rl->refs > 0); 270249259Sdim rl->refs--; 271249259Sdim if (rl->refs == 0) 272249259Sdim free_now = ISC_TRUE; 273249259Sdim UNLOCK(&rl->lock); 274249259Sdim 275249259Sdim if (free_now) 276249259Sdim ratelimiter_free(rl); 277249259Sdim 278249259Sdim *rlp = NULL; 279249259Sdim} 280249259Sdim 281249259Sdimisc_result_t 282249259Sdimisc_ratelimiter_stall(isc_ratelimiter_t *rl) { 283249259Sdim isc_result_t result = ISC_R_SUCCESS; 284249259Sdim 285249259Sdim LOCK(&rl->lock); 286249259Sdim switch (rl->state) { 287249259Sdim case isc_ratelimiter_shuttingdown: 288249259Sdim result = ISC_R_SHUTTINGDOWN; 289249259Sdim break; 290249259Sdim case isc_ratelimiter_ratelimited: 291249259Sdim result = isc_timer_reset(rl->timer, isc_timertype_inactive, 292249259Sdim NULL, NULL, ISC_FALSE); 293249259Sdim RUNTIME_CHECK(result == ISC_R_SUCCESS); 294249259Sdim /* FALLTHROUGH */ 295249259Sdim case isc_ratelimiter_idle: 296249259Sdim case isc_ratelimiter_stalled: 297249259Sdim rl->state = isc_ratelimiter_stalled; 298249259Sdim break; 299249259Sdim } 300249259Sdim UNLOCK(&rl->lock); 301249259Sdim return (result); 302249259Sdim} 303249259Sdim 304249259Sdimisc_result_t 305249259Sdimisc_ratelimiter_release(isc_ratelimiter_t *rl) { 306249259Sdim isc_result_t result = ISC_R_SUCCESS; 307249259Sdim 308249259Sdim LOCK(&rl->lock); 309249259Sdim switch (rl->state) { 310249259Sdim case isc_ratelimiter_shuttingdown: 311249259Sdim result = ISC_R_SHUTTINGDOWN; 312249259Sdim break; 313249259Sdim case isc_ratelimiter_stalled: 314249259Sdim if (!ISC_LIST_EMPTY(rl->pending)) { 315249259Sdim result = isc_timer_reset(rl->timer, 316249259Sdim isc_timertype_ticker, NULL, 317249259Sdim &rl->interval, ISC_FALSE); 318249259Sdim if (result == ISC_R_SUCCESS) 319249259Sdim rl->state = isc_ratelimiter_ratelimited; 320249259Sdim } else 321249259Sdim rl->state = isc_ratelimiter_idle; 322249259Sdim break; 323249259Sdim case isc_ratelimiter_ratelimited: 324249259Sdim case isc_ratelimiter_idle: 325249259Sdim break; 326249259Sdim } 327249259Sdim UNLOCK(&rl->lock); 328249259Sdim return (result); 329249259Sdim} 330249259Sdim