diff options
Diffstat (limited to 'src/util/scheduler.c')
-rw-r--r-- | src/util/scheduler.c | 886 |
1 files changed, 886 insertions, 0 deletions
diff --git a/src/util/scheduler.c b/src/util/scheduler.c new file mode 100644 index 000000000..0d30910e0 --- /dev/null +++ b/src/util/scheduler.c | |||
@@ -0,0 +1,886 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet | ||
3 | (C) 2009 Christian Grothoff (and other contributing authors) | ||
4 | |||
5 | GNUnet is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published | ||
7 | by the Free Software Foundation; either version 2, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License | ||
16 | along with GNUnet; see the file COPYING. If not, write to the | ||
17 | Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
18 | Boston, MA 02111-1307, USA. | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file util/scheduler/scheduler.c | ||
23 | * @brief schedule computations using continuation passing style | ||
24 | * @author Christian Grothoff | ||
25 | */ | ||
26 | #include "platform.h" | ||
27 | #include "gnunet_common.h" | ||
28 | #include "gnunet_scheduler_lib.h" | ||
29 | #include "gnunet_signal_lib.h" | ||
30 | #include "gnunet_time_lib.h" | ||
31 | |||
32 | /** | ||
33 | * Linked list of pending tasks. | ||
34 | */ | ||
35 | struct Task | ||
36 | { | ||
37 | /** | ||
38 | * This is a linked list. | ||
39 | */ | ||
40 | struct Task *next; | ||
41 | |||
42 | /** | ||
43 | * Function to run when ready. | ||
44 | */ | ||
45 | GNUNET_SCHEDULER_Task callback; | ||
46 | |||
47 | /** | ||
48 | * Closure for the callback. | ||
49 | */ | ||
50 | void *callback_cls; | ||
51 | |||
52 | /** | ||
53 | * Set of file descriptors this task is waiting | ||
54 | * for for reading. Once ready, this is updated | ||
55 | * to reflect the set of file descriptors ready | ||
56 | * for operation. | ||
57 | */ | ||
58 | fd_set read_set; | ||
59 | |||
60 | /** | ||
61 | * Set of file descriptors this task is waiting | ||
62 | * for for writing. Once ready, this is updated | ||
63 | * to reflect the set of file descriptors ready | ||
64 | * for operation. | ||
65 | */ | ||
66 | fd_set write_set; | ||
67 | |||
68 | /** | ||
69 | * Unique task identifier. | ||
70 | */ | ||
71 | GNUNET_SCHEDULER_TaskIdentifier id; | ||
72 | |||
73 | /** | ||
74 | * Identifier of a prerequisite task. | ||
75 | */ | ||
76 | GNUNET_SCHEDULER_TaskIdentifier prereq_id; | ||
77 | |||
78 | /** | ||
79 | * Absolute timeout value for the task, or | ||
80 | * GNUNET_TIME_UNIT_FOREVER_ABS for "no timeout". | ||
81 | */ | ||
82 | struct GNUNET_TIME_Absolute timeout; | ||
83 | |||
84 | /** | ||
85 | * Why is the task ready? Set after task is added to ready queue. | ||
86 | * Initially set to zero. All reasons that have already been | ||
87 | * satisfied (i.e. read or write ready) will be set over time. | ||
88 | */ | ||
89 | enum GNUNET_SCHEDULER_Reason reason; | ||
90 | |||
91 | /** | ||
92 | * Task priority. | ||
93 | */ | ||
94 | enum GNUNET_SCHEDULER_Priority priority; | ||
95 | |||
96 | /** | ||
97 | * highest-numbered file descriptor in read_set or write_set plus one | ||
98 | */ | ||
99 | int nfds; | ||
100 | |||
101 | /** | ||
102 | * Should this task be run on shutdown? | ||
103 | */ | ||
104 | int run_on_shutdown; | ||
105 | |||
106 | }; | ||
107 | |||
108 | |||
109 | /** | ||
110 | * Handle for the scheduling service. | ||
111 | */ | ||
112 | struct GNUNET_SCHEDULER_Handle | ||
113 | { | ||
114 | |||
115 | /** | ||
116 | * List of tasks waiting for an event. | ||
117 | */ | ||
118 | struct Task *pending; | ||
119 | |||
120 | /** | ||
121 | * List of tasks ready to run right now, | ||
122 | * grouped by importance. | ||
123 | */ | ||
124 | struct Task *ready[GNUNET_SCHEDULER_PRIORITY_COUNT]; | ||
125 | |||
126 | /** | ||
127 | * Identity of the last task queued. Incremented for each task to | ||
128 | * generate a unique task ID (it is virtually impossible to start | ||
129 | * more than 2^64 tasks during the lifetime of a process). | ||
130 | */ | ||
131 | GNUNET_SCHEDULER_TaskIdentifier last_id; | ||
132 | |||
133 | /** | ||
134 | * Highest number so that all tasks with smaller identifiers | ||
135 | * have already completed. Also the lowest number of a task | ||
136 | * still waiting to be executed. | ||
137 | */ | ||
138 | GNUNET_SCHEDULER_TaskIdentifier lowest_pending_id; | ||
139 | |||
140 | /** | ||
141 | * GNUNET_NO if we are running normally, | ||
142 | * GNUNET_YES if we are in shutdown mode. | ||
143 | */ | ||
144 | int shutdown; | ||
145 | |||
146 | /** | ||
147 | * Number of tasks on the ready list. | ||
148 | */ | ||
149 | unsigned int ready_count; | ||
150 | |||
151 | /** | ||
152 | * Priority of the task running right now. Only | ||
153 | * valid while a task is running. | ||
154 | */ | ||
155 | enum GNUNET_SCHEDULER_Priority current_priority; | ||
156 | |||
157 | }; | ||
158 | |||
159 | |||
160 | /** | ||
161 | * Check that the given priority is legal (and return it). | ||
162 | */ | ||
163 | static enum GNUNET_SCHEDULER_Priority | ||
164 | check_priority (enum GNUNET_SCHEDULER_Priority p) | ||
165 | { | ||
166 | if ((p >= 0) && (p < GNUNET_SCHEDULER_PRIORITY_COUNT)) | ||
167 | return p; | ||
168 | GNUNET_assert (0); | ||
169 | return 0; /* make compiler happy */ | ||
170 | } | ||
171 | |||
172 | |||
173 | /** | ||
174 | * Update the timeout value so that it is smaller than min. | ||
175 | */ | ||
176 | static void | ||
177 | update_timeout (struct timeval *tv, struct GNUNET_TIME_Relative min) | ||
178 | { | ||
179 | if (((tv->tv_sec * 1000) + (tv->tv_usec / 1000)) > min.value) | ||
180 | { | ||
181 | tv->tv_sec = min.value / 1000; | ||
182 | tv->tv_usec = (min.value - tv->tv_sec * 1000) * 1000; | ||
183 | } | ||
184 | } | ||
185 | |||
186 | |||
187 | /** | ||
188 | * Set the given file descriptor bit in the given set and update max | ||
189 | * to the maximum of the existing max and fd+1. | ||
190 | */ | ||
191 | static void | ||
192 | set_fd (int fd, int *max, fd_set * set) | ||
193 | { | ||
194 | if (*max <= fd) | ||
195 | *max = fd + 1; | ||
196 | FD_SET (fd, set); | ||
197 | } | ||
198 | |||
199 | |||
200 | /** | ||
201 | * Is a task with this identifier still pending? Also updates | ||
202 | * "lowest_pending_id" as a side-effect (for faster checks in the | ||
203 | * future), but only if the return value is "GNUNET_NO" (and | ||
204 | * the "lowest_pending_id" check failed). | ||
205 | * | ||
206 | * @return GNUNET_YES if so, GNUNET_NO if not | ||
207 | */ | ||
208 | static int | ||
209 | is_pending (struct GNUNET_SCHEDULER_Handle *sched, | ||
210 | GNUNET_SCHEDULER_TaskIdentifier id) | ||
211 | { | ||
212 | struct Task *pos; | ||
213 | enum GNUNET_SCHEDULER_Priority p; | ||
214 | GNUNET_SCHEDULER_TaskIdentifier min; | ||
215 | |||
216 | if (id < sched->lowest_pending_id) | ||
217 | return GNUNET_NO; | ||
218 | min = -1; /* maximum value */ | ||
219 | pos = sched->pending; | ||
220 | while (pos != NULL) | ||
221 | { | ||
222 | if (pos->id == id) | ||
223 | return GNUNET_YES; | ||
224 | if (pos->id < min) | ||
225 | min = pos->id; | ||
226 | pos = pos->next; | ||
227 | } | ||
228 | for (p = 0; p < GNUNET_SCHEDULER_PRIORITY_COUNT; p++) | ||
229 | { | ||
230 | pos = sched->ready[p]; | ||
231 | while (pos != NULL) | ||
232 | { | ||
233 | if (pos->id == id) | ||
234 | return GNUNET_YES; | ||
235 | if (pos->id < min) | ||
236 | min = pos->id; | ||
237 | pos = pos->next; | ||
238 | } | ||
239 | } | ||
240 | sched->lowest_pending_id = min; | ||
241 | return GNUNET_NO; | ||
242 | } | ||
243 | |||
244 | |||
245 | /** | ||
246 | * Update all sets and timeout for select. | ||
247 | */ | ||
248 | static void | ||
249 | update_sets (struct GNUNET_SCHEDULER_Handle *sched, | ||
250 | int *max, fd_set * rs, fd_set * ws, struct timeval *tv) | ||
251 | { | ||
252 | int i; | ||
253 | struct Task *pos; | ||
254 | |||
255 | pos = sched->pending; | ||
256 | while (pos != NULL) | ||
257 | { | ||
258 | if ((pos->prereq_id != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) && | ||
259 | (GNUNET_YES == is_pending (sched, pos->prereq_id))) | ||
260 | { | ||
261 | pos = pos->next; | ||
262 | continue; | ||
263 | } | ||
264 | |||
265 | if (pos->timeout.value != GNUNET_TIME_UNIT_FOREVER_ABS.value) | ||
266 | update_timeout (tv, | ||
267 | GNUNET_TIME_absolute_get_remaining (pos->timeout)); | ||
268 | for (i = 0; i < pos->nfds; i++) | ||
269 | { | ||
270 | if (FD_ISSET (i, &pos->read_set)) | ||
271 | set_fd (i, max, rs); | ||
272 | if (FD_ISSET (i, &pos->write_set)) | ||
273 | set_fd (i, max, ws); | ||
274 | } | ||
275 | pos = pos->next; | ||
276 | } | ||
277 | } | ||
278 | |||
279 | |||
280 | /** | ||
281 | * Check if the ready set overlaps with the set we want to have ready. | ||
282 | * If so, update the want set (set all FDs that are ready). If not, | ||
283 | * return GNUNET_NO. | ||
284 | * | ||
285 | * @param maxfd highest FD that needs to be checked. | ||
286 | * @return GNUNET_YES if there was some overlap | ||
287 | */ | ||
288 | static int | ||
289 | set_overlaps (const fd_set * ready, fd_set * want, int maxfd) | ||
290 | { | ||
291 | int i; | ||
292 | |||
293 | for (i = 0; i < maxfd; i++) | ||
294 | if (FD_ISSET (i, want) && FD_ISSET (i, ready)) | ||
295 | { | ||
296 | /* copy all over (yes, there maybe unrelated bits, | ||
297 | but this should not hurt well-written clients) */ | ||
298 | memcpy (want, ready, sizeof (fd_set)); | ||
299 | return GNUNET_YES; | ||
300 | } | ||
301 | return GNUNET_NO; | ||
302 | } | ||
303 | |||
304 | |||
305 | /** | ||
306 | * Check if the given task is eligible to run now. | ||
307 | * Also set the reason why it is eligible. | ||
308 | * | ||
309 | * @return GNUNET_YES if we can run it, GNUNET_NO if not. | ||
310 | */ | ||
311 | static int | ||
312 | is_ready (struct GNUNET_SCHEDULER_Handle *sched, | ||
313 | struct Task *task, | ||
314 | struct GNUNET_TIME_Absolute now, | ||
315 | const fd_set * rs, const fd_set * ws) | ||
316 | { | ||
317 | if ((GNUNET_NO == task->run_on_shutdown) && (GNUNET_YES == sched->shutdown)) | ||
318 | return GNUNET_NO; | ||
319 | if ((GNUNET_YES == task->run_on_shutdown) && | ||
320 | (GNUNET_YES == sched->shutdown)) | ||
321 | task->reason |= GNUNET_SCHEDULER_REASON_SHUTDOWN; | ||
322 | if (now.value >= task->timeout.value) | ||
323 | task->reason |= GNUNET_SCHEDULER_REASON_TIMEOUT; | ||
324 | if ((0 == (task->reason & GNUNET_SCHEDULER_REASON_READ_READY)) && | ||
325 | (rs != NULL) && (set_overlaps (rs, &task->read_set, task->nfds))) | ||
326 | task->reason |= GNUNET_SCHEDULER_REASON_READ_READY; | ||
327 | if ((0 == (task->reason & GNUNET_SCHEDULER_REASON_WRITE_READY)) && | ||
328 | (ws != NULL) && (set_overlaps (ws, &task->write_set, task->nfds))) | ||
329 | task->reason |= GNUNET_SCHEDULER_REASON_WRITE_READY; | ||
330 | if (task->reason == 0) | ||
331 | return GNUNET_NO; /* not ready */ | ||
332 | if (task->prereq_id != GNUNET_SCHEDULER_NO_PREREQUISITE_TASK) | ||
333 | { | ||
334 | if (GNUNET_YES == is_pending (sched, task->prereq_id)) | ||
335 | return GNUNET_NO; /* prereq waiting */ | ||
336 | task->reason |= GNUNET_SCHEDULER_REASON_PREREQ_DONE; | ||
337 | } | ||
338 | return GNUNET_YES; | ||
339 | } | ||
340 | |||
341 | |||
342 | /** | ||
343 | * Put a task that is ready for execution into the ready queue. | ||
344 | */ | ||
345 | static void | ||
346 | queue_ready_task (struct GNUNET_SCHEDULER_Handle *handle, struct Task *task) | ||
347 | { | ||
348 | task->next = handle->ready[check_priority (task->priority)]; | ||
349 | handle->ready[check_priority (task->priority)] = task; | ||
350 | handle->ready_count++; | ||
351 | } | ||
352 | |||
353 | |||
354 | /** | ||
355 | * Check which tasks are ready and move them | ||
356 | * to the respective ready queue. | ||
357 | */ | ||
358 | static void | ||
359 | check_ready (struct GNUNET_SCHEDULER_Handle *handle, | ||
360 | const fd_set * rs, const fd_set * ws) | ||
361 | { | ||
362 | struct Task *pos; | ||
363 | struct Task *prev; | ||
364 | struct Task *next; | ||
365 | struct GNUNET_TIME_Absolute now; | ||
366 | |||
367 | now = GNUNET_TIME_absolute_get (); | ||
368 | prev = NULL; | ||
369 | pos = handle->pending; | ||
370 | while (pos != NULL) | ||
371 | { | ||
372 | next = pos->next; | ||
373 | if (GNUNET_YES == is_ready (handle, pos, now, rs, ws)) | ||
374 | { | ||
375 | if (prev == NULL) | ||
376 | handle->pending = next; | ||
377 | else | ||
378 | prev->next = next; | ||
379 | queue_ready_task (handle, pos); | ||
380 | pos = next; | ||
381 | continue; | ||
382 | } | ||
383 | prev = pos; | ||
384 | pos = next; | ||
385 | } | ||
386 | } | ||
387 | |||
388 | |||
389 | /** | ||
390 | * Run at least one task in the highest-priority queue that is not | ||
391 | * empty. Keep running tasks until we are either no longer running | ||
392 | * "URGENT" tasks or until we have at least one "pending" task (which | ||
393 | * may become ready, hence we should select on it). Naturally, if | ||
394 | * there are no more ready tasks, we also return. | ||
395 | */ | ||
396 | static void | ||
397 | run_ready (struct GNUNET_SCHEDULER_Handle *sched) | ||
398 | { | ||
399 | enum GNUNET_SCHEDULER_Priority p; | ||
400 | struct Task *pos; | ||
401 | struct GNUNET_SCHEDULER_TaskContext tc; | ||
402 | |||
403 | do | ||
404 | { | ||
405 | if (sched->ready_count == 0) | ||
406 | return; | ||
407 | GNUNET_assert (sched->ready[GNUNET_SCHEDULER_PRIORITY_KEEP] == NULL); | ||
408 | /* yes, p>0 is correct, 0 is "KEEP" which should | ||
409 | always be an empty queue (see assertion)! */ | ||
410 | for (p = GNUNET_SCHEDULER_PRIORITY_COUNT - 1; p > 0; p--) | ||
411 | { | ||
412 | pos = sched->ready[p]; | ||
413 | if (pos != NULL) | ||
414 | break; | ||
415 | } | ||
416 | GNUNET_assert (pos != NULL); /* ready_count wrong? */ | ||
417 | sched->ready[p] = pos->next; | ||
418 | sched->ready_count--; | ||
419 | sched->current_priority = p; | ||
420 | GNUNET_assert (pos->priority == p); | ||
421 | tc.sched = sched; | ||
422 | tc.reason = pos->reason; | ||
423 | tc.read_ready = &pos->read_set; | ||
424 | tc.write_ready = &pos->write_set; | ||
425 | pos->callback (pos->callback_cls, &tc); | ||
426 | GNUNET_free (pos); | ||
427 | } | ||
428 | while ((sched->pending == NULL) || (p == GNUNET_SCHEDULER_PRIORITY_URGENT)); | ||
429 | } | ||
430 | |||
431 | |||
432 | /** | ||
433 | * Have we (ever) received a SIGINT/TERM/QUIT/HUP? | ||
434 | */ | ||
435 | static volatile int sig_shutdown; | ||
436 | |||
437 | |||
438 | /** | ||
439 | * Signal handler called for signals that should cause us to shutdown. | ||
440 | */ | ||
441 | static void | ||
442 | sighandler_shutdown () | ||
443 | { | ||
444 | sig_shutdown = 1; | ||
445 | } | ||
446 | |||
447 | |||
448 | /** | ||
449 | * Initialize a scheduler using this thread. This function will | ||
450 | * return when either a shutdown was initiated (via signal) and all | ||
451 | * tasks marked to "run_on_shutdown" have been completed or when all | ||
452 | * tasks in general have been completed. | ||
453 | * | ||
454 | * @param task task to run immediately | ||
455 | * @param cls closure of task | ||
456 | */ | ||
457 | void | ||
458 | GNUNET_SCHEDULER_run (GNUNET_SCHEDULER_Task task, void *cls) | ||
459 | { | ||
460 | struct GNUNET_SCHEDULER_Handle sched; | ||
461 | fd_set rs; | ||
462 | fd_set ws; | ||
463 | int max; | ||
464 | struct timeval tv; | ||
465 | int ret; | ||
466 | struct GNUNET_SIGNAL_Context *shc_int; | ||
467 | struct GNUNET_SIGNAL_Context *shc_term; | ||
468 | struct GNUNET_SIGNAL_Context *shc_quit; | ||
469 | struct GNUNET_SIGNAL_Context *shc_hup; | ||
470 | struct Task *tpos; | ||
471 | |||
472 | sig_shutdown = 0; | ||
473 | shc_int = GNUNET_SIGNAL_handler_install (SIGINT, &sighandler_shutdown); | ||
474 | shc_term = GNUNET_SIGNAL_handler_install (SIGTERM, &sighandler_shutdown); | ||
475 | shc_quit = GNUNET_SIGNAL_handler_install (SIGQUIT, &sighandler_shutdown); | ||
476 | shc_hup = GNUNET_SIGNAL_handler_install (SIGHUP, &sighandler_shutdown); | ||
477 | memset (&sched, 0, sizeof (sched)); | ||
478 | sched.current_priority = GNUNET_SCHEDULER_PRIORITY_DEFAULT; | ||
479 | GNUNET_SCHEDULER_add_continuation (&sched, | ||
480 | GNUNET_YES, | ||
481 | task, | ||
482 | cls, GNUNET_SCHEDULER_REASON_STARTUP); | ||
483 | while ((GNUNET_NO == sched.shutdown) && | ||
484 | (!sig_shutdown) && | ||
485 | ((sched.pending != NULL) || (sched.ready_count > 0))) | ||
486 | { | ||
487 | FD_ZERO (&rs); | ||
488 | FD_ZERO (&ws); | ||
489 | max = 0; | ||
490 | tv.tv_sec = 0x7FFFFFFF; | ||
491 | tv.tv_usec = 0; | ||
492 | if (sched.ready_count > 0) | ||
493 | { | ||
494 | /* no blocking, more work already ready! */ | ||
495 | tv.tv_sec = 0; | ||
496 | tv.tv_usec = 0; | ||
497 | } | ||
498 | update_sets (&sched, &max, &rs, &ws, &tv); | ||
499 | ret = SELECT (max, &rs, &ws, NULL, &tv); | ||
500 | if (ret == -1) | ||
501 | { | ||
502 | if (errno == EINTR) | ||
503 | continue; | ||
504 | GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "select"); | ||
505 | break; | ||
506 | } | ||
507 | check_ready (&sched, &rs, &ws); | ||
508 | run_ready (&sched); | ||
509 | } | ||
510 | if (sig_shutdown) | ||
511 | sched.shutdown = GNUNET_YES; | ||
512 | GNUNET_SIGNAL_handler_uninstall (shc_int); | ||
513 | GNUNET_SIGNAL_handler_uninstall (shc_term); | ||
514 | GNUNET_SIGNAL_handler_uninstall (shc_quit); | ||
515 | GNUNET_SIGNAL_handler_uninstall (shc_hup); | ||
516 | do | ||
517 | { | ||
518 | run_ready (&sched); | ||
519 | check_ready (&sched, NULL, NULL); | ||
520 | } | ||
521 | while (sched.ready_count > 0); | ||
522 | while (NULL != (tpos = sched.pending)) | ||
523 | { | ||
524 | sched.pending = tpos->next; | ||
525 | GNUNET_free (tpos); | ||
526 | } | ||
527 | } | ||
528 | |||
529 | |||
530 | /** | ||
531 | * Request the shutdown of a scheduler. This function can be used to | ||
532 | * stop a scheduling thread when created with the | ||
533 | * "GNUNET_SCHEDULER_init_thread" function or from within the signal | ||
534 | * handler for signals causing shutdowns. | ||
535 | */ | ||
536 | void | ||
537 | GNUNET_SCHEDULER_shutdown (struct GNUNET_SCHEDULER_Handle *sched) | ||
538 | { | ||
539 | sched->shutdown = GNUNET_YES; | ||
540 | } | ||
541 | |||
542 | |||
543 | /** | ||
544 | * Get information about the current load of this scheduler. Use this | ||
545 | * function to determine if an elective task should be added or simply | ||
546 | * dropped (if the decision should be made based on the number of | ||
547 | * tasks ready to run). | ||
548 | * | ||
549 | * @param sched scheduler to query | ||
550 | * @return number of tasks pending right now | ||
551 | */ | ||
552 | unsigned int | ||
553 | GNUNET_SCHEDULER_get_load (struct GNUNET_SCHEDULER_Handle *sched, | ||
554 | enum GNUNET_SCHEDULER_Priority p) | ||
555 | { | ||
556 | struct Task *pos; | ||
557 | unsigned int ret; | ||
558 | |||
559 | if (p == GNUNET_SCHEDULER_PRIORITY_COUNT) | ||
560 | return sched->ready_count; | ||
561 | if (p == GNUNET_SCHEDULER_PRIORITY_KEEP) | ||
562 | p = sched->current_priority; | ||
563 | ret = 0; | ||
564 | pos = sched->ready[p]; | ||
565 | while (pos != NULL) | ||
566 | { | ||
567 | pos = pos->next; | ||
568 | ret++; | ||
569 | } | ||
570 | return ret; | ||
571 | } | ||
572 | |||
573 | |||
574 | /** | ||
575 | * Cancel the task with the specified identifier. | ||
576 | * The task must not yet have run. | ||
577 | * | ||
578 | * @param sched scheduler to use | ||
579 | * @param task id of the task to cancel | ||
580 | */ | ||
581 | void * | ||
582 | GNUNET_SCHEDULER_cancel (struct GNUNET_SCHEDULER_Handle *sched, | ||
583 | GNUNET_SCHEDULER_TaskIdentifier task) | ||
584 | { | ||
585 | struct Task *t; | ||
586 | struct Task *prev; | ||
587 | enum GNUNET_SCHEDULER_Priority p; | ||
588 | void *ret; | ||
589 | |||
590 | prev = NULL; | ||
591 | t = sched->pending; | ||
592 | while (t != NULL) | ||
593 | { | ||
594 | if (t->id == task) | ||
595 | break; | ||
596 | prev = t; | ||
597 | t = t->next; | ||
598 | } | ||
599 | p = 0; | ||
600 | while (t == NULL) | ||
601 | { | ||
602 | p++; | ||
603 | GNUNET_assert (p < GNUNET_SCHEDULER_PRIORITY_COUNT); | ||
604 | prev = NULL; | ||
605 | t = sched->ready[p]; | ||
606 | while (t != NULL) | ||
607 | { | ||
608 | if (t->id == task) | ||
609 | { | ||
610 | sched->ready_count--; | ||
611 | break; | ||
612 | } | ||
613 | prev = t; | ||
614 | t = t->next; | ||
615 | } | ||
616 | } | ||
617 | if (prev == NULL) | ||
618 | { | ||
619 | if (p == 0) | ||
620 | sched->pending = t->next; | ||
621 | else | ||
622 | sched->ready[p] = t->next; | ||
623 | } | ||
624 | else | ||
625 | prev->next = t->next; | ||
626 | ret = t->callback_cls; | ||
627 | GNUNET_free (t); | ||
628 | return ret; | ||
629 | } | ||
630 | |||
631 | |||
632 | /** | ||
633 | * Continue the current execution with the given function. This is | ||
634 | * similar to the other "add" functions except that there is no delay | ||
635 | * and the reason code can be specified. | ||
636 | * | ||
637 | * @param sched scheduler to use | ||
638 | * @param main main function of the task | ||
639 | * @param cls closure of task | ||
640 | * @param reason reason for task invocation | ||
641 | */ | ||
642 | void | ||
643 | GNUNET_SCHEDULER_add_continuation (struct GNUNET_SCHEDULER_Handle *sched, | ||
644 | int run_on_shutdown, | ||
645 | GNUNET_SCHEDULER_Task main, | ||
646 | void *cls, | ||
647 | enum GNUNET_SCHEDULER_Reason reason) | ||
648 | { | ||
649 | struct Task *task; | ||
650 | |||
651 | task = GNUNET_malloc (sizeof (struct Task)); | ||
652 | task->callback = main; | ||
653 | task->callback_cls = cls; | ||
654 | task->id = ++sched->last_id; | ||
655 | task->reason = reason; | ||
656 | task->priority = sched->current_priority; | ||
657 | task->run_on_shutdown = run_on_shutdown; | ||
658 | queue_ready_task (sched, task); | ||
659 | } | ||
660 | |||
661 | |||
662 | /** | ||
663 | * Schedule a new task to be run after the specified | ||
664 | * prerequisite task has completed. | ||
665 | * | ||
666 | * @param sched scheduler to use | ||
667 | * @param run_on_shutdown run on shutdown? | ||
668 | * @param prio how important is this task? | ||
669 | * @param prerequisite_task run this task after the task with the given | ||
670 | * task identifier completes (and any of our other | ||
671 | * conditions, such as delay, read or write-readyness | ||
672 | * are satisfied). Use GNUNET_SCHEDULER_NO_PREREQUISITE_TASK to not have any dependency | ||
673 | * on completion of other tasks. | ||
674 | * @param main main function of the task | ||
675 | * @param cls closure of task | ||
676 | * @return unique task identifier for the job | ||
677 | * only valid until "main" is started! | ||
678 | */ | ||
679 | GNUNET_SCHEDULER_TaskIdentifier | ||
680 | GNUNET_SCHEDULER_add_after (struct GNUNET_SCHEDULER_Handle *sched, | ||
681 | int run_on_shutdown, | ||
682 | enum GNUNET_SCHEDULER_Priority prio, | ||
683 | GNUNET_SCHEDULER_TaskIdentifier prerequisite_task, | ||
684 | GNUNET_SCHEDULER_Task main, void *cls) | ||
685 | { | ||
686 | return GNUNET_SCHEDULER_add_select (sched, run_on_shutdown, prio, | ||
687 | prerequisite_task, | ||
688 | GNUNET_TIME_UNIT_ZERO, | ||
689 | 0, NULL, NULL, main, cls); | ||
690 | } | ||
691 | |||
692 | |||
693 | /** | ||
694 | * Schedule a new task to be run with a specified delay. The task | ||
695 | * will be scheduled for execution once the delay has expired and the | ||
696 | * prerequisite task has completed. | ||
697 | * | ||
698 | * @param sched scheduler to use | ||
699 | * @param run_on_shutdown run on shutdown? You can use this | ||
700 | * argument to run a function only during shutdown | ||
701 | * by setting delay to -1. Set this | ||
702 | * argument to GNUNET_NO to skip this task if | ||
703 | * the user requested process termination. | ||
704 | * @param prio how important is this task? | ||
705 | * @param prerequisite_task run this task after the task with the given | ||
706 | * task identifier completes (and any of our other | ||
707 | * conditions, such as delay, read or write-readyness | ||
708 | * are satisfied). Use GNUNET_SCHEDULER_NO_PREREQUISITE_TASK to not have any dependency | ||
709 | * on completion of other tasks. | ||
710 | * @param delay how long should we wait? Use GNUNET_TIME_UNIT_FOREVER_REL for "forever" | ||
711 | * @param main main function of the task | ||
712 | * @param cls closure of task | ||
713 | * @return unique task identifier for the job | ||
714 | * only valid until "main" is started! | ||
715 | */ | ||
716 | GNUNET_SCHEDULER_TaskIdentifier | ||
717 | GNUNET_SCHEDULER_add_delayed (struct GNUNET_SCHEDULER_Handle * sched, | ||
718 | int run_on_shutdown, | ||
719 | enum GNUNET_SCHEDULER_Priority prio, | ||
720 | GNUNET_SCHEDULER_TaskIdentifier | ||
721 | prerequisite_task, | ||
722 | struct GNUNET_TIME_Relative delay, | ||
723 | GNUNET_SCHEDULER_Task main, void *cls) | ||
724 | { | ||
725 | return GNUNET_SCHEDULER_add_select (sched, run_on_shutdown, prio, | ||
726 | prerequisite_task, delay, | ||
727 | 0, NULL, NULL, main, cls); | ||
728 | } | ||
729 | |||
730 | |||
731 | /** | ||
732 | * Schedule a new task to be run with a specified delay or when the | ||
733 | * specified file descriptor is ready for reading. The delay can be | ||
734 | * used as a timeout on the socket being ready. The task will be | ||
735 | * scheduled for execution once either the delay has expired or the | ||
736 | * socket operation is ready. | ||
737 | * | ||
738 | * @param sched scheduler to use | ||
739 | * @param run_on_shutdown run on shutdown? Set this | ||
740 | * argument to GNUNET_NO to skip this task if | ||
741 | * the user requested process termination. | ||
742 | * @param prio how important is this task? | ||
743 | * @param prerequisite_task run this task after the task with the given | ||
744 | * task identifier completes (and any of our other | ||
745 | * conditions, such as delay, read or write-readyness | ||
746 | * are satisfied). Use GNUNET_SCHEDULER_NO_PREREQUISITE_TASK to not have any dependency | ||
747 | * on completion of other tasks. | ||
748 | * @param delay how long should we wait? Use GNUNET_TIME_UNIT_FOREVER_REL for "forever" | ||
749 | * @param rfd read file-descriptor | ||
750 | * @param main main function of the task | ||
751 | * @param cls closure of task | ||
752 | * @return unique task identifier for the job | ||
753 | * only valid until "main" is started! | ||
754 | */ | ||
755 | GNUNET_SCHEDULER_TaskIdentifier | ||
756 | GNUNET_SCHEDULER_add_read (struct GNUNET_SCHEDULER_Handle * sched, | ||
757 | int run_on_shutdown, | ||
758 | enum GNUNET_SCHEDULER_Priority prio, | ||
759 | GNUNET_SCHEDULER_TaskIdentifier prerequisite_task, | ||
760 | struct GNUNET_TIME_Relative delay, | ||
761 | int rfd, GNUNET_SCHEDULER_Task main, void *cls) | ||
762 | { | ||
763 | fd_set rs; | ||
764 | |||
765 | GNUNET_assert (rfd >= 0); | ||
766 | FD_ZERO (&rs); | ||
767 | FD_SET (rfd, &rs); | ||
768 | return GNUNET_SCHEDULER_add_select (sched, run_on_shutdown, prio, | ||
769 | prerequisite_task, delay, | ||
770 | rfd + 1, &rs, NULL, main, cls); | ||
771 | } | ||
772 | |||
773 | |||
774 | /** | ||
775 | * Schedule a new task to be run with a specified delay or when the | ||
776 | * specified file descriptor is ready for writing. The delay can be | ||
777 | * used as a timeout on the socket being ready. The task will be | ||
778 | * scheduled for execution once either the delay has expired or the | ||
779 | * socket operation is ready. | ||
780 | * | ||
781 | * @param sched scheduler to use | ||
782 | * @param run_on_shutdown run on shutdown? Set this | ||
783 | * argument to GNUNET_NO to skip this task if | ||
784 | * the user requested process termination. | ||
785 | * @param prio how important is this task? | ||
786 | * @param prerequisite_task run this task after the task with the given | ||
787 | * task identifier completes (and any of our other | ||
788 | * conditions, such as delay, read or write-readyness | ||
789 | * are satisfied). Use GNUNET_SCHEDULER_NO_PREREQUISITE_TASK to not have any dependency | ||
790 | * on completion of other tasks. | ||
791 | * @param delay how long should we wait? Use GNUNET_TIME_UNIT_FOREVER_REL for "forever" | ||
792 | * @param wfd write file-descriptor | ||
793 | * @param main main function of the task | ||
794 | * @param cls closure of task | ||
795 | * @return unique task identifier for the job | ||
796 | * only valid until "main" is started! | ||
797 | */ | ||
798 | GNUNET_SCHEDULER_TaskIdentifier | ||
799 | GNUNET_SCHEDULER_add_write (struct GNUNET_SCHEDULER_Handle * sched, | ||
800 | int run_on_shutdown, | ||
801 | enum GNUNET_SCHEDULER_Priority prio, | ||
802 | GNUNET_SCHEDULER_TaskIdentifier prerequisite_task, | ||
803 | struct GNUNET_TIME_Relative delay, | ||
804 | int wfd, GNUNET_SCHEDULER_Task main, void *cls) | ||
805 | { | ||
806 | fd_set ws; | ||
807 | |||
808 | GNUNET_assert (wfd >= 0); | ||
809 | FD_ZERO (&ws); | ||
810 | FD_SET (wfd, &ws); | ||
811 | return GNUNET_SCHEDULER_add_select (sched, run_on_shutdown, prio, | ||
812 | prerequisite_task, delay, | ||
813 | wfd + 1, NULL, &ws, main, cls); | ||
814 | } | ||
815 | |||
816 | |||
817 | /** | ||
818 | * Schedule a new task to be run with a specified delay or when any of | ||
819 | * the specified file descriptor sets is ready. The delay can be used | ||
820 | * as a timeout on the socket(s) being ready. The task will be | ||
821 | * scheduled for execution once either the delay has expired or any of | ||
822 | * the socket operations is ready. This is the most general | ||
823 | * function of the "add" family. Note that the "prerequisite_task" | ||
824 | * must be satisfied in addition to any of the other conditions. In | ||
825 | * other words, the task will be started when | ||
826 | * <code> | ||
827 | * (prerequisite-run) | ||
828 | * && (delay-ready | ||
829 | * || any-rs-ready | ||
830 | * || any-ws-ready | ||
831 | * || (shutdown-active && run-on-shutdown) ) | ||
832 | * </code> | ||
833 | * | ||
834 | * @param sched scheduler to use | ||
835 | * @param run_on_shutdown run on shutdown? Set this | ||
836 | * argument to GNUNET_NO to skip this task if | ||
837 | * the user requested process termination. | ||
838 | * @param prio how important is this task? | ||
839 | * @param prerequisite_task run this task after the task with the given | ||
840 | * task identifier completes (and any of our other | ||
841 | * conditions, such as delay, read or write-readyness | ||
842 | * are satisfied). Use GNUNET_SCHEDULER_NO_PREREQUISITE_TASK to not have any dependency | ||
843 | * on completion of other tasks. | ||
844 | * @param delay how long should we wait? Use GNUNET_TIME_UNIT_FOREVER_REL for "forever" | ||
845 | * @param nfds highest-numbered file descriptor in any of the two sets plus one | ||
846 | * @param rs set of file descriptors we want to read (can be NULL) | ||
847 | * @param ws set of file descriptors we want to write (can be NULL) | ||
848 | * @param main main function of the task | ||
849 | * @param cls closure of task | ||
850 | * @return unique task identifier for the job | ||
851 | * only valid until "main" is started! | ||
852 | */ | ||
853 | GNUNET_SCHEDULER_TaskIdentifier | ||
854 | GNUNET_SCHEDULER_add_select (struct GNUNET_SCHEDULER_Handle * sched, | ||
855 | int run_on_shutdown, | ||
856 | enum GNUNET_SCHEDULER_Priority prio, | ||
857 | GNUNET_SCHEDULER_TaskIdentifier | ||
858 | prerequisite_task, | ||
859 | struct GNUNET_TIME_Relative delay, | ||
860 | int nfds, const fd_set * rs, const fd_set * ws, | ||
861 | GNUNET_SCHEDULER_Task main, void *cls) | ||
862 | { | ||
863 | struct Task *task; | ||
864 | |||
865 | task = GNUNET_malloc (sizeof (struct Task)); | ||
866 | task->callback = main; | ||
867 | task->callback_cls = cls; | ||
868 | if ((rs != NULL) && (nfds > 0)) | ||
869 | memcpy (&task->read_set, rs, sizeof (fd_set)); | ||
870 | if ((ws != NULL) && (nfds > 0)) | ||
871 | memcpy (&task->write_set, ws, sizeof (fd_set)); | ||
872 | task->id = ++sched->last_id; | ||
873 | task->prereq_id = prerequisite_task; | ||
874 | task->timeout = GNUNET_TIME_relative_to_absolute (delay); | ||
875 | task->priority = | ||
876 | check_priority ((prio == | ||
877 | GNUNET_SCHEDULER_PRIORITY_KEEP) ? sched-> | ||
878 | current_priority : prio); | ||
879 | task->nfds = nfds; | ||
880 | task->run_on_shutdown = run_on_shutdown; | ||
881 | task->next = sched->pending; | ||
882 | sched->pending = task; | ||
883 | return task->id; | ||
884 | } | ||
885 | |||
886 | /* end of scheduler.c */ | ||