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