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