Import bind 9.5.2 vendor sources.
[dragonfly.git] / contrib / bind-9.5.2 / lib / isc / ratelimiter.c
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
31 typedef 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
38 struct 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
53 static void
54 ratelimiter_tick(isc_task_t *task, isc_event_t *event);
55
56 static void
57 ratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event);
58
59 isc_result_t
60 isc_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
102 free_mutex:
103         DESTROYLOCK(&rl->lock);
104 free_mem:
105         isc_mem_put(mctx, rl, sizeof(*rl));
106         return (result);
107 }
108
109 isc_result_t
110 isc_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
125 void
126 isc_ratelimiter_setpertic(isc_ratelimiter_t *rl, isc_uint32_t pertic) {
127         if (pertic == 0)
128                 pertic = 1;
129         rl->pertic = pertic;
130 }
131
132 isc_result_t
133 isc_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
168 static void
169 ratelimiter_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
210 void
211 isc_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
235 static void
236 ratelimiter_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
244 static void
245 ratelimiter_free(isc_ratelimiter_t *rl) {
246         DESTROYLOCK(&rl->lock);
247         isc_mem_put(rl->mctx, rl, sizeof(*rl));
248 }
249
250 void
251 isc_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
263 void
264 isc_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
281 isc_result_t
282 isc_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
303 isc_result_t
304 isc_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 }