evsched.c 6.49 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*  Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

17 18
#include <sys/time.h>
#include <stdlib.h>
19
#include <stdio.h>
20
#include <string.h>
Marek Vavrusa's avatar
Marek Vavrusa committed
21
#include <assert.h>
22

23
#include "libknot/errcode.h"
24
#include "knot/common/evsched.h"
25

26 27 28 29 30
/*! \brief Some implementations of timercmp >= are broken, this is for compat.*/
static inline int timercmp_ge(struct timeval *a, struct timeval *b) {
	return timercmp(a, b, >) || timercmp(a, b, ==);
}

31
static int compare_event_heap_nodes(event_t *e1, event_t *e2)
32
{
33 34 35
	if (timercmp(&e1->tv, &e2->tv, <)) return -1;
	if (timercmp(&e1->tv, &e2->tv, >)) return 1;
	return 0;
36 37
}

38 39 40
/*!
 * \brief Set event timer to T (now) + dt miliseconds.
 */
41
static void evsched_settimer(event_t *e, uint32_t dt)
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
{
	if (!e) {
		return;
	}

	/* Get absolute time T. */
	gettimeofday(&e->tv, 0);

	/* Add number of seconds. */
	e->tv.tv_sec += dt / 1000;

	/* Add the number of microseconds. */
	e->tv.tv_usec += (dt % 1000) * 1000;

	/* Check for overflow. */
	while (e->tv.tv_usec > 999999) {
		e->tv.tv_sec += 1;
		e->tv.tv_usec -= 1 * 1000 * 1000;
	}
}

63
int evsched_init(evsched_t *sched, void *ctx)
64
{
65 66
	memset(sched, 0, sizeof(evsched_t));
	sched->ctx = ctx;
67 68

	/* Initialize event calendar. */
69 70
	pthread_mutex_init(&sched->run_lock, 0);
	pthread_mutex_init(&sched->heap_lock, 0);
71 72 73 74
	pthread_cond_init(&sched->notify, 0);
	heap_init(&sched->heap, compare_event_heap_nodes, 0);

	return KNOT_EOK;
75 76
}

77
void evsched_deinit(evsched_t *sched)
78
{
79
	if (sched == NULL) {
80 81 82 83
		return;
	}

	/* Deinitialize event calendar. */
84 85
	pthread_mutex_destroy(&sched->run_lock);
	pthread_mutex_destroy(&sched->heap_lock);
86 87 88
	pthread_cond_destroy(&sched->notify);

	while (! EMPTY_HEAP(&sched->heap))
89
	{
90 91 92
		event_t *e = *HHEAD(&sched->heap);
		heap_delmin(&sched->heap);
		evsched_event_free(e);
93
	}
Jan Včelák's avatar
Jan Včelák committed
94

95
	free(sched->heap.data);
96

97 98
	/* Clear the structure. */
	memset(sched, 0, sizeof(evsched_t));
99 100
}

101
event_t *evsched_event_create(evsched_t *sched, event_cb_t cb, void *data)
102
{
103 104
	/* Create event. */
	if (sched == NULL) {
105
		return NULL;
106 107 108
	}

	/* Allocate. */
109
	event_t *e = malloc(sizeof(event_t));
110
	if (e == NULL) {
111 112
		return NULL;
	}
113 114 115

	/* Initialize. */
	memset(e, 0, sizeof(event_t));
116 117 118
	e->sched = sched;
	e->cb = cb;
	e->data = data;
119

120 121 122
	return e;
}

123
void evsched_event_free(event_t *ev)
124
{
125
	if (ev == NULL) {
126 127 128
		return;
	}

129
	free(ev);
130 131
}

132
int evsched_schedule(event_t *ev, uint32_t dt)
133
{
134
	if (ev == NULL) {
135
		return KNOT_EINVAL;
136 137
	}

138 139
	/* Update event timer. */
	evsched_settimer(ev, dt);
Jan Včelák's avatar
Jan Včelák committed
140

141
	/* Lock calendar. */
142
	evsched_t *sched = ev->sched;
143
	pthread_mutex_lock(&sched->heap_lock);
144

145 146 147 148
	/* Make sure it's not already enqueued. */
	int found = 0;
	if ((found = heap_find(&sched->heap, ev))) {
		heap_delete(&sched->heap, found);
149 150
	}

151
	heap_insert(&sched->heap, ev);
152

153 154
	/* Unlock calendar. */
	pthread_cond_broadcast(&sched->notify);
155
	pthread_mutex_unlock(&sched->heap_lock);
156

157
	return KNOT_EOK;
158 159
}

160
static int evsched_try_cancel(evsched_t *sched, event_t *ev)
161
{
162
	int found = 0;
163

164
	if (sched == NULL || ev == NULL) {
165
		return KNOT_EINVAL;
166
	}
167 168 169 170
	
	/* Make sure not running. If an event is starting, we race for this lock
	 * and either win or lose. If we lose, we may find it in heap because
	 * it rescheduled itself. Either way, it will be marked as last running. */
171
	pthread_mutex_lock(&sched->run_lock);
172
	
173
	/* Lock calendar. */
174
	pthread_mutex_lock(&sched->heap_lock);
175
	
176 177
	if ((found = heap_find(&sched->heap, ev))) {
		heap_delete(&sched->heap, found);
178
	}
Jan Včelák's avatar
Jan Včelák committed
179

180
	/* Last running event was (probably) the one we're trying to cancel. */
181 182
	if (sched->last_ev == ev) {
		sched->last_ev = NULL;   /* Invalidate it. */
183
		found = KNOT_EAGAIN; /* Let's try again if it didn't reschedule itself. */
184
	}
185 186

	/* Unlock calendar. */
187
	pthread_cond_broadcast(&sched->notify);
188
	pthread_mutex_unlock(&sched->heap_lock);
189
	
190
	/* Enable running events. */
191
	pthread_mutex_unlock(&sched->run_lock);
192 193

	if (found > 0) {        /* Event canceled. */
194
		return KNOT_EOK;
195 196
	} else if (found < 0) { /* Error */
		return found;
197
	}
198

199
	return KNOT_ENOENT;     /* Not found. */
200
}
201

202
int evsched_cancel(event_t *ev)
203
{
204
	if (ev == NULL || ev->sched == NULL) {
205
		return KNOT_EINVAL;
206 207
	}

208 209 210
	/* Event may have already run, try again. */
	int ret = KNOT_EAGAIN;
	while (ret == KNOT_EAGAIN) {
211
		ret = evsched_try_cancel(ev->sched, ev);
212 213
	}

214 215
	/* Reset event timer. */
	memset(&ev->tv, 0, sizeof(struct timeval));
216
	/* Now we're sure event is canceled or finished. */
217
	return KNOT_EOK;
218
}
219 220 221 222 223 224 225 226 227

event_t* evsched_begin_process(evsched_t *sched)
{
	/* Check. */
	if (!sched) {
		return NULL;
	}

	/* Lock calendar. */
228
	pthread_mutex_lock(&sched->heap_lock);
229 230 231

	while(1) {

232
		/* Check event heap. */
233 234 235 236 237 238 239 240
		if (!EMPTY_HEAP(&sched->heap)) {

			/* Get current time. */
			struct timeval dt;
			gettimeofday(&dt, 0);

			/* Get next event. */
			event_t *next_ev = *((event_t**)HHEAD(&sched->heap));
241
			assert(next_ev != NULL);
242 243 244 245 246 247

			/* Immediately return. */
			if (timercmp_ge(&dt, &next_ev->tv)) {
				sched->last_ev = next_ev;
				sched->running = true;
				heap_delmin(&sched->heap);
248 249
				pthread_mutex_unlock(&sched->heap_lock);
				pthread_mutex_lock(&sched->run_lock);
250 251 252 253 254 255 256 257
				return next_ev;
			}

			/* Wait for next event or interrupt. Unlock calendar. */
			/* FIXME: Blocks this the possibility to add any event earlier? */
			struct timespec ts;
			ts.tv_sec = next_ev->tv.tv_sec;
			ts.tv_nsec = next_ev->tv.tv_usec * 1000L;
258
			pthread_cond_timedwait(&sched->notify, &sched->heap_lock, &ts);
259 260
		} else {
			/* Block until an event is scheduled. Unlock calendar.*/
261
			pthread_cond_wait(&sched->notify, &sched->heap_lock);
262 263 264
		}
	}

265 266
	/* Unlock heap, this shouldn't happen. */
	pthread_mutex_unlock(&sched->heap_lock);
267 268 269 270 271 272 273 274 275 276 277
	return NULL;
}

int evsched_end_process(evsched_t *sched)
{
	if (!sched) {
		return KNOT_EINVAL;
	}

	/* \note This enables event cancellation & running on next event. */
	if(sched->running) {
278
		if (pthread_mutex_unlock(&sched->run_lock) != 0) {
279 280 281 282 283 284 285 286 287
			return KNOT_ERROR;
		}
		sched->running = false; /* Mark as not running. */
	} else {
		return KNOT_ENOTRUNNING;
	}

	return KNOT_EOK;
}