ratelimiter.c revision 290001
1/* 2 * Copyright (C) 2004, 2005, 2007 Internet Systems Consortium, Inc. ("ISC") 3 * Copyright (C) 1999-2002 Internet Software Consortium. 4 * 5 * Permission to use, copy, modify, and/or distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 10 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 12 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 * PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18/* $Id: ratelimiter.c,v 1.25 2007/06/19 23:47:17 tbox Exp $ */ 19 20/*! \file */ 21 22#include <config.h> 23 24#include <isc/mem.h> 25#include <isc/ratelimiter.h> 26#include <isc/task.h> 27#include <isc/time.h> 28#include <isc/timer.h> 29#include <isc/util.h> 30 31typedef enum { 32 isc_ratelimiter_stalled = 0, 33 isc_ratelimiter_ratelimited = 1, 34 isc_ratelimiter_idle = 2, 35 isc_ratelimiter_shuttingdown = 3 36} isc_ratelimiter_state_t; 37 38struct isc_ratelimiter { 39 isc_mem_t * mctx; 40 isc_mutex_t lock; 41 int refs; 42 isc_task_t * task; 43 isc_timer_t * timer; 44 isc_interval_t interval; 45 isc_uint32_t pertic; 46 isc_ratelimiter_state_t state; 47 isc_event_t shutdownevent; 48 ISC_LIST(isc_event_t) pending; 49}; 50 51#define ISC_RATELIMITEREVENT_SHUTDOWN (ISC_EVENTCLASS_RATELIMITER + 1) 52 53static void 54ratelimiter_tick(isc_task_t *task, isc_event_t *event); 55 56static void 57ratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event); 58 59isc_result_t 60isc_ratelimiter_create(isc_mem_t *mctx, isc_timermgr_t *timermgr, 61 isc_task_t *task, isc_ratelimiter_t **ratelimiterp) 62{ 63 isc_result_t result; 64 isc_ratelimiter_t *rl; 65 INSIST(ratelimiterp != NULL && *ratelimiterp == NULL); 66 67 rl = isc_mem_get(mctx, sizeof(*rl)); 68 if (rl == NULL) 69 return ISC_R_NOMEMORY; 70 rl->mctx = mctx; 71 rl->refs = 1; 72 rl->task = task; 73 isc_interval_set(&rl->interval, 0, 0); 74 rl->timer = NULL; 75 rl->pertic = 1; 76 rl->state = isc_ratelimiter_idle; 77 ISC_LIST_INIT(rl->pending); 78 79 result = isc_mutex_init(&rl->lock); 80 if (result != ISC_R_SUCCESS) 81 goto free_mem; 82 result = isc_timer_create(timermgr, isc_timertype_inactive, 83 NULL, NULL, rl->task, ratelimiter_tick, 84 rl, &rl->timer); 85 if (result != ISC_R_SUCCESS) 86 goto free_mutex; 87 88 /* 89 * Increment the reference count to indicate that we may 90 * (soon) have events outstanding. 91 */ 92 rl->refs++; 93 94 ISC_EVENT_INIT(&rl->shutdownevent, 95 sizeof(isc_event_t), 96 0, NULL, ISC_RATELIMITEREVENT_SHUTDOWN, 97 ratelimiter_shutdowncomplete, rl, rl, NULL, NULL); 98 99 *ratelimiterp = rl; 100 return (ISC_R_SUCCESS); 101 102free_mutex: 103 DESTROYLOCK(&rl->lock); 104free_mem: 105 isc_mem_put(mctx, rl, sizeof(*rl)); 106 return (result); 107} 108 109isc_result_t 110isc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval) { 111 isc_result_t result = ISC_R_SUCCESS; 112 LOCK(&rl->lock); 113 rl->interval = *interval; 114 /* 115 * If the timer is currently running, change its rate. 116 */ 117 if (rl->state == isc_ratelimiter_ratelimited) { 118 result = isc_timer_reset(rl->timer, isc_timertype_ticker, NULL, 119 &rl->interval, ISC_FALSE); 120 } 121 UNLOCK(&rl->lock); 122 return (result); 123} 124 125void 126isc_ratelimiter_setpertic(isc_ratelimiter_t *rl, isc_uint32_t pertic) { 127 if (pertic == 0) 128 pertic = 1; 129 rl->pertic = pertic; 130} 131 132isc_result_t 133isc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_task_t *task, 134 isc_event_t **eventp) 135{ 136 isc_result_t result = ISC_R_SUCCESS; 137 isc_event_t *ev; 138 139 REQUIRE(eventp != NULL && *eventp != NULL); 140 REQUIRE(task != NULL); 141 ev = *eventp; 142 REQUIRE(ev->ev_sender == NULL); 143 144 LOCK(&rl->lock); 145 if (rl->state == isc_ratelimiter_ratelimited || 146 rl->state == isc_ratelimiter_stalled) { 147 isc_event_t *ev = *eventp; 148 ev->ev_sender = task; 149 ISC_LIST_APPEND(rl->pending, ev, ev_link); 150 *eventp = NULL; 151 } else if (rl->state == isc_ratelimiter_idle) { 152 result = isc_timer_reset(rl->timer, isc_timertype_ticker, NULL, 153 &rl->interval, ISC_FALSE); 154 if (result == ISC_R_SUCCESS) { 155 ev->ev_sender = task; 156 rl->state = isc_ratelimiter_ratelimited; 157 } 158 } else { 159 INSIST(rl->state == isc_ratelimiter_shuttingdown); 160 result = ISC_R_SHUTTINGDOWN; 161 } 162 UNLOCK(&rl->lock); 163 if (*eventp != NULL && result == ISC_R_SUCCESS) 164 isc_task_send(task, eventp); 165 return (result); 166} 167 168static void 169ratelimiter_tick(isc_task_t *task, isc_event_t *event) { 170 isc_result_t result = ISC_R_SUCCESS; 171 isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg; 172 isc_event_t *p; 173 isc_uint32_t pertic; 174 175 UNUSED(task); 176 177 isc_event_free(&event); 178 179 pertic = rl->pertic; 180 while (pertic != 0) { 181 pertic--; 182 LOCK(&rl->lock); 183 p = ISC_LIST_HEAD(rl->pending); 184 if (p != NULL) { 185 /* 186 * There is work to do. Let's do it after unlocking. 187 */ 188 ISC_LIST_UNLINK(rl->pending, p, ev_link); 189 } else { 190 /* 191 * No work left to do. Stop the timer so that we don't 192 * waste resources by having it fire periodically. 193 */ 194 result = isc_timer_reset(rl->timer, 195 isc_timertype_inactive, 196 NULL, NULL, ISC_FALSE); 197 RUNTIME_CHECK(result == ISC_R_SUCCESS); 198 rl->state = isc_ratelimiter_idle; 199 pertic = 0; /* Force the loop to exit. */ 200 } 201 UNLOCK(&rl->lock); 202 if (p != NULL) { 203 isc_task_t *evtask = p->ev_sender; 204 isc_task_send(evtask, &p); 205 } 206 INSIST(p == NULL); 207 } 208} 209 210void 211isc_ratelimiter_shutdown(isc_ratelimiter_t *rl) { 212 isc_event_t *ev; 213 isc_task_t *task; 214 LOCK(&rl->lock); 215 rl->state = isc_ratelimiter_shuttingdown; 216 (void)isc_timer_reset(rl->timer, isc_timertype_inactive, 217 NULL, NULL, ISC_FALSE); 218 while ((ev = ISC_LIST_HEAD(rl->pending)) != NULL) { 219 ISC_LIST_UNLINK(rl->pending, ev, ev_link); 220 ev->ev_attributes |= ISC_EVENTATTR_CANCELED; 221 task = ev->ev_sender; 222 isc_task_send(task, &ev); 223 } 224 isc_timer_detach(&rl->timer); 225 /* 226 * Send an event to our task. The delivery of this event 227 * indicates that no more timer events will be delivered. 228 */ 229 ev = &rl->shutdownevent; 230 isc_task_send(rl->task, &ev); 231 232 UNLOCK(&rl->lock); 233} 234 235static void 236ratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event) { 237 isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg; 238 239 UNUSED(task); 240 241 isc_ratelimiter_detach(&rl); 242} 243 244static void 245ratelimiter_free(isc_ratelimiter_t *rl) { 246 DESTROYLOCK(&rl->lock); 247 isc_mem_put(rl->mctx, rl, sizeof(*rl)); 248} 249 250void 251isc_ratelimiter_attach(isc_ratelimiter_t *source, isc_ratelimiter_t **target) { 252 REQUIRE(source != NULL); 253 REQUIRE(target != NULL && *target == NULL); 254 255 LOCK(&source->lock); 256 REQUIRE(source->refs > 0); 257 source->refs++; 258 INSIST(source->refs > 0); 259 UNLOCK(&source->lock); 260 *target = source; 261} 262 263void 264isc_ratelimiter_detach(isc_ratelimiter_t **rlp) { 265 isc_ratelimiter_t *rl = *rlp; 266 isc_boolean_t free_now = ISC_FALSE; 267 268 LOCK(&rl->lock); 269 REQUIRE(rl->refs > 0); 270 rl->refs--; 271 if (rl->refs == 0) 272 free_now = ISC_TRUE; 273 UNLOCK(&rl->lock); 274 275 if (free_now) 276 ratelimiter_free(rl); 277 278 *rlp = NULL; 279} 280 281isc_result_t 282isc_ratelimiter_stall(isc_ratelimiter_t *rl) { 283 isc_result_t result = ISC_R_SUCCESS; 284 285 LOCK(&rl->lock); 286 switch (rl->state) { 287 case isc_ratelimiter_shuttingdown: 288 result = ISC_R_SHUTTINGDOWN; 289 break; 290 case isc_ratelimiter_ratelimited: 291 result = isc_timer_reset(rl->timer, isc_timertype_inactive, 292 NULL, NULL, ISC_FALSE); 293 RUNTIME_CHECK(result == ISC_R_SUCCESS); 294 case isc_ratelimiter_idle: 295 case isc_ratelimiter_stalled: 296 rl->state = isc_ratelimiter_stalled; 297 break; 298 } 299 UNLOCK(&rl->lock); 300 return (result); 301} 302 303isc_result_t 304isc_ratelimiter_release(isc_ratelimiter_t *rl) { 305 isc_result_t result = ISC_R_SUCCESS; 306 307 LOCK(&rl->lock); 308 switch (rl->state) { 309 case isc_ratelimiter_shuttingdown: 310 result = ISC_R_SHUTTINGDOWN; 311 break; 312 case isc_ratelimiter_stalled: 313 if (!ISC_LIST_EMPTY(rl->pending)) { 314 result = isc_timer_reset(rl->timer, 315 isc_timertype_ticker, NULL, 316 &rl->interval, ISC_FALSE); 317 if (result == ISC_R_SUCCESS) 318 rl->state = isc_ratelimiter_ratelimited; 319 } else 320 rl->state = isc_ratelimiter_idle; 321 break; 322 case isc_ratelimiter_ratelimited: 323 case isc_ratelimiter_idle: 324 break; 325 } 326 UNLOCK(&rl->lock); 327 return (result); 328} 329