diff options
Diffstat (limited to 'src/util/scheduler.c')
-rw-r--r-- | src/util/scheduler.c | 2569 |
1 files changed, 0 insertions, 2569 deletions
diff --git a/src/util/scheduler.c b/src/util/scheduler.c deleted file mode 100644 index d5c7b964f..000000000 --- a/src/util/scheduler.c +++ /dev/null | |||
@@ -1,2569 +0,0 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet | ||
3 | Copyright (C) 2009-2017 GNUnet e.V. | ||
4 | |||
5 | GNUnet is free software: you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Affero General Public License as published | ||
7 | by the Free Software Foundation, either version 3 of the License, | ||
8 | or (at your 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 | Affero General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Affero General Public License | ||
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | SPDX-License-Identifier: AGPL3.0-or-later | ||
19 | */ | ||
20 | /** | ||
21 | * @file util/scheduler.c | ||
22 | * @brief schedule computations using continuation passing style | ||
23 | * @author Christian Grothoff | ||
24 | */ | ||
25 | #include "platform.h" | ||
26 | #include "gnunet_util_lib.h" | ||
27 | #include "disk.h" | ||
28 | // DEBUG | ||
29 | #include <inttypes.h> | ||
30 | |||
31 | #define LOG(kind, ...) GNUNET_log_from (kind, "util-scheduler", __VA_ARGS__) | ||
32 | |||
33 | #define LOG_STRERROR(kind, syscall) GNUNET_log_from_strerror (kind, \ | ||
34 | "util-scheduler", \ | ||
35 | syscall) | ||
36 | |||
37 | |||
38 | #if HAVE_EXECINFO_H | ||
39 | #include "execinfo.h" | ||
40 | |||
41 | /** | ||
42 | * Use lsof to generate file descriptor reports on select error? | ||
43 | * (turn off for stable releases). | ||
44 | */ | ||
45 | #define USE_LSOF GNUNET_NO | ||
46 | |||
47 | /** | ||
48 | * Obtain trace information for all scheduler calls that schedule tasks. | ||
49 | */ | ||
50 | #define EXECINFO GNUNET_NO | ||
51 | |||
52 | /** | ||
53 | * Check each file descriptor before adding | ||
54 | */ | ||
55 | #define DEBUG_FDS GNUNET_NO | ||
56 | |||
57 | /** | ||
58 | * Depth of the traces collected via EXECINFO. | ||
59 | */ | ||
60 | #define MAX_TRACE_DEPTH 50 | ||
61 | #endif | ||
62 | |||
63 | /** | ||
64 | * Should we figure out which tasks are delayed for a while | ||
65 | * before they are run? (Consider using in combination with EXECINFO). | ||
66 | */ | ||
67 | #define PROFILE_DELAYS GNUNET_NO | ||
68 | |||
69 | /** | ||
70 | * Task that were in the queue for longer than this are reported if | ||
71 | * PROFILE_DELAYS is active. | ||
72 | */ | ||
73 | #define DELAY_THRESHOLD GNUNET_TIME_UNIT_SECONDS | ||
74 | |||
75 | |||
76 | /** | ||
77 | * Argument to be passed from the driver to | ||
78 | * #GNUNET_SCHEDULER_do_work(). Contains the | ||
79 | * scheduler's internal state. | ||
80 | */ | ||
81 | struct GNUNET_SCHEDULER_Handle | ||
82 | { | ||
83 | /** | ||
84 | * Passed here to avoid constantly allocating/deallocating | ||
85 | * this element, but generally we want to get rid of this. | ||
86 | * @deprecated | ||
87 | */ | ||
88 | struct GNUNET_NETWORK_FDSet *rs; | ||
89 | |||
90 | /** | ||
91 | * Passed here to avoid constantly allocating/deallocating | ||
92 | * this element, but generally we want to get rid of this. | ||
93 | * @deprecated | ||
94 | */ | ||
95 | struct GNUNET_NETWORK_FDSet *ws; | ||
96 | |||
97 | /** | ||
98 | * context of the SIGINT handler | ||
99 | */ | ||
100 | struct GNUNET_SIGNAL_Context *shc_int; | ||
101 | |||
102 | /** | ||
103 | * context of the SIGTERM handler | ||
104 | */ | ||
105 | struct GNUNET_SIGNAL_Context *shc_term; | ||
106 | |||
107 | #if (SIGTERM != GNUNET_TERM_SIG) | ||
108 | /** | ||
109 | * context of the TERM_SIG handler | ||
110 | */ | ||
111 | struct GNUNET_SIGNAL_Context *shc_gterm; | ||
112 | #endif | ||
113 | |||
114 | /** | ||
115 | * context of the SIGQUIT handler | ||
116 | */ | ||
117 | struct GNUNET_SIGNAL_Context *shc_quit; | ||
118 | |||
119 | /** | ||
120 | * context of the SIGHUP handler | ||
121 | */ | ||
122 | struct GNUNET_SIGNAL_Context *shc_hup; | ||
123 | |||
124 | /** | ||
125 | * context of the SIGPIPE handler | ||
126 | */ | ||
127 | struct GNUNET_SIGNAL_Context *shc_pipe; | ||
128 | }; | ||
129 | |||
130 | |||
131 | /** | ||
132 | * Entry in list of pending tasks. | ||
133 | */ | ||
134 | struct GNUNET_SCHEDULER_Task | ||
135 | { | ||
136 | /** | ||
137 | * This is a linked list. | ||
138 | */ | ||
139 | struct GNUNET_SCHEDULER_Task *next; | ||
140 | |||
141 | /** | ||
142 | * This is a linked list. | ||
143 | */ | ||
144 | struct GNUNET_SCHEDULER_Task *prev; | ||
145 | |||
146 | /** | ||
147 | * Function to run when ready. | ||
148 | */ | ||
149 | GNUNET_SCHEDULER_TaskCallback callback; | ||
150 | |||
151 | /** | ||
152 | * Closure for the @e callback. | ||
153 | */ | ||
154 | void *callback_cls; | ||
155 | |||
156 | /** | ||
157 | * Information about which FDs are ready for this task (and why). | ||
158 | */ | ||
159 | struct GNUNET_SCHEDULER_FdInfo *fds; | ||
160 | |||
161 | /** | ||
162 | * Storage location used for @e fds if we want to avoid | ||
163 | * a separate malloc() call in the common case that this | ||
164 | * task is only about a single FD. | ||
165 | */ | ||
166 | struct GNUNET_SCHEDULER_FdInfo fdx; | ||
167 | |||
168 | /** | ||
169 | * Size of the @e fds array. | ||
170 | */ | ||
171 | unsigned int fds_len; | ||
172 | |||
173 | /** | ||
174 | * Do we own the network and file handles referenced by the FdInfo | ||
175 | * structs in the fds array. This will only be GNUNET_YES if the | ||
176 | * task was created by the #GNUNET_SCHEDULER_add_select function. | ||
177 | */ | ||
178 | int own_handles; | ||
179 | |||
180 | /** | ||
181 | * Absolute timeout value for the task, or | ||
182 | * #GNUNET_TIME_UNIT_FOREVER_ABS for "no timeout". | ||
183 | */ | ||
184 | struct GNUNET_TIME_Absolute timeout; | ||
185 | |||
186 | #if PROFILE_DELAYS | ||
187 | /** | ||
188 | * When was the task scheduled? | ||
189 | */ | ||
190 | struct GNUNET_TIME_Absolute start_time; | ||
191 | #endif | ||
192 | |||
193 | /** | ||
194 | * Why is the task ready? Set after task is added to ready queue. | ||
195 | * Initially set to zero. All reasons that have already been | ||
196 | * satisfied (e.g. read or write ready) will be set over time. | ||
197 | */ | ||
198 | enum GNUNET_SCHEDULER_Reason reason; | ||
199 | |||
200 | /** | ||
201 | * Task priority. | ||
202 | */ | ||
203 | enum GNUNET_SCHEDULER_Priority priority; | ||
204 | |||
205 | /** | ||
206 | * Set if we only wait for reading from a single FD, otherwise -1. | ||
207 | */ | ||
208 | int read_fd; | ||
209 | |||
210 | /** | ||
211 | * Set if we only wait for writing to a single FD, otherwise -1. | ||
212 | */ | ||
213 | int write_fd; | ||
214 | |||
215 | /** | ||
216 | * Should the existence of this task in the queue be counted as | ||
217 | * reason to not shutdown the scheduler? | ||
218 | */ | ||
219 | int lifeness; | ||
220 | |||
221 | /** | ||
222 | * Is this task run on shutdown? | ||
223 | */ | ||
224 | int on_shutdown; | ||
225 | |||
226 | /** | ||
227 | * Is this task in the ready list? | ||
228 | */ | ||
229 | int in_ready_list; | ||
230 | |||
231 | #if EXECINFO | ||
232 | /** | ||
233 | * Array of strings which make up a backtrace from the point when this | ||
234 | * task was scheduled (essentially, who scheduled the task?) | ||
235 | */ | ||
236 | char **backtrace_strings; | ||
237 | |||
238 | /** | ||
239 | * Size of the backtrace_strings array | ||
240 | */ | ||
241 | int num_backtrace_strings; | ||
242 | #endif | ||
243 | |||
244 | /** | ||
245 | * Asynchronous scope of the task that scheduled this scope, | ||
246 | */ | ||
247 | struct GNUNET_AsyncScopeSave scope; | ||
248 | }; | ||
249 | |||
250 | |||
251 | /** | ||
252 | * A struct representing an event the select driver is waiting for | ||
253 | */ | ||
254 | struct Scheduled | ||
255 | { | ||
256 | struct Scheduled *prev; | ||
257 | |||
258 | struct Scheduled *next; | ||
259 | |||
260 | /** | ||
261 | * the task, the event is related to | ||
262 | */ | ||
263 | struct GNUNET_SCHEDULER_Task *task; | ||
264 | |||
265 | /** | ||
266 | * information about the network socket / file descriptor where | ||
267 | * the event is expected to occur | ||
268 | */ | ||
269 | struct GNUNET_SCHEDULER_FdInfo *fdi; | ||
270 | |||
271 | /** | ||
272 | * the event types (multiple event types can be ORed) the select | ||
273 | * driver is expected to wait for | ||
274 | */ | ||
275 | enum GNUNET_SCHEDULER_EventType et; | ||
276 | }; | ||
277 | |||
278 | |||
279 | /** | ||
280 | * Driver context used by GNUNET_SCHEDULER_run | ||
281 | */ | ||
282 | struct DriverContext | ||
283 | { | ||
284 | /** | ||
285 | * the head of a DLL containing information about the events the | ||
286 | * select driver is waiting for | ||
287 | */ | ||
288 | struct Scheduled *scheduled_head; | ||
289 | |||
290 | /** | ||
291 | * the tail of a DLL containing information about the events the | ||
292 | * select driver is waiting for | ||
293 | */ | ||
294 | struct Scheduled *scheduled_tail; | ||
295 | |||
296 | /** | ||
297 | * the time when the select driver will wake up again (after | ||
298 | * calling select) | ||
299 | */ | ||
300 | struct GNUNET_TIME_Absolute timeout; | ||
301 | }; | ||
302 | |||
303 | |||
304 | /** | ||
305 | * The driver used for the event loop. Will be handed over to | ||
306 | * the scheduler in #GNUNET_SCHEDULER_do_work(), persisted | ||
307 | * there in this variable for later use in functions like | ||
308 | * #GNUNET_SCHEDULER_add_select(), #add_without_sets() and | ||
309 | * #GNUNET_SCHEDULER_cancel(). | ||
310 | */ | ||
311 | static const struct GNUNET_SCHEDULER_Driver *scheduler_driver; | ||
312 | |||
313 | /** | ||
314 | * Head of list of tasks waiting for an event. | ||
315 | */ | ||
316 | static struct GNUNET_SCHEDULER_Task *pending_head; | ||
317 | |||
318 | /** | ||
319 | * Tail of list of tasks waiting for an event. | ||
320 | */ | ||
321 | static struct GNUNET_SCHEDULER_Task *pending_tail; | ||
322 | |||
323 | /** | ||
324 | * Head of list of tasks waiting for shutdown. | ||
325 | */ | ||
326 | static struct GNUNET_SCHEDULER_Task *shutdown_head; | ||
327 | |||
328 | /** | ||
329 | * Tail of list of tasks waiting for shutdown. | ||
330 | */ | ||
331 | static struct GNUNET_SCHEDULER_Task *shutdown_tail; | ||
332 | |||
333 | /** | ||
334 | * List of tasks waiting ONLY for a timeout event. | ||
335 | * Sorted by timeout (earliest first). Used so that | ||
336 | * we do not traverse the list of these tasks when | ||
337 | * building select sets (we just look at the head | ||
338 | * to determine the respective timeout ONCE). | ||
339 | */ | ||
340 | static struct GNUNET_SCHEDULER_Task *pending_timeout_head; | ||
341 | |||
342 | /** | ||
343 | * List of tasks waiting ONLY for a timeout event. | ||
344 | * Sorted by timeout (earliest first). Used so that | ||
345 | * we do not traverse the list of these tasks when | ||
346 | * building select sets (we just look at the head | ||
347 | * to determine the respective timeout ONCE). | ||
348 | */ | ||
349 | static struct GNUNET_SCHEDULER_Task *pending_timeout_tail; | ||
350 | |||
351 | /** | ||
352 | * Last inserted task waiting ONLY for a timeout event. | ||
353 | * Used to (heuristically) speed up insertion. | ||
354 | */ | ||
355 | static struct GNUNET_SCHEDULER_Task *pending_timeout_last; | ||
356 | |||
357 | /** | ||
358 | * ID of the task that is running right now. | ||
359 | */ | ||
360 | static struct GNUNET_SCHEDULER_Task *active_task; | ||
361 | |||
362 | /** | ||
363 | * Head of list of tasks ready to run right now, grouped by importance. | ||
364 | */ | ||
365 | static struct | ||
366 | GNUNET_SCHEDULER_Task *ready_head[GNUNET_SCHEDULER_PRIORITY_COUNT]; | ||
367 | |||
368 | /** | ||
369 | * Tail of list of tasks ready to run right now, grouped by importance. | ||
370 | */ | ||
371 | static struct | ||
372 | GNUNET_SCHEDULER_Task *ready_tail[GNUNET_SCHEDULER_PRIORITY_COUNT]; | ||
373 | |||
374 | /** | ||
375 | * Task for installing parent control handlers (it might happen that the | ||
376 | * scheduler is shutdown before this task is executed, so | ||
377 | * GNUNET_SCHEDULER_shutdown must cancel it in that case) | ||
378 | */ | ||
379 | static struct GNUNET_SCHEDULER_Task *install_parent_control_task; | ||
380 | |||
381 | /** | ||
382 | * Task for reading from a pipe that signal handlers will use to initiate | ||
383 | * shutdown | ||
384 | */ | ||
385 | static struct GNUNET_SCHEDULER_Task *shutdown_pipe_task; | ||
386 | |||
387 | /** | ||
388 | * Number of tasks on the ready list. | ||
389 | */ | ||
390 | static unsigned int ready_count; | ||
391 | |||
392 | /** | ||
393 | * Priority of the task running right now. Only | ||
394 | * valid while a task is running. | ||
395 | */ | ||
396 | static enum GNUNET_SCHEDULER_Priority current_priority; | ||
397 | |||
398 | /** | ||
399 | * Priority of the highest task added in the current select | ||
400 | * iteration. | ||
401 | */ | ||
402 | static enum GNUNET_SCHEDULER_Priority max_priority_added; | ||
403 | |||
404 | /** | ||
405 | * Value of the 'lifeness' flag for the current task. | ||
406 | */ | ||
407 | static int current_lifeness; | ||
408 | |||
409 | /** | ||
410 | * Priority used currently in #GNUNET_SCHEDULER_do_work(). | ||
411 | */ | ||
412 | static enum GNUNET_SCHEDULER_Priority work_priority; | ||
413 | |||
414 | /** | ||
415 | * Function to use as a select() in the scheduler. | ||
416 | * If NULL, we use GNUNET_NETWORK_socket_select(). | ||
417 | */ | ||
418 | static GNUNET_SCHEDULER_select scheduler_select; | ||
419 | |||
420 | /** | ||
421 | * Task context of the current task. | ||
422 | */ | ||
423 | static struct GNUNET_SCHEDULER_TaskContext tc; | ||
424 | |||
425 | /** | ||
426 | * Closure for #scheduler_select. | ||
427 | */ | ||
428 | static void *scheduler_select_cls; | ||
429 | |||
430 | |||
431 | /** | ||
432 | * Sets the select function to use in the scheduler (scheduler_select). | ||
433 | * | ||
434 | * @param new_select new select function to use | ||
435 | * @param new_select_cls closure for @a new_select | ||
436 | * @return previously used select function, NULL for default | ||
437 | */ | ||
438 | void | ||
439 | GNUNET_SCHEDULER_set_select (GNUNET_SCHEDULER_select new_select, | ||
440 | void *new_select_cls) | ||
441 | { | ||
442 | scheduler_select = new_select; | ||
443 | scheduler_select_cls = new_select_cls; | ||
444 | } | ||
445 | |||
446 | |||
447 | /** | ||
448 | * Check that the given priority is legal (and return it). | ||
449 | * | ||
450 | * @param p priority value to check | ||
451 | * @return p on success, 0 on error | ||
452 | */ | ||
453 | static enum GNUNET_SCHEDULER_Priority | ||
454 | check_priority (enum GNUNET_SCHEDULER_Priority p) | ||
455 | { | ||
456 | if ((p >= 0) && (p < GNUNET_SCHEDULER_PRIORITY_COUNT)) | ||
457 | return p; | ||
458 | GNUNET_assert (0); | ||
459 | return 0; /* make compiler happy */ | ||
460 | } | ||
461 | |||
462 | |||
463 | /** | ||
464 | * chooses the nearest timeout from all pending tasks, to be used | ||
465 | * to tell the driver the next wakeup time (using its set_wakeup | ||
466 | * callback) | ||
467 | */ | ||
468 | struct GNUNET_TIME_Absolute | ||
469 | get_timeout () | ||
470 | { | ||
471 | struct GNUNET_SCHEDULER_Task *pos; | ||
472 | struct GNUNET_TIME_Absolute now; | ||
473 | struct GNUNET_TIME_Absolute timeout; | ||
474 | |||
475 | pos = pending_timeout_head; | ||
476 | now = GNUNET_TIME_absolute_get (); | ||
477 | timeout = GNUNET_TIME_UNIT_FOREVER_ABS; | ||
478 | if (NULL != pos) | ||
479 | { | ||
480 | if (0 != pos->reason) | ||
481 | { | ||
482 | return now; | ||
483 | } | ||
484 | else | ||
485 | { | ||
486 | timeout = pos->timeout; | ||
487 | } | ||
488 | } | ||
489 | for (pos = pending_head; NULL != pos; pos = pos->next) | ||
490 | { | ||
491 | if (0 != pos->reason) | ||
492 | { | ||
493 | return now; | ||
494 | } | ||
495 | else if ((pos->timeout.abs_value_us != | ||
496 | GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us) && | ||
497 | (timeout.abs_value_us > pos->timeout.abs_value_us)) | ||
498 | { | ||
499 | timeout = pos->timeout; | ||
500 | } | ||
501 | } | ||
502 | return timeout; | ||
503 | } | ||
504 | |||
505 | |||
506 | /** | ||
507 | * Put a task that is ready for execution into the ready queue. | ||
508 | * | ||
509 | * @param task task ready for execution | ||
510 | */ | ||
511 | static void | ||
512 | queue_ready_task (struct GNUNET_SCHEDULER_Task *task) | ||
513 | { | ||
514 | enum GNUNET_SCHEDULER_Priority p = check_priority (task->priority); | ||
515 | |||
516 | GNUNET_CONTAINER_DLL_insert_tail (ready_head[p], | ||
517 | ready_tail[p], | ||
518 | task); | ||
519 | task->in_ready_list = GNUNET_YES; | ||
520 | ready_count++; | ||
521 | } | ||
522 | |||
523 | |||
524 | /** | ||
525 | * Request the shutdown of a scheduler. Marks all tasks | ||
526 | * awaiting shutdown as ready. Note that tasks | ||
527 | * scheduled with #GNUNET_SCHEDULER_add_shutdown() AFTER this call | ||
528 | * will be delayed until the next shutdown signal. | ||
529 | */ | ||
530 | void | ||
531 | GNUNET_SCHEDULER_shutdown () | ||
532 | { | ||
533 | struct GNUNET_SCHEDULER_Task *pos; | ||
534 | |||
535 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
536 | "GNUNET_SCHEDULER_shutdown\n"); | ||
537 | if (NULL != install_parent_control_task) | ||
538 | { | ||
539 | GNUNET_SCHEDULER_cancel (install_parent_control_task); | ||
540 | install_parent_control_task = NULL; | ||
541 | } | ||
542 | if (NULL != shutdown_pipe_task) | ||
543 | { | ||
544 | GNUNET_SCHEDULER_cancel (shutdown_pipe_task); | ||
545 | shutdown_pipe_task = NULL; | ||
546 | } | ||
547 | while (NULL != (pos = shutdown_head)) | ||
548 | { | ||
549 | GNUNET_CONTAINER_DLL_remove (shutdown_head, | ||
550 | shutdown_tail, | ||
551 | pos); | ||
552 | pos->reason |= GNUNET_SCHEDULER_REASON_SHUTDOWN; | ||
553 | queue_ready_task (pos); | ||
554 | } | ||
555 | } | ||
556 | |||
557 | |||
558 | /** | ||
559 | * Output stack trace of task @a t. | ||
560 | * | ||
561 | * @param t task to dump stack trace of | ||
562 | */ | ||
563 | static void | ||
564 | dump_backtrace (struct GNUNET_SCHEDULER_Task *t) | ||
565 | { | ||
566 | #if EXECINFO | ||
567 | for (unsigned int i = 0; i < t->num_backtrace_strings; i++) | ||
568 | LOG (GNUNET_ERROR_TYPE_WARNING, | ||
569 | "Task %p trace %u: %s\n", | ||
570 | t, | ||
571 | i, | ||
572 | t->backtrace_strings[i]); | ||
573 | #else | ||
574 | (void) t; | ||
575 | #endif | ||
576 | } | ||
577 | |||
578 | |||
579 | /** | ||
580 | * Destroy a task (release associated resources) | ||
581 | * | ||
582 | * @param t task to destroy | ||
583 | */ | ||
584 | static void | ||
585 | destroy_task (struct GNUNET_SCHEDULER_Task *t) | ||
586 | { | ||
587 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
588 | "destroying task %p\n", | ||
589 | t); | ||
590 | |||
591 | if (GNUNET_YES == t->own_handles) | ||
592 | { | ||
593 | for (unsigned int i = 0; i != t->fds_len; ++i) | ||
594 | { | ||
595 | const struct GNUNET_NETWORK_Handle *fd = t->fds[i].fd; | ||
596 | const struct GNUNET_DISK_FileHandle *fh = t->fds[i].fh; | ||
597 | if (fd) | ||
598 | { | ||
599 | GNUNET_NETWORK_socket_free_memory_only_ ( | ||
600 | (struct GNUNET_NETWORK_Handle *) fd); | ||
601 | } | ||
602 | if (fh) | ||
603 | { | ||
604 | // FIXME: on WIN32 this is not enough! A function | ||
605 | // GNUNET_DISK_file_free_memory_only would be nice | ||
606 | GNUNET_free_nz ((void *) fh); | ||
607 | } | ||
608 | } | ||
609 | } | ||
610 | if (t->fds_len > 1) | ||
611 | { | ||
612 | GNUNET_array_grow (t->fds, t->fds_len, 0); | ||
613 | } | ||
614 | #if EXECINFO | ||
615 | GNUNET_free (t->backtrace_strings); | ||
616 | #endif | ||
617 | GNUNET_free (t); | ||
618 | } | ||
619 | |||
620 | |||
621 | /** | ||
622 | * Pipe used to communicate shutdown via signal. | ||
623 | */ | ||
624 | static struct GNUNET_DISK_PipeHandle *shutdown_pipe_handle; | ||
625 | |||
626 | /** | ||
627 | * Process ID of this process at the time we installed the various | ||
628 | * signal handlers. | ||
629 | */ | ||
630 | static pid_t my_pid; | ||
631 | |||
632 | /** | ||
633 | * Signal handler called for SIGPIPE. | ||
634 | */ | ||
635 | static void | ||
636 | sighandler_pipe () | ||
637 | { | ||
638 | return; | ||
639 | } | ||
640 | |||
641 | |||
642 | ///** | ||
643 | // * Wait for a short time. | ||
644 | // * Sleeps for @a ms ms (as that should be long enough for virtually all | ||
645 | // * modern systems to context switch and allow another process to do | ||
646 | // * some 'real' work). | ||
647 | // * | ||
648 | // * @param ms how many ms to wait | ||
649 | // */ | ||
650 | // static void | ||
651 | // short_wait (unsigned int ms) | ||
652 | // { | ||
653 | // struct GNUNET_TIME_Relative timeout; | ||
654 | // | ||
655 | // timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, ms); | ||
656 | // (void) GNUNET_NETWORK_socket_select (NULL, NULL, NULL, timeout); | ||
657 | // } | ||
658 | |||
659 | |||
660 | /** | ||
661 | * Signal handler called for signals that should cause us to shutdown. | ||
662 | */ | ||
663 | static void | ||
664 | sighandler_shutdown () | ||
665 | { | ||
666 | static char c; | ||
667 | int old_errno = errno; /* backup errno */ | ||
668 | |||
669 | if (getpid () != my_pid) | ||
670 | _exit (1); /* we have fork'ed since the signal handler was created, | ||
671 | * ignore the signal, see https://gnunet.org/vfork discussion */ | ||
672 | GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle | ||
673 | (shutdown_pipe_handle, GNUNET_DISK_PIPE_END_WRITE), | ||
674 | &c, sizeof(c)); | ||
675 | errno = old_errno; | ||
676 | } | ||
677 | |||
678 | |||
679 | static void | ||
680 | shutdown_if_no_lifeness () | ||
681 | { | ||
682 | struct GNUNET_SCHEDULER_Task *t; | ||
683 | |||
684 | if (ready_count > 0) | ||
685 | return; | ||
686 | for (t = pending_head; NULL != t; t = t->next) | ||
687 | if (GNUNET_YES == t->lifeness) | ||
688 | return; | ||
689 | for (t = shutdown_head; NULL != t; t = t->next) | ||
690 | if (GNUNET_YES == t->lifeness) | ||
691 | return; | ||
692 | for (t = pending_timeout_head; NULL != t; t = t->next) | ||
693 | if (GNUNET_YES == t->lifeness) | ||
694 | return; | ||
695 | /* No lifeness! */ | ||
696 | GNUNET_SCHEDULER_shutdown (); | ||
697 | } | ||
698 | |||
699 | |||
700 | static int | ||
701 | select_loop (struct GNUNET_SCHEDULER_Handle *sh, | ||
702 | struct DriverContext *context); | ||
703 | |||
704 | |||
705 | /** | ||
706 | * Initialize and run scheduler. This function will return when all | ||
707 | * tasks have completed. On systems with signals, receiving a SIGTERM | ||
708 | * (and other similar signals) will cause #GNUNET_SCHEDULER_shutdown() | ||
709 | * to be run after the active task is complete. As a result, SIGTERM | ||
710 | * causes all active tasks to be scheduled with reason | ||
711 | * #GNUNET_SCHEDULER_REASON_SHUTDOWN. (However, tasks added | ||
712 | * afterwards will execute normally!). Note that any particular signal | ||
713 | * will only shut down one scheduler; applications should always only | ||
714 | * create a single scheduler. | ||
715 | * | ||
716 | * @param task task to run immediately | ||
717 | * @param task_cls closure of @a task | ||
718 | */ | ||
719 | void | ||
720 | GNUNET_SCHEDULER_run (GNUNET_SCHEDULER_TaskCallback task, | ||
721 | void *task_cls) | ||
722 | { | ||
723 | struct GNUNET_SCHEDULER_Handle *sh; | ||
724 | struct GNUNET_SCHEDULER_Driver *driver; | ||
725 | struct DriverContext context = { | ||
726 | .scheduled_head = NULL, | ||
727 | .scheduled_tail = NULL, | ||
728 | .timeout = GNUNET_TIME_absolute_get () | ||
729 | }; | ||
730 | |||
731 | driver = GNUNET_SCHEDULER_driver_select (); | ||
732 | driver->cls = &context; | ||
733 | sh = GNUNET_SCHEDULER_driver_init (driver); | ||
734 | GNUNET_SCHEDULER_add_with_reason_and_priority (task, | ||
735 | task_cls, | ||
736 | GNUNET_SCHEDULER_REASON_STARTUP, | ||
737 | GNUNET_SCHEDULER_PRIORITY_DEFAULT); | ||
738 | select_loop (sh, | ||
739 | &context); | ||
740 | GNUNET_SCHEDULER_driver_done (sh); | ||
741 | GNUNET_free (driver); | ||
742 | } | ||
743 | |||
744 | |||
745 | /** | ||
746 | * Obtain the task context, giving the reason why the current task was | ||
747 | * started. | ||
748 | * | ||
749 | * @return current tasks' scheduler context | ||
750 | */ | ||
751 | const struct GNUNET_SCHEDULER_TaskContext * | ||
752 | GNUNET_SCHEDULER_get_task_context () | ||
753 | { | ||
754 | GNUNET_assert (NULL != active_task); | ||
755 | return &tc; | ||
756 | } | ||
757 | |||
758 | |||
759 | /** | ||
760 | * Get information about the current load of this scheduler. Use this | ||
761 | * function to determine if an elective task should be added or simply | ||
762 | * dropped (if the decision should be made based on the number of | ||
763 | * tasks ready to run). | ||
764 | * | ||
765 | * @param p priority level to look at | ||
766 | * @return number of tasks pending right now | ||
767 | */ | ||
768 | unsigned int | ||
769 | GNUNET_SCHEDULER_get_load (enum GNUNET_SCHEDULER_Priority p) | ||
770 | { | ||
771 | unsigned int ret; | ||
772 | |||
773 | GNUNET_assert (NULL != active_task); | ||
774 | if (p == GNUNET_SCHEDULER_PRIORITY_COUNT) | ||
775 | return ready_count; | ||
776 | if (p == GNUNET_SCHEDULER_PRIORITY_KEEP) | ||
777 | p = current_priority; | ||
778 | ret = 0; | ||
779 | for (struct GNUNET_SCHEDULER_Task *pos = ready_head[check_priority (p)]; | ||
780 | NULL != pos; | ||
781 | pos = pos->next) | ||
782 | ret++; | ||
783 | return ret; | ||
784 | } | ||
785 | |||
786 | |||
787 | void | ||
788 | init_fd_info (struct GNUNET_SCHEDULER_Task *t, | ||
789 | const struct GNUNET_NETWORK_Handle *const *read_nh, | ||
790 | unsigned int read_nh_len, | ||
791 | const struct GNUNET_NETWORK_Handle *const *write_nh, | ||
792 | unsigned int write_nh_len, | ||
793 | const struct GNUNET_DISK_FileHandle *const *read_fh, | ||
794 | unsigned int read_fh_len, | ||
795 | const struct GNUNET_DISK_FileHandle *const *write_fh, | ||
796 | unsigned int write_fh_len) | ||
797 | { | ||
798 | // FIXME: if we have exactly two network handles / exactly two file handles | ||
799 | // and they are equal, we can make one FdInfo with both | ||
800 | // GNUNET_SCHEDULER_ET_IN and GNUNET_SCHEDULER_ET_OUT set. | ||
801 | struct GNUNET_SCHEDULER_FdInfo *fdi; | ||
802 | |||
803 | t->fds_len = read_nh_len + write_nh_len + read_fh_len + write_fh_len; | ||
804 | if (1 == t->fds_len) | ||
805 | { | ||
806 | fdi = &t->fdx; | ||
807 | t->fds = fdi; | ||
808 | if (1 == read_nh_len) | ||
809 | { | ||
810 | GNUNET_assert (NULL != read_nh); | ||
811 | GNUNET_assert (NULL != *read_nh); | ||
812 | fdi->fd = *read_nh; | ||
813 | fdi->et = GNUNET_SCHEDULER_ET_IN; | ||
814 | fdi->sock = GNUNET_NETWORK_get_fd (*read_nh); | ||
815 | t->read_fd = fdi->sock; | ||
816 | t->write_fd = -1; | ||
817 | } | ||
818 | else if (1 == write_nh_len) | ||
819 | { | ||
820 | GNUNET_assert (NULL != write_nh); | ||
821 | GNUNET_assert (NULL != *write_nh); | ||
822 | fdi->fd = *write_nh; | ||
823 | fdi->et = GNUNET_SCHEDULER_ET_OUT; | ||
824 | fdi->sock = GNUNET_NETWORK_get_fd (*write_nh); | ||
825 | t->read_fd = -1; | ||
826 | t->write_fd = fdi->sock; | ||
827 | } | ||
828 | else if (1 == read_fh_len) | ||
829 | { | ||
830 | GNUNET_assert (NULL != read_fh); | ||
831 | GNUNET_assert (NULL != *read_fh); | ||
832 | fdi->fh = *read_fh; | ||
833 | fdi->et = GNUNET_SCHEDULER_ET_IN; | ||
834 | fdi->sock = (*read_fh)->fd; // FIXME: does not work under WIN32 | ||
835 | t->read_fd = fdi->sock; | ||
836 | t->write_fd = -1; | ||
837 | } | ||
838 | else | ||
839 | { | ||
840 | GNUNET_assert (NULL != write_fh); | ||
841 | GNUNET_assert (NULL != *write_fh); | ||
842 | fdi->fh = *write_fh; | ||
843 | fdi->et = GNUNET_SCHEDULER_ET_OUT; | ||
844 | fdi->sock = (*write_fh)->fd; // FIXME: does not work under WIN32 | ||
845 | t->read_fd = -1; | ||
846 | t->write_fd = fdi->sock; | ||
847 | } | ||
848 | } | ||
849 | else | ||
850 | { | ||
851 | fdi = GNUNET_new_array (t->fds_len, struct GNUNET_SCHEDULER_FdInfo); | ||
852 | t->fds = fdi; | ||
853 | t->read_fd = -1; | ||
854 | t->write_fd = -1; | ||
855 | unsigned int i; | ||
856 | for (i = 0; i != read_nh_len; ++i) | ||
857 | { | ||
858 | fdi->fd = read_nh[i]; | ||
859 | GNUNET_assert (NULL != fdi->fd); | ||
860 | fdi->et = GNUNET_SCHEDULER_ET_IN; | ||
861 | fdi->sock = GNUNET_NETWORK_get_fd (read_nh[i]); | ||
862 | ++fdi; | ||
863 | } | ||
864 | for (i = 0; i != write_nh_len; ++i) | ||
865 | { | ||
866 | fdi->fd = write_nh[i]; | ||
867 | GNUNET_assert (NULL != fdi->fd); | ||
868 | fdi->et = GNUNET_SCHEDULER_ET_OUT; | ||
869 | fdi->sock = GNUNET_NETWORK_get_fd (write_nh[i]); | ||
870 | ++fdi; | ||
871 | } | ||
872 | for (i = 0; i != read_fh_len; ++i) | ||
873 | { | ||
874 | fdi->fh = read_fh[i]; | ||
875 | GNUNET_assert (NULL != fdi->fh); | ||
876 | fdi->et = GNUNET_SCHEDULER_ET_IN; | ||
877 | fdi->sock = (read_fh[i])->fd; // FIXME: does not work under WIN32 | ||
878 | ++fdi; | ||
879 | } | ||
880 | for (i = 0; i != write_fh_len; ++i) | ||
881 | { | ||
882 | fdi->fh = write_fh[i]; | ||
883 | GNUNET_assert (NULL != fdi->fh); | ||
884 | fdi->et = GNUNET_SCHEDULER_ET_OUT; | ||
885 | fdi->sock = (write_fh[i])->fd; // FIXME: does not work under WIN32 | ||
886 | ++fdi; | ||
887 | } | ||
888 | } | ||
889 | } | ||
890 | |||
891 | |||
892 | /** | ||
893 | * calls the given function @a func on each FdInfo related to @a t. | ||
894 | * Optionally updates the event type field in each FdInfo after calling | ||
895 | * @a func. | ||
896 | * | ||
897 | * @param t the task | ||
898 | * @param driver_func the function to call with each FdInfo contained in | ||
899 | * in @a t | ||
900 | * @param if_not_ready only call @a driver_func on FdInfos that are not | ||
901 | * ready | ||
902 | * @param et the event type to be set in each FdInfo after calling | ||
903 | * @a driver_func on it, or -1 if no updating not desired. | ||
904 | */ | ||
905 | static void | ||
906 | driver_add_multiple (struct GNUNET_SCHEDULER_Task *t) | ||
907 | { | ||
908 | struct GNUNET_SCHEDULER_FdInfo *fdi; | ||
909 | int success = GNUNET_YES; | ||
910 | |||
911 | for (unsigned int i = 0; i != t->fds_len; ++i) | ||
912 | { | ||
913 | fdi = &t->fds[i]; | ||
914 | success = scheduler_driver->add (scheduler_driver->cls, | ||
915 | t, | ||
916 | fdi) && success; | ||
917 | fdi->et = GNUNET_SCHEDULER_ET_NONE; | ||
918 | } | ||
919 | if (GNUNET_YES != success) | ||
920 | { | ||
921 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
922 | "driver could not add task\n"); | ||
923 | } | ||
924 | } | ||
925 | |||
926 | |||
927 | static void | ||
928 | install_parent_control_handler (void *cls) | ||
929 | { | ||
930 | (void) cls; | ||
931 | install_parent_control_task = NULL; | ||
932 | GNUNET_OS_install_parent_control_handler (NULL); | ||
933 | } | ||
934 | |||
935 | |||
936 | static void | ||
937 | shutdown_pipe_cb (void *cls) | ||
938 | { | ||
939 | char c; | ||
940 | const struct GNUNET_DISK_FileHandle *pr; | ||
941 | |||
942 | (void) cls; | ||
943 | shutdown_pipe_task = NULL; | ||
944 | pr = GNUNET_DISK_pipe_handle (shutdown_pipe_handle, | ||
945 | GNUNET_DISK_PIPE_END_READ); | ||
946 | GNUNET_assert (! GNUNET_DISK_handle_invalid (pr)); | ||
947 | /* consume the signal */ | ||
948 | GNUNET_DISK_file_read (pr, &c, sizeof(c)); | ||
949 | /* mark all active tasks as ready due to shutdown */ | ||
950 | GNUNET_SCHEDULER_shutdown (); | ||
951 | shutdown_pipe_task = | ||
952 | GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, | ||
953 | pr, | ||
954 | &shutdown_pipe_cb, | ||
955 | NULL); | ||
956 | } | ||
957 | |||
958 | |||
959 | /** | ||
960 | * Cancel the task with the specified identifier. | ||
961 | * The task must not yet have run. Only allowed to be called as long as the | ||
962 | * scheduler is running, that is one of the following conditions is met: | ||
963 | * | ||
964 | * - #GNUNET_SCHEDULER_run has been called and has not returned yet | ||
965 | * - #GNUNET_SCHEDULER_driver_init has been run and | ||
966 | * #GNUNET_SCHEDULER_driver_done has not been called yet | ||
967 | * | ||
968 | * @param task id of the task to cancel | ||
969 | * @return original closure of the task | ||
970 | */ | ||
971 | void * | ||
972 | GNUNET_SCHEDULER_cancel (struct GNUNET_SCHEDULER_Task *task) | ||
973 | { | ||
974 | enum GNUNET_SCHEDULER_Priority p; | ||
975 | int is_fd_task; | ||
976 | void *ret; | ||
977 | |||
978 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
979 | "canceling task %p\n", | ||
980 | task); | ||
981 | |||
982 | /* scheduler must be running */ | ||
983 | GNUNET_assert (NULL != scheduler_driver); | ||
984 | is_fd_task = (NULL != task->fds); | ||
985 | if (is_fd_task) | ||
986 | { | ||
987 | int del_result = scheduler_driver->del (scheduler_driver->cls, task); | ||
988 | if (GNUNET_OK != del_result) | ||
989 | { | ||
990 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
991 | "driver could not delete task\n"); | ||
992 | GNUNET_assert (0); | ||
993 | } | ||
994 | } | ||
995 | if (! task->in_ready_list) | ||
996 | { | ||
997 | if (is_fd_task) | ||
998 | { | ||
999 | GNUNET_CONTAINER_DLL_remove (pending_head, | ||
1000 | pending_tail, | ||
1001 | task); | ||
1002 | } | ||
1003 | else if (GNUNET_YES == task->on_shutdown) | ||
1004 | { | ||
1005 | GNUNET_CONTAINER_DLL_remove (shutdown_head, | ||
1006 | shutdown_tail, | ||
1007 | task); | ||
1008 | } | ||
1009 | else | ||
1010 | { | ||
1011 | GNUNET_CONTAINER_DLL_remove (pending_timeout_head, | ||
1012 | pending_timeout_tail, | ||
1013 | task); | ||
1014 | if (pending_timeout_last == task) | ||
1015 | pending_timeout_last = NULL; | ||
1016 | } | ||
1017 | } | ||
1018 | else | ||
1019 | { | ||
1020 | p = check_priority (task->priority); | ||
1021 | GNUNET_CONTAINER_DLL_remove (ready_head[p], | ||
1022 | ready_tail[p], | ||
1023 | task); | ||
1024 | ready_count--; | ||
1025 | } | ||
1026 | ret = task->callback_cls; | ||
1027 | destroy_task (task); | ||
1028 | return ret; | ||
1029 | } | ||
1030 | |||
1031 | |||
1032 | /** | ||
1033 | * Initialize backtrace data for task @a t | ||
1034 | * | ||
1035 | * @param t task to initialize | ||
1036 | */ | ||
1037 | static void | ||
1038 | init_backtrace (struct GNUNET_SCHEDULER_Task *t) | ||
1039 | { | ||
1040 | #if EXECINFO | ||
1041 | void *backtrace_array[MAX_TRACE_DEPTH]; | ||
1042 | |||
1043 | t->num_backtrace_strings | ||
1044 | = backtrace (backtrace_array, MAX_TRACE_DEPTH); | ||
1045 | t->backtrace_strings = | ||
1046 | backtrace_symbols (backtrace_array, | ||
1047 | t->num_backtrace_strings); | ||
1048 | dump_backtrace (t); | ||
1049 | #else | ||
1050 | (void) t; | ||
1051 | #endif | ||
1052 | } | ||
1053 | |||
1054 | |||
1055 | /** | ||
1056 | * Continue the current execution with the given function. This is | ||
1057 | * similar to the other "add" functions except that there is no delay | ||
1058 | * and the reason code can be specified. | ||
1059 | * | ||
1060 | * @param task main function of the task | ||
1061 | * @param task_cls closure for @a task | ||
1062 | * @param reason reason for task invocation | ||
1063 | * @param priority priority to use for the task | ||
1064 | */ | ||
1065 | void | ||
1066 | GNUNET_SCHEDULER_add_with_reason_and_priority (GNUNET_SCHEDULER_TaskCallback | ||
1067 | task, | ||
1068 | void *task_cls, | ||
1069 | enum GNUNET_SCHEDULER_Reason | ||
1070 | reason, | ||
1071 | enum GNUNET_SCHEDULER_Priority | ||
1072 | priority) | ||
1073 | { | ||
1074 | struct GNUNET_SCHEDULER_Task *t; | ||
1075 | |||
1076 | /* scheduler must be running */ | ||
1077 | GNUNET_assert (NULL != scheduler_driver); | ||
1078 | GNUNET_assert (NULL != task); | ||
1079 | t = GNUNET_new (struct GNUNET_SCHEDULER_Task); | ||
1080 | t->read_fd = -1; | ||
1081 | t->write_fd = -1; | ||
1082 | t->callback = task; | ||
1083 | t->callback_cls = task_cls; | ||
1084 | #if PROFILE_DELAYS | ||
1085 | t->start_time = GNUNET_TIME_absolute_get (); | ||
1086 | #endif | ||
1087 | t->reason = reason; | ||
1088 | t->priority = check_priority (priority); | ||
1089 | t->lifeness = current_lifeness; | ||
1090 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
1091 | "Adding continuation task %p\n", | ||
1092 | t); | ||
1093 | init_backtrace (t); | ||
1094 | queue_ready_task (t); | ||
1095 | } | ||
1096 | |||
1097 | |||
1098 | /** | ||
1099 | * Schedule a new task to be run at the specified time. The task | ||
1100 | * will be scheduled for execution at time @a at. | ||
1101 | * | ||
1102 | * @param at time when the operation should run | ||
1103 | * @param priority priority to use for the task | ||
1104 | * @param task main function of the task | ||
1105 | * @param task_cls closure of @a task | ||
1106 | * @return unique task identifier for the job | ||
1107 | * only valid until @a task is started! | ||
1108 | */ | ||
1109 | struct GNUNET_SCHEDULER_Task * | ||
1110 | GNUNET_SCHEDULER_add_at_with_priority (struct GNUNET_TIME_Absolute at, | ||
1111 | enum GNUNET_SCHEDULER_Priority priority, | ||
1112 | GNUNET_SCHEDULER_TaskCallback task, | ||
1113 | void *task_cls) | ||
1114 | { | ||
1115 | struct GNUNET_SCHEDULER_Task *t; | ||
1116 | struct GNUNET_SCHEDULER_Task *pos; | ||
1117 | struct GNUNET_SCHEDULER_Task *prev; | ||
1118 | struct GNUNET_TIME_Relative left; | ||
1119 | |||
1120 | /* scheduler must be running */ | ||
1121 | GNUNET_assert (NULL != scheduler_driver); | ||
1122 | GNUNET_assert (NULL != task); | ||
1123 | t = GNUNET_new (struct GNUNET_SCHEDULER_Task); | ||
1124 | GNUNET_async_scope_get (&t->scope); | ||
1125 | t->callback = task; | ||
1126 | t->callback_cls = task_cls; | ||
1127 | t->read_fd = -1; | ||
1128 | t->write_fd = -1; | ||
1129 | #if PROFILE_DELAYS | ||
1130 | t->start_time = GNUNET_TIME_absolute_get (); | ||
1131 | #endif | ||
1132 | t->timeout = at; | ||
1133 | t->priority = check_priority (priority); | ||
1134 | t->lifeness = current_lifeness; | ||
1135 | init_backtrace (t); | ||
1136 | |||
1137 | left = GNUNET_TIME_absolute_get_remaining (at); | ||
1138 | if (0 == left.rel_value_us) | ||
1139 | { | ||
1140 | queue_ready_task (t); | ||
1141 | if (priority > work_priority) | ||
1142 | work_priority = priority; | ||
1143 | return t; | ||
1144 | } | ||
1145 | |||
1146 | /* try tail first (optimization in case we are | ||
1147 | * appending to a long list of tasks with timeouts) */ | ||
1148 | if ((NULL == pending_timeout_head) || | ||
1149 | (at.abs_value_us < pending_timeout_head->timeout.abs_value_us)) | ||
1150 | { | ||
1151 | GNUNET_CONTAINER_DLL_insert (pending_timeout_head, | ||
1152 | pending_timeout_tail, | ||
1153 | t); | ||
1154 | } | ||
1155 | else | ||
1156 | { | ||
1157 | /* first move from heuristic start backwards to before start time */ | ||
1158 | prev = pending_timeout_last; | ||
1159 | while ((NULL != prev) && | ||
1160 | (prev->timeout.abs_value_us > t->timeout.abs_value_us)) | ||
1161 | prev = prev->prev; | ||
1162 | /* now, move from heuristic start (or head of list) forward to insertion point */ | ||
1163 | if (NULL == prev) | ||
1164 | pos = pending_timeout_head; | ||
1165 | else | ||
1166 | pos = prev->next; | ||
1167 | while ((NULL != pos) && (pos->timeout.abs_value_us <= | ||
1168 | t->timeout.abs_value_us)) | ||
1169 | { | ||
1170 | prev = pos; | ||
1171 | pos = pos->next; | ||
1172 | } | ||
1173 | GNUNET_CONTAINER_DLL_insert_after (pending_timeout_head, | ||
1174 | pending_timeout_tail, | ||
1175 | prev, | ||
1176 | t); | ||
1177 | } | ||
1178 | /* finally, update heuristic insertion point to last insertion... */ | ||
1179 | pending_timeout_last = t; | ||
1180 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
1181 | "Adding task %p\n", | ||
1182 | t); | ||
1183 | return t; | ||
1184 | } | ||
1185 | |||
1186 | |||
1187 | /** | ||
1188 | * Schedule a new task to be run with a specified delay. The task | ||
1189 | * will be scheduled for execution once the delay has expired. | ||
1190 | * | ||
1191 | * @param delay when should this operation time out? | ||
1192 | * @param priority priority to use for the task | ||
1193 | * @param task main function of the task | ||
1194 | * @param task_cls closure of @a task | ||
1195 | * @return unique task identifier for the job | ||
1196 | * only valid until @a task is started! | ||
1197 | */ | ||
1198 | struct GNUNET_SCHEDULER_Task * | ||
1199 | GNUNET_SCHEDULER_add_delayed_with_priority (struct GNUNET_TIME_Relative delay, | ||
1200 | enum GNUNET_SCHEDULER_Priority | ||
1201 | priority, | ||
1202 | GNUNET_SCHEDULER_TaskCallback task, | ||
1203 | void *task_cls) | ||
1204 | { | ||
1205 | return GNUNET_SCHEDULER_add_at_with_priority ( | ||
1206 | GNUNET_TIME_relative_to_absolute (delay), | ||
1207 | priority, | ||
1208 | task, | ||
1209 | task_cls); | ||
1210 | } | ||
1211 | |||
1212 | |||
1213 | /** | ||
1214 | * Schedule a new task to be run with a specified priority. | ||
1215 | * | ||
1216 | * @param prio how important is the new task? | ||
1217 | * @param task main function of the task | ||
1218 | * @param task_cls closure of @a task | ||
1219 | * @return unique task identifier for the job | ||
1220 | * only valid until @a task is started! | ||
1221 | */ | ||
1222 | struct GNUNET_SCHEDULER_Task * | ||
1223 | GNUNET_SCHEDULER_add_with_priority (enum GNUNET_SCHEDULER_Priority prio, | ||
1224 | GNUNET_SCHEDULER_TaskCallback task, | ||
1225 | void *task_cls) | ||
1226 | { | ||
1227 | return GNUNET_SCHEDULER_add_delayed_with_priority (GNUNET_TIME_UNIT_ZERO, | ||
1228 | prio, | ||
1229 | task, | ||
1230 | task_cls); | ||
1231 | } | ||
1232 | |||
1233 | |||
1234 | /** | ||
1235 | * Schedule a new task to be run at the specified time. The task | ||
1236 | * will be scheduled for execution once specified time has been | ||
1237 | * reached. It will be run with the DEFAULT priority. | ||
1238 | * | ||
1239 | * @param at time at which this operation should run | ||
1240 | * @param task main function of the task | ||
1241 | * @param task_cls closure of @a task | ||
1242 | * @return unique task identifier for the job | ||
1243 | * only valid until @a task is started! | ||
1244 | */ | ||
1245 | struct GNUNET_SCHEDULER_Task * | ||
1246 | GNUNET_SCHEDULER_add_at (struct GNUNET_TIME_Absolute at, | ||
1247 | GNUNET_SCHEDULER_TaskCallback task, | ||
1248 | void *task_cls) | ||
1249 | { | ||
1250 | return GNUNET_SCHEDULER_add_at_with_priority (at, | ||
1251 | GNUNET_SCHEDULER_PRIORITY_DEFAULT, | ||
1252 | task, | ||
1253 | task_cls); | ||
1254 | } | ||
1255 | |||
1256 | |||
1257 | /** | ||
1258 | * Schedule a new task to be run with a specified delay. The task | ||
1259 | * will be scheduled for execution once the delay has expired. It | ||
1260 | * will be run with the DEFAULT priority. | ||
1261 | * | ||
1262 | * @param delay when should this operation time out? | ||
1263 | * @param task main function of the task | ||
1264 | * @param task_cls closure of @a task | ||
1265 | * @return unique task identifier for the job | ||
1266 | * only valid until @a task is started! | ||
1267 | */ | ||
1268 | struct GNUNET_SCHEDULER_Task * | ||
1269 | GNUNET_SCHEDULER_add_delayed (struct GNUNET_TIME_Relative delay, | ||
1270 | GNUNET_SCHEDULER_TaskCallback task, | ||
1271 | void *task_cls) | ||
1272 | { | ||
1273 | return GNUNET_SCHEDULER_add_delayed_with_priority (delay, | ||
1274 | GNUNET_SCHEDULER_PRIORITY_DEFAULT, | ||
1275 | task, | ||
1276 | task_cls); | ||
1277 | } | ||
1278 | |||
1279 | |||
1280 | /** | ||
1281 | * Schedule a new task to be run as soon as possible. Note that this | ||
1282 | * does not guarantee that this will be the next task that is being | ||
1283 | * run, as other tasks with higher priority (or that are already ready | ||
1284 | * to run) might get to run first. Just as with delays, clients must | ||
1285 | * not rely on any particular order of execution between tasks | ||
1286 | * scheduled concurrently. | ||
1287 | * | ||
1288 | * The task will be run with the DEFAULT priority. | ||
1289 | * | ||
1290 | * @param task main function of the task | ||
1291 | * @param task_cls closure of @a task | ||
1292 | * @return unique task identifier for the job | ||
1293 | * only valid until @a task is started! | ||
1294 | */ | ||
1295 | struct GNUNET_SCHEDULER_Task * | ||
1296 | GNUNET_SCHEDULER_add_now (GNUNET_SCHEDULER_TaskCallback task, | ||
1297 | void *task_cls) | ||
1298 | { | ||
1299 | struct GNUNET_SCHEDULER_Task *t; | ||
1300 | |||
1301 | t = GNUNET_new (struct GNUNET_SCHEDULER_Task); | ||
1302 | GNUNET_async_scope_get (&t->scope); | ||
1303 | t->callback = task; | ||
1304 | t->callback_cls = task_cls; | ||
1305 | t->read_fd = -1; | ||
1306 | t->write_fd = -1; | ||
1307 | #if PROFILE_DELAYS | ||
1308 | t->start_time = GNUNET_TIME_absolute_get (); | ||
1309 | #endif | ||
1310 | t->timeout = GNUNET_TIME_UNIT_ZERO_ABS; | ||
1311 | t->priority = current_priority; | ||
1312 | t->on_shutdown = GNUNET_YES; | ||
1313 | t->lifeness = current_lifeness; | ||
1314 | queue_ready_task (t); | ||
1315 | init_backtrace (t); | ||
1316 | return t; | ||
1317 | } | ||
1318 | |||
1319 | |||
1320 | /** | ||
1321 | * Schedule a new task to be run on shutdown, that is when a CTRL-C | ||
1322 | * signal is received, or when #GNUNET_SCHEDULER_shutdown() is being | ||
1323 | * invoked. | ||
1324 | * | ||
1325 | * @param task main function of the task | ||
1326 | * @param task_cls closure of @a task | ||
1327 | * @return unique task identifier for the job | ||
1328 | * only valid until @a task is started! | ||
1329 | */ | ||
1330 | struct GNUNET_SCHEDULER_Task * | ||
1331 | GNUNET_SCHEDULER_add_shutdown (GNUNET_SCHEDULER_TaskCallback task, | ||
1332 | void *task_cls) | ||
1333 | { | ||
1334 | struct GNUNET_SCHEDULER_Task *t; | ||
1335 | |||
1336 | /* scheduler must be running */ | ||
1337 | GNUNET_assert (NULL != scheduler_driver); | ||
1338 | GNUNET_assert (NULL != task); | ||
1339 | t = GNUNET_new (struct GNUNET_SCHEDULER_Task); | ||
1340 | GNUNET_async_scope_get (&t->scope); | ||
1341 | t->callback = task; | ||
1342 | t->callback_cls = task_cls; | ||
1343 | t->read_fd = -1; | ||
1344 | t->write_fd = -1; | ||
1345 | #if PROFILE_DELAYS | ||
1346 | t->start_time = GNUNET_TIME_absolute_get (); | ||
1347 | #endif | ||
1348 | t->timeout = GNUNET_TIME_UNIT_FOREVER_ABS; | ||
1349 | t->priority = GNUNET_SCHEDULER_PRIORITY_SHUTDOWN; | ||
1350 | t->on_shutdown = GNUNET_YES; | ||
1351 | t->lifeness = GNUNET_NO; | ||
1352 | GNUNET_CONTAINER_DLL_insert (shutdown_head, | ||
1353 | shutdown_tail, | ||
1354 | t); | ||
1355 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
1356 | "Adding shutdown task %p\n", | ||
1357 | t); | ||
1358 | init_backtrace (t); | ||
1359 | return t; | ||
1360 | } | ||
1361 | |||
1362 | |||
1363 | /** | ||
1364 | * Schedule a new task to be run as soon as possible with the | ||
1365 | * (transitive) ignore-shutdown flag either explicitly set or | ||
1366 | * explicitly enabled. This task (and all tasks created from it, | ||
1367 | * other than by another call to this function) will either count or | ||
1368 | * not count for the "lifeness" of the process. This API is only | ||
1369 | * useful in a few special cases. | ||
1370 | * | ||
1371 | * @param lifeness #GNUNET_YES if the task counts for lifeness, #GNUNET_NO if not. | ||
1372 | * @param task main function of the task | ||
1373 | * @param task_cls closure of @a task | ||
1374 | * @return unique task identifier for the job | ||
1375 | * only valid until @a task is started! | ||
1376 | */ | ||
1377 | struct GNUNET_SCHEDULER_Task * | ||
1378 | GNUNET_SCHEDULER_add_now_with_lifeness (int lifeness, | ||
1379 | GNUNET_SCHEDULER_TaskCallback task, | ||
1380 | void *task_cls) | ||
1381 | { | ||
1382 | struct GNUNET_SCHEDULER_Task *ret; | ||
1383 | |||
1384 | ret = GNUNET_SCHEDULER_add_now (task, task_cls); | ||
1385 | ret->lifeness = lifeness; | ||
1386 | return ret; | ||
1387 | } | ||
1388 | |||
1389 | |||
1390 | #if DEBUG_FDS | ||
1391 | /** | ||
1392 | * check a raw file descriptor and abort if it is bad (for debugging purposes) | ||
1393 | * | ||
1394 | * @param t the task related to the file descriptor | ||
1395 | * @param raw_fd the raw file descriptor to check | ||
1396 | */ | ||
1397 | void | ||
1398 | check_fd (struct GNUNET_SCHEDULER_Task *t, int raw_fd) | ||
1399 | { | ||
1400 | if (-1 != raw_fd) | ||
1401 | { | ||
1402 | int flags = fcntl (raw_fd, F_GETFD); | ||
1403 | |||
1404 | if ((flags == -1) && (errno == EBADF)) | ||
1405 | { | ||
1406 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
1407 | "Got invalid file descriptor %d!\n", | ||
1408 | raw_fd); | ||
1409 | init_backtrace (t); | ||
1410 | GNUNET_assert (0); | ||
1411 | } | ||
1412 | } | ||
1413 | } | ||
1414 | |||
1415 | |||
1416 | #endif | ||
1417 | |||
1418 | |||
1419 | /** | ||
1420 | * Schedule a new task to be run with a specified delay or when any of | ||
1421 | * the specified file descriptor sets is ready. The delay can be used | ||
1422 | * as a timeout on the socket(s) being ready. The task will be | ||
1423 | * scheduled for execution once either the delay has expired or any of | ||
1424 | * the socket operations is ready. This is the most general | ||
1425 | * function of the "add" family. Note that the "prerequisite_task" | ||
1426 | * must be satisfied in addition to any of the other conditions. In | ||
1427 | * other words, the task will be started when | ||
1428 | * <code> | ||
1429 | * (prerequisite-run) | ||
1430 | * && (delay-ready | ||
1431 | * || any-rs-ready | ||
1432 | * || any-ws-ready) | ||
1433 | * </code> | ||
1434 | * | ||
1435 | * @param delay how long should we wait? | ||
1436 | * @param priority priority to use | ||
1437 | * @param rfd file descriptor we want to read (can be -1) | ||
1438 | * @param wfd file descriptors we want to write (can be -1) | ||
1439 | * @param task main function of the task | ||
1440 | * @param task_cls closure of @a task | ||
1441 | * @return unique task identifier for the job | ||
1442 | * only valid until @a task is started! | ||
1443 | */ | ||
1444 | static struct GNUNET_SCHEDULER_Task * | ||
1445 | add_without_sets (struct GNUNET_TIME_Relative delay, | ||
1446 | enum GNUNET_SCHEDULER_Priority priority, | ||
1447 | const struct GNUNET_NETWORK_Handle *read_nh, | ||
1448 | const struct GNUNET_NETWORK_Handle *write_nh, | ||
1449 | const struct GNUNET_DISK_FileHandle *read_fh, | ||
1450 | const struct GNUNET_DISK_FileHandle *write_fh, | ||
1451 | GNUNET_SCHEDULER_TaskCallback task, | ||
1452 | void *task_cls) | ||
1453 | { | ||
1454 | struct GNUNET_SCHEDULER_Task *t; | ||
1455 | |||
1456 | /* scheduler must be running */ | ||
1457 | GNUNET_assert (NULL != scheduler_driver); | ||
1458 | GNUNET_assert (NULL != task); | ||
1459 | t = GNUNET_new (struct GNUNET_SCHEDULER_Task); | ||
1460 | GNUNET_async_scope_get (&t->scope); | ||
1461 | init_fd_info (t, | ||
1462 | &read_nh, | ||
1463 | read_nh ? 1 : 0, | ||
1464 | &write_nh, | ||
1465 | write_nh ? 1 : 0, | ||
1466 | &read_fh, | ||
1467 | read_fh ? 1 : 0, | ||
1468 | &write_fh, | ||
1469 | write_fh ? 1 : 0); | ||
1470 | t->callback = task; | ||
1471 | t->callback_cls = task_cls; | ||
1472 | #if DEBUG_FDS | ||
1473 | check_fd (t, NULL != read_nh ? GNUNET_NETWORK_get_fd (read_nh) : -1); | ||
1474 | check_fd (t, NULL != write_nh ? GNUNET_NETWORK_get_fd (write_nh) : -1); | ||
1475 | check_fd (t, NULL != read_fh ? read_fh->fd : -1); | ||
1476 | check_fd (t, NULL != write_fh ? write_fh->fd : -1); | ||
1477 | #endif | ||
1478 | #if PROFILE_DELAYS | ||
1479 | t->start_time = GNUNET_TIME_absolute_get (); | ||
1480 | #endif | ||
1481 | t->timeout = GNUNET_TIME_relative_to_absolute (delay); | ||
1482 | t->priority = check_priority ((priority == GNUNET_SCHEDULER_PRIORITY_KEEP) ? | ||
1483 | current_priority : priority); | ||
1484 | t->lifeness = current_lifeness; | ||
1485 | GNUNET_CONTAINER_DLL_insert (pending_head, | ||
1486 | pending_tail, | ||
1487 | t); | ||
1488 | driver_add_multiple (t); | ||
1489 | max_priority_added = GNUNET_MAX (max_priority_added, | ||
1490 | t->priority); | ||
1491 | init_backtrace (t); | ||
1492 | return t; | ||
1493 | } | ||
1494 | |||
1495 | |||
1496 | /** | ||
1497 | * Schedule a new task to be run with a specified delay or when the | ||
1498 | * specified file descriptor is ready for reading. The delay can be | ||
1499 | * used as a timeout on the socket being ready. The task will be | ||
1500 | * scheduled for execution once either the delay has expired or the | ||
1501 | * socket operation is ready. It will be run with the DEFAULT priority. | ||
1502 | * Only allowed to be called as long as the scheduler is running, that | ||
1503 | * is one of the following conditions is met: | ||
1504 | * | ||
1505 | * - #GNUNET_SCHEDULER_run has been called and has not returned yet | ||
1506 | * - #GNUNET_SCHEDULER_driver_init has been run and | ||
1507 | * #GNUNET_SCHEDULER_driver_done has not been called yet | ||
1508 | * | ||
1509 | * @param delay when should this operation time out? | ||
1510 | * @param rfd read file-descriptor | ||
1511 | * @param task main function of the task | ||
1512 | * @param task_cls closure of @a task | ||
1513 | * @return unique task identifier for the job | ||
1514 | * only valid until @a task is started! | ||
1515 | */ | ||
1516 | struct GNUNET_SCHEDULER_Task * | ||
1517 | GNUNET_SCHEDULER_add_read_net (struct GNUNET_TIME_Relative delay, | ||
1518 | struct GNUNET_NETWORK_Handle *rfd, | ||
1519 | GNUNET_SCHEDULER_TaskCallback task, | ||
1520 | void *task_cls) | ||
1521 | { | ||
1522 | return GNUNET_SCHEDULER_add_read_net_with_priority (delay, | ||
1523 | GNUNET_SCHEDULER_PRIORITY_DEFAULT, | ||
1524 | rfd, task, task_cls); | ||
1525 | } | ||
1526 | |||
1527 | |||
1528 | /** | ||
1529 | * Schedule a new task to be run with a specified priority and to be | ||
1530 | * run after the specified delay or when the specified file descriptor | ||
1531 | * is ready for reading. The delay can be used as a timeout on the | ||
1532 | * socket being ready. The task will be scheduled for execution once | ||
1533 | * either the delay has expired or the socket operation is ready. It | ||
1534 | * will be run with the DEFAULT priority. | ||
1535 | * Only allowed to be called as long as the scheduler is running, that | ||
1536 | * is one of the following conditions is met: | ||
1537 | * | ||
1538 | * - #GNUNET_SCHEDULER_run has been called and has not returned yet | ||
1539 | * - #GNUNET_SCHEDULER_driver_init has been run and | ||
1540 | * #GNUNET_SCHEDULER_driver_done has not been called yet | ||
1541 | * | ||
1542 | * @param delay when should this operation time out? | ||
1543 | * @param priority priority to use for the task | ||
1544 | * @param rfd read file-descriptor | ||
1545 | * @param task main function of the task | ||
1546 | * @param task_cls closure of @a task | ||
1547 | * @return unique task identifier for the job | ||
1548 | * only valid until @a task is started! | ||
1549 | */ | ||
1550 | struct GNUNET_SCHEDULER_Task * | ||
1551 | GNUNET_SCHEDULER_add_read_net_with_priority (struct GNUNET_TIME_Relative delay, | ||
1552 | enum GNUNET_SCHEDULER_Priority | ||
1553 | priority, | ||
1554 | struct GNUNET_NETWORK_Handle *rfd, | ||
1555 | GNUNET_SCHEDULER_TaskCallback task, | ||
1556 | void *task_cls) | ||
1557 | { | ||
1558 | return GNUNET_SCHEDULER_add_net_with_priority (delay, priority, | ||
1559 | rfd, | ||
1560 | GNUNET_YES, | ||
1561 | GNUNET_NO, | ||
1562 | task, task_cls); | ||
1563 | } | ||
1564 | |||
1565 | |||
1566 | /** | ||
1567 | * Schedule a new task to be run with a specified delay or when the | ||
1568 | * specified file descriptor is ready for writing. The delay can be | ||
1569 | * used as a timeout on the socket being ready. The task will be | ||
1570 | * scheduled for execution once either the delay has expired or the | ||
1571 | * socket operation is ready. It will be run with the priority of | ||
1572 | * the calling task. | ||
1573 | * Only allowed to be called as long as the scheduler is running, that | ||
1574 | * is one of the following conditions is met: | ||
1575 | * | ||
1576 | * - #GNUNET_SCHEDULER_run has been called and has not returned yet | ||
1577 | * - #GNUNET_SCHEDULER_driver_init has been run and | ||
1578 | * #GNUNET_SCHEDULER_driver_done has not been called yet | ||
1579 | * | ||
1580 | * @param delay when should this operation time out? | ||
1581 | * @param wfd write file-descriptor | ||
1582 | * @param task main function of the task | ||
1583 | * @param task_cls closure of @a task | ||
1584 | * @return unique task identifier for the job | ||
1585 | * only valid until @a task is started! | ||
1586 | */ | ||
1587 | struct GNUNET_SCHEDULER_Task * | ||
1588 | GNUNET_SCHEDULER_add_write_net (struct GNUNET_TIME_Relative delay, | ||
1589 | struct GNUNET_NETWORK_Handle *wfd, | ||
1590 | GNUNET_SCHEDULER_TaskCallback task, | ||
1591 | void *task_cls) | ||
1592 | { | ||
1593 | return GNUNET_SCHEDULER_add_net_with_priority (delay, | ||
1594 | GNUNET_SCHEDULER_PRIORITY_DEFAULT, | ||
1595 | wfd, | ||
1596 | GNUNET_NO, GNUNET_YES, | ||
1597 | task, task_cls); | ||
1598 | } | ||
1599 | |||
1600 | |||
1601 | /** | ||
1602 | * Schedule a new task to be run with a specified delay or when the | ||
1603 | * specified file descriptor is ready. The delay can be | ||
1604 | * used as a timeout on the socket being ready. The task will be | ||
1605 | * scheduled for execution once either the delay has expired or the | ||
1606 | * socket operation is ready. | ||
1607 | * Only allowed to be called as long as the scheduler is running, that | ||
1608 | * is one of the following conditions is met: | ||
1609 | * | ||
1610 | * - #GNUNET_SCHEDULER_run has been called and has not returned yet | ||
1611 | * - #GNUNET_SCHEDULER_driver_init has been run and | ||
1612 | * #GNUNET_SCHEDULER_driver_done has not been called yet | ||
1613 | * | ||
1614 | * @param delay when should this operation time out? | ||
1615 | * @param priority priority of the task | ||
1616 | * @param fd file-descriptor | ||
1617 | * @param on_read whether to poll the file-descriptor for readability | ||
1618 | * @param on_write whether to poll the file-descriptor for writability | ||
1619 | * @param task main function of the task | ||
1620 | * @param task_cls closure of task | ||
1621 | * @return unique task identifier for the job | ||
1622 | * only valid until "task" is started! | ||
1623 | */ | ||
1624 | struct GNUNET_SCHEDULER_Task * | ||
1625 | GNUNET_SCHEDULER_add_net_with_priority (struct GNUNET_TIME_Relative delay, | ||
1626 | enum GNUNET_SCHEDULER_Priority priority, | ||
1627 | struct GNUNET_NETWORK_Handle *fd, | ||
1628 | int on_read, | ||
1629 | int on_write, | ||
1630 | GNUNET_SCHEDULER_TaskCallback task, | ||
1631 | void *task_cls) | ||
1632 | { | ||
1633 | /* scheduler must be running */ | ||
1634 | GNUNET_assert (NULL != scheduler_driver); | ||
1635 | GNUNET_assert (on_read || on_write); | ||
1636 | GNUNET_assert (GNUNET_NETWORK_get_fd (fd) >= 0); | ||
1637 | return add_without_sets (delay, priority, | ||
1638 | on_read ? fd : NULL, | ||
1639 | on_write ? fd : NULL, | ||
1640 | NULL, | ||
1641 | NULL, | ||
1642 | task, task_cls); | ||
1643 | } | ||
1644 | |||
1645 | |||
1646 | /** | ||
1647 | * Schedule a new task to be run with a specified delay or when the | ||
1648 | * specified file descriptor is ready for reading. The delay can be | ||
1649 | * used as a timeout on the socket being ready. The task will be | ||
1650 | * scheduled for execution once either the delay has expired or the | ||
1651 | * socket operation is ready. It will be run with the DEFAULT priority. | ||
1652 | * Only allowed to be called as long as the scheduler is running, that | ||
1653 | * is one of the following conditions is met: | ||
1654 | * | ||
1655 | * - #GNUNET_SCHEDULER_run has been called and has not returned yet | ||
1656 | * - #GNUNET_SCHEDULER_driver_init has been run and | ||
1657 | * #GNUNET_SCHEDULER_driver_done has not been called yet | ||
1658 | * | ||
1659 | * @param delay when should this operation time out? | ||
1660 | * @param rfd read file-descriptor | ||
1661 | * @param task main function of the task | ||
1662 | * @param task_cls closure of @a task | ||
1663 | * @return unique task identifier for the job | ||
1664 | * only valid until @a task is started! | ||
1665 | */ | ||
1666 | struct GNUNET_SCHEDULER_Task * | ||
1667 | GNUNET_SCHEDULER_add_read_file (struct GNUNET_TIME_Relative delay, | ||
1668 | const struct GNUNET_DISK_FileHandle *rfd, | ||
1669 | GNUNET_SCHEDULER_TaskCallback task, | ||
1670 | void *task_cls) | ||
1671 | { | ||
1672 | return GNUNET_SCHEDULER_add_file_with_priority ( | ||
1673 | delay, GNUNET_SCHEDULER_PRIORITY_DEFAULT, | ||
1674 | rfd, GNUNET_YES, GNUNET_NO, | ||
1675 | task, task_cls); | ||
1676 | } | ||
1677 | |||
1678 | |||
1679 | /** | ||
1680 | * Schedule a new task to be run with a specified delay or when the | ||
1681 | * specified file descriptor is ready for writing. The delay can be | ||
1682 | * used as a timeout on the socket being ready. The task will be | ||
1683 | * scheduled for execution once either the delay has expired or the | ||
1684 | * socket operation is ready. It will be run with the DEFAULT priority. | ||
1685 | * Only allowed to be called as long as the scheduler is running, that | ||
1686 | * is one of the following conditions is met: | ||
1687 | * | ||
1688 | * - #GNUNET_SCHEDULER_run has been called and has not returned yet | ||
1689 | * - #GNUNET_SCHEDULER_driver_init has been run and | ||
1690 | * #GNUNET_SCHEDULER_driver_done has not been called yet | ||
1691 | * | ||
1692 | * @param delay when should this operation time out? | ||
1693 | * @param wfd write file-descriptor | ||
1694 | * @param task main function of the task | ||
1695 | * @param task_cls closure of @a task | ||
1696 | * @return unique task identifier for the job | ||
1697 | * only valid until @a task is started! | ||
1698 | */ | ||
1699 | struct GNUNET_SCHEDULER_Task * | ||
1700 | GNUNET_SCHEDULER_add_write_file (struct GNUNET_TIME_Relative delay, | ||
1701 | const struct GNUNET_DISK_FileHandle *wfd, | ||
1702 | GNUNET_SCHEDULER_TaskCallback task, | ||
1703 | void *task_cls) | ||
1704 | { | ||
1705 | return GNUNET_SCHEDULER_add_file_with_priority ( | ||
1706 | delay, GNUNET_SCHEDULER_PRIORITY_DEFAULT, | ||
1707 | wfd, GNUNET_NO, GNUNET_YES, | ||
1708 | task, task_cls); | ||
1709 | } | ||
1710 | |||
1711 | |||
1712 | /** | ||
1713 | * Schedule a new task to be run with a specified delay or when the | ||
1714 | * specified file descriptor is ready. The delay can be | ||
1715 | * used as a timeout on the socket being ready. The task will be | ||
1716 | * scheduled for execution once either the delay has expired or the | ||
1717 | * socket operation is ready. | ||
1718 | * Only allowed to be called as long as the scheduler is running, that | ||
1719 | * is one of the following conditions is met: | ||
1720 | * | ||
1721 | * - #GNUNET_SCHEDULER_run has been called and has not returned yet | ||
1722 | * - #GNUNET_SCHEDULER_driver_init has been run and | ||
1723 | * #GNUNET_SCHEDULER_driver_done has not been called yet | ||
1724 | * | ||
1725 | * @param delay when should this operation time out? | ||
1726 | * @param priority priority of the task | ||
1727 | * @param fd file-descriptor | ||
1728 | * @param on_read whether to poll the file-descriptor for readability | ||
1729 | * @param on_write whether to poll the file-descriptor for writability | ||
1730 | * @param task main function of the task | ||
1731 | * @param task_cls closure of @a task | ||
1732 | * @return unique task identifier for the job | ||
1733 | * only valid until @a task is started! | ||
1734 | */ | ||
1735 | struct GNUNET_SCHEDULER_Task * | ||
1736 | GNUNET_SCHEDULER_add_file_with_priority (struct GNUNET_TIME_Relative delay, | ||
1737 | enum GNUNET_SCHEDULER_Priority | ||
1738 | priority, | ||
1739 | const struct | ||
1740 | GNUNET_DISK_FileHandle *fd, | ||
1741 | int on_read, int on_write, | ||
1742 | GNUNET_SCHEDULER_TaskCallback task, | ||
1743 | void *task_cls) | ||
1744 | { | ||
1745 | /* scheduler must be running */ | ||
1746 | GNUNET_assert (NULL != scheduler_driver); | ||
1747 | GNUNET_assert (on_read || on_write); | ||
1748 | GNUNET_assert (fd->fd >= 0); | ||
1749 | return add_without_sets (delay, priority, | ||
1750 | NULL, | ||
1751 | NULL, | ||
1752 | on_read ? fd : NULL, | ||
1753 | on_write ? fd : NULL, | ||
1754 | task, task_cls); | ||
1755 | } | ||
1756 | |||
1757 | |||
1758 | void | ||
1759 | extract_handles (const struct GNUNET_NETWORK_FDSet *fdset, | ||
1760 | const struct GNUNET_NETWORK_Handle ***ntarget, | ||
1761 | unsigned int *extracted_nhandles, | ||
1762 | const struct GNUNET_DISK_FileHandle ***ftarget, | ||
1763 | unsigned int *extracted_fhandles) | ||
1764 | { | ||
1765 | // FIXME: this implementation only works for unix, for WIN32 the file handles | ||
1766 | // in fdset must be handled separately | ||
1767 | const struct GNUNET_NETWORK_Handle **nhandles; | ||
1768 | const struct GNUNET_DISK_FileHandle **fhandles; | ||
1769 | unsigned int nhandles_len; | ||
1770 | unsigned int fhandles_len; | ||
1771 | |||
1772 | nhandles = NULL; | ||
1773 | fhandles = NULL; | ||
1774 | nhandles_len = 0; | ||
1775 | fhandles_len = 0; | ||
1776 | for (int sock = 0; sock != fdset->nsds; ++sock) | ||
1777 | { | ||
1778 | if (GNUNET_YES == GNUNET_NETWORK_fdset_test_native (fdset, sock)) | ||
1779 | { | ||
1780 | struct GNUNET_NETWORK_Handle *nhandle; | ||
1781 | struct GNUNET_DISK_FileHandle *fhandle; | ||
1782 | |||
1783 | nhandle = GNUNET_NETWORK_socket_box_native (sock); | ||
1784 | if (NULL != nhandle) | ||
1785 | { | ||
1786 | GNUNET_array_append (nhandles, nhandles_len, nhandle); | ||
1787 | } | ||
1788 | else | ||
1789 | { | ||
1790 | fhandle = GNUNET_DISK_get_handle_from_int_fd (sock); | ||
1791 | if (NULL != fhandle) | ||
1792 | { | ||
1793 | GNUNET_array_append (fhandles, fhandles_len, fhandle); | ||
1794 | } | ||
1795 | else | ||
1796 | { | ||
1797 | GNUNET_assert (0); | ||
1798 | } | ||
1799 | } | ||
1800 | } | ||
1801 | } | ||
1802 | *ntarget = nhandles_len > 0 ? nhandles : NULL; | ||
1803 | *ftarget = fhandles_len > 0 ? fhandles : NULL; | ||
1804 | *extracted_nhandles = nhandles_len; | ||
1805 | *extracted_fhandles = fhandles_len; | ||
1806 | } | ||
1807 | |||
1808 | |||
1809 | /** | ||
1810 | * Schedule a new task to be run with a specified delay or when any of | ||
1811 | * the specified file descriptor sets is ready. The delay can be used | ||
1812 | * as a timeout on the socket(s) being ready. The task will be | ||
1813 | * scheduled for execution once either the delay has expired or any of | ||
1814 | * the socket operations is ready. This is the most general | ||
1815 | * function of the "add" family. Note that the "prerequisite_task" | ||
1816 | * must be satisfied in addition to any of the other conditions. In | ||
1817 | * other words, the task will be started when | ||
1818 | * <code> | ||
1819 | * (prerequisite-run) | ||
1820 | * && (delay-ready | ||
1821 | * || any-rs-ready | ||
1822 | * || any-ws-ready) ) | ||
1823 | * </code> | ||
1824 | * Only allowed to be called as long as the scheduler is running, that | ||
1825 | * is one of the following conditions is met: | ||
1826 | * | ||
1827 | * - #GNUNET_SCHEDULER_run has been called and has not returned yet | ||
1828 | * - #GNUNET_SCHEDULER_driver_init has been run and | ||
1829 | * #GNUNET_SCHEDULER_driver_done has not been called yet | ||
1830 | * | ||
1831 | * @param prio how important is this task? | ||
1832 | * @param delay how long should we wait? | ||
1833 | * @param rs set of file descriptors we want to read (can be NULL) | ||
1834 | * @param ws set of file descriptors we want to write (can be NULL) | ||
1835 | * @param task main function of the task | ||
1836 | * @param task_cls closure of @a task | ||
1837 | * @return unique task identifier for the job | ||
1838 | * only valid until @a task is started! | ||
1839 | */ | ||
1840 | struct GNUNET_SCHEDULER_Task * | ||
1841 | GNUNET_SCHEDULER_add_select (enum GNUNET_SCHEDULER_Priority prio, | ||
1842 | struct GNUNET_TIME_Relative delay, | ||
1843 | const struct GNUNET_NETWORK_FDSet *rs, | ||
1844 | const struct GNUNET_NETWORK_FDSet *ws, | ||
1845 | GNUNET_SCHEDULER_TaskCallback task, | ||
1846 | void *task_cls) | ||
1847 | { | ||
1848 | struct GNUNET_SCHEDULER_Task *t; | ||
1849 | const struct GNUNET_NETWORK_Handle **read_nhandles = NULL; | ||
1850 | const struct GNUNET_NETWORK_Handle **write_nhandles = NULL; | ||
1851 | const struct GNUNET_DISK_FileHandle **read_fhandles = NULL; | ||
1852 | const struct GNUNET_DISK_FileHandle **write_fhandles = NULL; | ||
1853 | unsigned int read_nhandles_len = 0; | ||
1854 | unsigned int write_nhandles_len = 0; | ||
1855 | unsigned int read_fhandles_len = 0; | ||
1856 | unsigned int write_fhandles_len = 0; | ||
1857 | |||
1858 | /* scheduler must be running */ | ||
1859 | GNUNET_assert (NULL != scheduler_driver); | ||
1860 | GNUNET_assert (NULL != task); | ||
1861 | int no_rs = (NULL == rs); | ||
1862 | int no_ws = (NULL == ws); | ||
1863 | int empty_rs = (NULL != rs) && (0 == rs->nsds); | ||
1864 | int empty_ws = (NULL != ws) && (0 == ws->nsds); | ||
1865 | int no_fds = (no_rs && no_ws) || | ||
1866 | (empty_rs && empty_ws) || | ||
1867 | (no_rs && empty_ws) || | ||
1868 | (no_ws && empty_rs); | ||
1869 | if (! no_fds) | ||
1870 | { | ||
1871 | if (NULL != rs) | ||
1872 | { | ||
1873 | extract_handles (rs, | ||
1874 | &read_nhandles, | ||
1875 | &read_nhandles_len, | ||
1876 | &read_fhandles, | ||
1877 | &read_fhandles_len); | ||
1878 | } | ||
1879 | if (NULL != ws) | ||
1880 | { | ||
1881 | extract_handles (ws, | ||
1882 | &write_nhandles, | ||
1883 | &write_nhandles_len, | ||
1884 | &write_fhandles, | ||
1885 | &write_fhandles_len); | ||
1886 | } | ||
1887 | } | ||
1888 | /** | ||
1889 | * here we consider the case that a GNUNET_NETWORK_FDSet might be empty | ||
1890 | * although its maximum FD number (nsds) is greater than 0. We handle | ||
1891 | * this case gracefully because some libraries such as libmicrohttpd | ||
1892 | * only provide a hint what the maximum FD number in an FD set might be | ||
1893 | * and not the exact FD number (see e.g. gnunet-rest-service.c) | ||
1894 | */int no_fds_extracted = (0 == read_nhandles_len) && | ||
1895 | (0 == read_fhandles_len) && | ||
1896 | (0 == write_nhandles_len) && | ||
1897 | (0 == write_fhandles_len); | ||
1898 | if (no_fds || no_fds_extracted) | ||
1899 | return GNUNET_SCHEDULER_add_delayed_with_priority (delay, | ||
1900 | prio, | ||
1901 | task, | ||
1902 | task_cls); | ||
1903 | t = GNUNET_new (struct GNUNET_SCHEDULER_Task); | ||
1904 | GNUNET_async_scope_get (&t->scope); | ||
1905 | init_fd_info (t, | ||
1906 | read_nhandles, | ||
1907 | read_nhandles_len, | ||
1908 | write_nhandles, | ||
1909 | write_nhandles_len, | ||
1910 | read_fhandles, | ||
1911 | read_fhandles_len, | ||
1912 | write_fhandles, | ||
1913 | write_fhandles_len); | ||
1914 | t->callback = task; | ||
1915 | t->callback_cls = task_cls; | ||
1916 | t->own_handles = GNUNET_YES; | ||
1917 | /* free the arrays of pointers to network / file handles, the actual | ||
1918 | * handles will be freed in destroy_task */ | ||
1919 | GNUNET_array_grow (read_nhandles, read_nhandles_len, 0); | ||
1920 | GNUNET_array_grow (write_nhandles, write_nhandles_len, 0); | ||
1921 | GNUNET_array_grow (read_fhandles, read_fhandles_len, 0); | ||
1922 | GNUNET_array_grow (write_fhandles, write_fhandles_len, 0); | ||
1923 | #if PROFILE_DELAYS | ||
1924 | t->start_time = GNUNET_TIME_absolute_get (); | ||
1925 | #endif | ||
1926 | t->timeout = GNUNET_TIME_relative_to_absolute (delay); | ||
1927 | t->priority = | ||
1928 | check_priority ((prio == | ||
1929 | GNUNET_SCHEDULER_PRIORITY_KEEP) ? current_priority : | ||
1930 | prio); | ||
1931 | t->lifeness = current_lifeness; | ||
1932 | GNUNET_CONTAINER_DLL_insert (pending_head, | ||
1933 | pending_tail, | ||
1934 | t); | ||
1935 | driver_add_multiple (t); | ||
1936 | max_priority_added = GNUNET_MAX (max_priority_added, | ||
1937 | t->priority); | ||
1938 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
1939 | "Adding task %p\n", | ||
1940 | t); | ||
1941 | init_backtrace (t); | ||
1942 | return t; | ||
1943 | } | ||
1944 | |||
1945 | |||
1946 | /** | ||
1947 | * Function used by event-loop implementations to signal the scheduler | ||
1948 | * that a particular @a task is ready due to an event specified in the | ||
1949 | * et field of @a fdi. | ||
1950 | * | ||
1951 | * This function will then queue the task to notify the application | ||
1952 | * that the task is ready (with the respective priority). | ||
1953 | * | ||
1954 | * @param task the task that is ready | ||
1955 | * @param fdi information about the related FD | ||
1956 | */ | ||
1957 | void | ||
1958 | GNUNET_SCHEDULER_task_ready (struct GNUNET_SCHEDULER_Task *task, | ||
1959 | struct GNUNET_SCHEDULER_FdInfo *fdi) | ||
1960 | { | ||
1961 | enum GNUNET_SCHEDULER_Reason reason; | ||
1962 | |||
1963 | reason = task->reason; | ||
1964 | if ((0 == (reason & GNUNET_SCHEDULER_REASON_READ_READY)) && | ||
1965 | (0 != (GNUNET_SCHEDULER_ET_IN & fdi->et))) | ||
1966 | reason |= GNUNET_SCHEDULER_REASON_READ_READY; | ||
1967 | if ((0 == (reason & GNUNET_SCHEDULER_REASON_WRITE_READY)) && | ||
1968 | (0 != (GNUNET_SCHEDULER_ET_OUT & fdi->et))) | ||
1969 | reason |= GNUNET_SCHEDULER_REASON_WRITE_READY; | ||
1970 | reason |= GNUNET_SCHEDULER_REASON_PREREQ_DONE; | ||
1971 | task->reason = reason; | ||
1972 | if (GNUNET_NO == task->in_ready_list) | ||
1973 | { | ||
1974 | GNUNET_CONTAINER_DLL_remove (pending_head, | ||
1975 | pending_tail, | ||
1976 | task); | ||
1977 | queue_ready_task (task); | ||
1978 | } | ||
1979 | } | ||
1980 | |||
1981 | |||
1982 | /** | ||
1983 | * Function called by external event loop implementations to tell the | ||
1984 | * scheduler to run some of the tasks that are ready. Must be called | ||
1985 | * only after #GNUNET_SCHEDULER_driver_init has been called and before | ||
1986 | * #GNUNET_SCHEDULER_driver_done is called. | ||
1987 | * This function may return even though there are tasks left to run | ||
1988 | * just to give other tasks a chance as well. If we return #GNUNET_YES, | ||
1989 | * the event loop implementation should call this function again as | ||
1990 | * soon as possible, while if we return #GNUNET_NO it must block until | ||
1991 | * either the operating system has more work (the scheduler has no more | ||
1992 | * work to do right now) or the timeout set by the scheduler (using the | ||
1993 | * set_wakeup callback) is reached. | ||
1994 | * | ||
1995 | * @param sh scheduler handle that was returned by | ||
1996 | * #GNUNET_SCHEDULER_driver_init | ||
1997 | * @return #GNUNET_YES if there are more tasks that are ready, | ||
1998 | * and thus we would like to run more (yield to avoid | ||
1999 | * blocking other activities for too long) #GNUNET_NO | ||
2000 | * if we are done running tasks (yield to block) | ||
2001 | */ | ||
2002 | int | ||
2003 | GNUNET_SCHEDULER_do_work (struct GNUNET_SCHEDULER_Handle *sh) | ||
2004 | { | ||
2005 | struct GNUNET_SCHEDULER_Task *pos; | ||
2006 | struct GNUNET_TIME_Absolute now; | ||
2007 | |||
2008 | /* check for tasks that reached the timeout! */ | ||
2009 | now = GNUNET_TIME_absolute_get (); | ||
2010 | pos = pending_timeout_head; | ||
2011 | while (NULL != pos) | ||
2012 | { | ||
2013 | struct GNUNET_SCHEDULER_Task *next = pos->next; | ||
2014 | if (now.abs_value_us >= pos->timeout.abs_value_us) | ||
2015 | pos->reason |= GNUNET_SCHEDULER_REASON_TIMEOUT; | ||
2016 | if (0 == pos->reason) | ||
2017 | break; | ||
2018 | GNUNET_CONTAINER_DLL_remove (pending_timeout_head, | ||
2019 | pending_timeout_tail, | ||
2020 | pos); | ||
2021 | if (pending_timeout_last == pos) | ||
2022 | pending_timeout_last = NULL; | ||
2023 | queue_ready_task (pos); | ||
2024 | pos = next; | ||
2025 | } | ||
2026 | pos = pending_head; | ||
2027 | while (NULL != pos) | ||
2028 | { | ||
2029 | struct GNUNET_SCHEDULER_Task *next = pos->next; | ||
2030 | if (now.abs_value_us >= pos->timeout.abs_value_us) | ||
2031 | { | ||
2032 | pos->reason |= GNUNET_SCHEDULER_REASON_TIMEOUT; | ||
2033 | GNUNET_CONTAINER_DLL_remove (pending_head, | ||
2034 | pending_tail, | ||
2035 | pos); | ||
2036 | queue_ready_task (pos); | ||
2037 | } | ||
2038 | pos = next; | ||
2039 | } | ||
2040 | |||
2041 | if (0 == ready_count) | ||
2042 | { | ||
2043 | struct GNUNET_TIME_Absolute timeout = get_timeout (); | ||
2044 | |||
2045 | if (timeout.abs_value_us > now.abs_value_us) | ||
2046 | { | ||
2047 | /** | ||
2048 | * The event loop called this function before the current timeout was | ||
2049 | * reached (and no FD tasks are ready). This is acceptable if | ||
2050 | * | ||
2051 | * - the system time was changed while the driver was waiting for | ||
2052 | * the timeout | ||
2053 | * - an external event loop called GNUnet API functions outside of | ||
2054 | * the callbacks called in GNUNET_SCHEDULER_do_work and thus | ||
2055 | * wasn't notified about the new timeout | ||
2056 | * | ||
2057 | * It might also mean we are busy-waiting because of a programming | ||
2058 | * error in the external event loop. | ||
2059 | */LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
2060 | "GNUNET_SCHEDULER_do_work did not find any ready " | ||
2061 | "tasks and timeout has not been reached yet.\n"); | ||
2062 | } | ||
2063 | else | ||
2064 | { | ||
2065 | /** | ||
2066 | * the current timeout was reached but no ready tasks were found, | ||
2067 | * internal scheduler error! | ||
2068 | */ | ||
2069 | GNUNET_assert (0); | ||
2070 | } | ||
2071 | } | ||
2072 | else | ||
2073 | { | ||
2074 | /* find out which task priority level we are going to | ||
2075 | process this time */ | ||
2076 | max_priority_added = GNUNET_SCHEDULER_PRIORITY_KEEP; | ||
2077 | GNUNET_assert (NULL == ready_head[GNUNET_SCHEDULER_PRIORITY_KEEP]); | ||
2078 | /* yes, p>0 is correct, 0 is "KEEP" which should | ||
2079 | * always be an empty queue (see assertion)! */ | ||
2080 | for (work_priority = GNUNET_SCHEDULER_PRIORITY_COUNT - 1; | ||
2081 | work_priority > 0; | ||
2082 | work_priority--) | ||
2083 | { | ||
2084 | pos = ready_head[work_priority]; | ||
2085 | if (NULL != pos) | ||
2086 | break; | ||
2087 | } | ||
2088 | GNUNET_assert (NULL != pos); /* ready_count wrong? */ | ||
2089 | |||
2090 | /* process all tasks at this priority level, then yield */ | ||
2091 | while (NULL != (pos = ready_head[work_priority])) | ||
2092 | { | ||
2093 | GNUNET_CONTAINER_DLL_remove (ready_head[work_priority], | ||
2094 | ready_tail[work_priority], | ||
2095 | pos); | ||
2096 | ready_count--; | ||
2097 | current_priority = pos->priority; | ||
2098 | current_lifeness = pos->lifeness; | ||
2099 | active_task = pos; | ||
2100 | #if PROFILE_DELAYS | ||
2101 | if (GNUNET_TIME_absolute_get_duration (pos->start_time).rel_value_us > | ||
2102 | DELAY_THRESHOLD.rel_value_us) | ||
2103 | { | ||
2104 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
2105 | "Task %p took %s to be scheduled\n", | ||
2106 | pos, | ||
2107 | GNUNET_STRINGS_relative_time_to_string ( | ||
2108 | GNUNET_TIME_absolute_get_duration (pos->start_time), | ||
2109 | GNUNET_YES)); | ||
2110 | } | ||
2111 | #endif | ||
2112 | tc.reason = pos->reason; | ||
2113 | GNUNET_NETWORK_fdset_zero (sh->rs); | ||
2114 | GNUNET_NETWORK_fdset_zero (sh->ws); | ||
2115 | // FIXME: do we have to remove FdInfos from fds if they are not ready? | ||
2116 | tc.fds_len = pos->fds_len; | ||
2117 | tc.fds = pos->fds; | ||
2118 | for (unsigned int i = 0; i != pos->fds_len; ++i) | ||
2119 | { | ||
2120 | struct GNUNET_SCHEDULER_FdInfo *fdi = &pos->fds[i]; | ||
2121 | if (0 != (GNUNET_SCHEDULER_ET_IN & fdi->et)) | ||
2122 | { | ||
2123 | GNUNET_NETWORK_fdset_set_native (sh->rs, | ||
2124 | fdi->sock); | ||
2125 | } | ||
2126 | if (0 != (GNUNET_SCHEDULER_ET_OUT & fdi->et)) | ||
2127 | { | ||
2128 | GNUNET_NETWORK_fdset_set_native (sh->ws, | ||
2129 | fdi->sock); | ||
2130 | } | ||
2131 | } | ||
2132 | tc.read_ready = sh->rs; | ||
2133 | tc.write_ready = sh->ws; | ||
2134 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
2135 | "Running task %p\n", | ||
2136 | pos); | ||
2137 | GNUNET_assert (NULL != pos->callback); | ||
2138 | { | ||
2139 | struct GNUNET_AsyncScopeSave old_scope; | ||
2140 | if (pos->scope.have_scope) | ||
2141 | GNUNET_async_scope_enter (&pos->scope.scope_id, &old_scope); | ||
2142 | else | ||
2143 | GNUNET_async_scope_get (&old_scope); | ||
2144 | pos->callback (pos->callback_cls); | ||
2145 | GNUNET_async_scope_restore (&old_scope); | ||
2146 | } | ||
2147 | if (NULL != pos->fds) | ||
2148 | { | ||
2149 | int del_result = scheduler_driver->del (scheduler_driver->cls, pos); | ||
2150 | if (GNUNET_OK != del_result) | ||
2151 | { | ||
2152 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
2153 | "driver could not delete task %p\n", pos); | ||
2154 | GNUNET_assert (0); | ||
2155 | } | ||
2156 | } | ||
2157 | active_task = NULL; | ||
2158 | dump_backtrace (pos); | ||
2159 | destroy_task (pos); | ||
2160 | } | ||
2161 | } | ||
2162 | shutdown_if_no_lifeness (); | ||
2163 | if (0 == ready_count) | ||
2164 | { | ||
2165 | scheduler_driver->set_wakeup (scheduler_driver->cls, | ||
2166 | get_timeout ()); | ||
2167 | return GNUNET_NO; | ||
2168 | } | ||
2169 | scheduler_driver->set_wakeup (scheduler_driver->cls, | ||
2170 | GNUNET_TIME_absolute_get ()); | ||
2171 | return GNUNET_YES; | ||
2172 | } | ||
2173 | |||
2174 | |||
2175 | /** | ||
2176 | * Function called by external event loop implementations to initialize | ||
2177 | * the scheduler. An external implementation has to provide @a driver | ||
2178 | * which contains callbacks for the scheduler (see definition of struct | ||
2179 | * #GNUNET_SCHEDULER_Driver). The callbacks are used to instruct the | ||
2180 | * external implementation to watch for events. If it detects any of | ||
2181 | * those events it is expected to call #GNUNET_SCHEDULER_do_work to let | ||
2182 | * the scheduler handle it. If an event is related to a specific task | ||
2183 | * (e.g. the scheduler gave instructions to watch a file descriptor), | ||
2184 | * the external implementation is expected to mark that task ready | ||
2185 | * before by calling #GNUNET_SCHEDULER_task_ready. | ||
2186 | |||
2187 | * This function has to be called before any tasks are scheduled and | ||
2188 | * before GNUNET_SCHEDULER_do_work is called for the first time. It | ||
2189 | * allocates resources that have to be freed again by calling | ||
2190 | * #GNUNET_SCHEDULER_driver_done. | ||
2191 | * | ||
2192 | * This function installs the same signal handlers as | ||
2193 | * #GNUNET_SCHEDULER_run. This means SIGTERM (and other similar signals) | ||
2194 | * will induce a call to #GNUNET_SCHEDULER_shutdown during the next | ||
2195 | * call to #GNUNET_SCHEDULER_do_work. As a result, SIGTERM causes all | ||
2196 | * active tasks to be scheduled with reason | ||
2197 | * #GNUNET_SCHEDULER_REASON_SHUTDOWN. (However, tasks added afterwards | ||
2198 | * will execute normally!). Note that any particular signal will only | ||
2199 | * shut down one scheduler; applications should always only create a | ||
2200 | * single scheduler. | ||
2201 | * | ||
2202 | * @param driver to use for the event loop | ||
2203 | * @return handle to be passed to #GNUNET_SCHEDULER_do_work and | ||
2204 | * #GNUNET_SCHEDULER_driver_done | ||
2205 | */ | ||
2206 | struct GNUNET_SCHEDULER_Handle * | ||
2207 | GNUNET_SCHEDULER_driver_init (const struct GNUNET_SCHEDULER_Driver *driver) | ||
2208 | { | ||
2209 | struct GNUNET_SCHEDULER_Handle *sh; | ||
2210 | const struct GNUNET_DISK_FileHandle *pr; | ||
2211 | |||
2212 | /* scheduler must not be running */ | ||
2213 | GNUNET_assert (NULL == scheduler_driver); | ||
2214 | GNUNET_assert (NULL == shutdown_pipe_handle); | ||
2215 | /* general set-up */ | ||
2216 | sh = GNUNET_new (struct GNUNET_SCHEDULER_Handle); | ||
2217 | shutdown_pipe_handle = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE); | ||
2218 | GNUNET_assert (NULL != shutdown_pipe_handle); | ||
2219 | pr = GNUNET_DISK_pipe_handle (shutdown_pipe_handle, | ||
2220 | GNUNET_DISK_PIPE_END_READ); | ||
2221 | my_pid = getpid (); | ||
2222 | scheduler_driver = driver; | ||
2223 | |||
2224 | /* install signal handlers */ | ||
2225 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
2226 | "Registering signal handlers\n"); | ||
2227 | sh->shc_int = GNUNET_SIGNAL_handler_install (SIGINT, | ||
2228 | &sighandler_shutdown); | ||
2229 | sh->shc_term = GNUNET_SIGNAL_handler_install (SIGTERM, | ||
2230 | &sighandler_shutdown); | ||
2231 | #if (SIGTERM != GNUNET_TERM_SIG) | ||
2232 | sh->shc_gterm = GNUNET_SIGNAL_handler_install (GNUNET_TERM_SIG, | ||
2233 | &sighandler_shutdown); | ||
2234 | #endif | ||
2235 | sh->shc_pipe = GNUNET_SIGNAL_handler_install (SIGPIPE, | ||
2236 | &sighandler_pipe); | ||
2237 | sh->shc_quit = GNUNET_SIGNAL_handler_install (SIGQUIT, | ||
2238 | &sighandler_shutdown); | ||
2239 | sh->shc_hup = GNUNET_SIGNAL_handler_install (SIGHUP, | ||
2240 | &sighandler_shutdown); | ||
2241 | |||
2242 | /* Setup initial tasks */ | ||
2243 | current_priority = GNUNET_SCHEDULER_PRIORITY_DEFAULT; | ||
2244 | current_lifeness = GNUNET_NO; | ||
2245 | /* ensure this task runs first, by using a priority level reserved for | ||
2246 | the scheduler (not really shutdown, but start-up ;-) */ | ||
2247 | install_parent_control_task = | ||
2248 | GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_SHUTDOWN, | ||
2249 | &install_parent_control_handler, | ||
2250 | NULL); | ||
2251 | shutdown_pipe_task = | ||
2252 | GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, | ||
2253 | pr, | ||
2254 | &shutdown_pipe_cb, | ||
2255 | NULL); | ||
2256 | current_lifeness = GNUNET_YES; | ||
2257 | scheduler_driver->set_wakeup (scheduler_driver->cls, | ||
2258 | get_timeout ()); | ||
2259 | /* begin main event loop */ | ||
2260 | sh->rs = GNUNET_NETWORK_fdset_create (); | ||
2261 | sh->ws = GNUNET_NETWORK_fdset_create (); | ||
2262 | GNUNET_NETWORK_fdset_handle_set (sh->rs, pr); | ||
2263 | return sh; | ||
2264 | } | ||
2265 | |||
2266 | |||
2267 | /** | ||
2268 | * Counter-part of #GNUNET_SCHEDULER_driver_init. Has to be called | ||
2269 | * by external event loop implementations after the scheduler has | ||
2270 | * shut down. This is the case if both of the following conditions | ||
2271 | * are met: | ||
2272 | * | ||
2273 | * - all tasks the scheduler has added through the driver's add | ||
2274 | * callback have been removed again through the driver's del | ||
2275 | * callback | ||
2276 | * - the timeout the scheduler has set through the driver's | ||
2277 | * add_wakeup callback is FOREVER | ||
2278 | * | ||
2279 | * @param sh the handle returned by #GNUNET_SCHEDULER_driver_init | ||
2280 | */ | ||
2281 | void | ||
2282 | GNUNET_SCHEDULER_driver_done (struct GNUNET_SCHEDULER_Handle *sh) | ||
2283 | { | ||
2284 | GNUNET_assert (NULL == pending_head); | ||
2285 | GNUNET_assert (NULL == pending_timeout_head); | ||
2286 | GNUNET_assert (NULL == shutdown_head); | ||
2287 | for (int i = 0; i != GNUNET_SCHEDULER_PRIORITY_COUNT; ++i) | ||
2288 | { | ||
2289 | GNUNET_assert (NULL == ready_head[i]); | ||
2290 | } | ||
2291 | GNUNET_NETWORK_fdset_destroy (sh->rs); | ||
2292 | GNUNET_NETWORK_fdset_destroy (sh->ws); | ||
2293 | |||
2294 | /* uninstall signal handlers */ | ||
2295 | GNUNET_SIGNAL_handler_uninstall (sh->shc_int); | ||
2296 | GNUNET_SIGNAL_handler_uninstall (sh->shc_term); | ||
2297 | #if (SIGTERM != GNUNET_TERM_SIG) | ||
2298 | GNUNET_SIGNAL_handler_uninstall (sh->shc_gterm); | ||
2299 | #endif | ||
2300 | GNUNET_SIGNAL_handler_uninstall (sh->shc_pipe); | ||
2301 | GNUNET_SIGNAL_handler_uninstall (sh->shc_quit); | ||
2302 | GNUNET_SIGNAL_handler_uninstall (sh->shc_hup); | ||
2303 | GNUNET_DISK_pipe_close (shutdown_pipe_handle); | ||
2304 | shutdown_pipe_handle = NULL; | ||
2305 | scheduler_driver = NULL; | ||
2306 | GNUNET_free (sh); | ||
2307 | } | ||
2308 | |||
2309 | |||
2310 | static int | ||
2311 | select_loop (struct GNUNET_SCHEDULER_Handle *sh, | ||
2312 | struct DriverContext *context) | ||
2313 | { | ||
2314 | struct GNUNET_NETWORK_FDSet *rs; | ||
2315 | struct GNUNET_NETWORK_FDSet *ws; | ||
2316 | int select_result; | ||
2317 | |||
2318 | GNUNET_assert (NULL != context); | ||
2319 | rs = GNUNET_NETWORK_fdset_create (); | ||
2320 | ws = GNUNET_NETWORK_fdset_create (); | ||
2321 | while ((NULL != context->scheduled_head) || | ||
2322 | (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != | ||
2323 | context->timeout.abs_value_us)) | ||
2324 | { | ||
2325 | struct GNUNET_TIME_Relative time_remaining; | ||
2326 | |||
2327 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
2328 | "select timeout = %s\n", | ||
2329 | GNUNET_STRINGS_absolute_time_to_string (context->timeout)); | ||
2330 | |||
2331 | GNUNET_NETWORK_fdset_zero (rs); | ||
2332 | GNUNET_NETWORK_fdset_zero (ws); | ||
2333 | |||
2334 | for (struct Scheduled *pos = context->scheduled_head; | ||
2335 | NULL != pos; | ||
2336 | pos = pos->next) | ||
2337 | { | ||
2338 | if (0 != (GNUNET_SCHEDULER_ET_IN & pos->et)) | ||
2339 | { | ||
2340 | GNUNET_NETWORK_fdset_set_native (rs, pos->fdi->sock); | ||
2341 | } | ||
2342 | if (0 != (GNUNET_SCHEDULER_ET_OUT & pos->et)) | ||
2343 | { | ||
2344 | GNUNET_NETWORK_fdset_set_native (ws, pos->fdi->sock); | ||
2345 | } | ||
2346 | } | ||
2347 | time_remaining = GNUNET_TIME_absolute_get_remaining (context->timeout); | ||
2348 | if (0 < ready_count) | ||
2349 | time_remaining = GNUNET_TIME_UNIT_ZERO; | ||
2350 | if (NULL == scheduler_select) | ||
2351 | { | ||
2352 | select_result = GNUNET_NETWORK_socket_select (rs, | ||
2353 | ws, | ||
2354 | NULL, | ||
2355 | time_remaining); | ||
2356 | } | ||
2357 | else | ||
2358 | { | ||
2359 | select_result = scheduler_select (scheduler_select_cls, | ||
2360 | rs, | ||
2361 | ws, | ||
2362 | NULL, | ||
2363 | time_remaining); | ||
2364 | } | ||
2365 | if (select_result == GNUNET_SYSERR) | ||
2366 | { | ||
2367 | if (errno == EINTR) | ||
2368 | continue; | ||
2369 | |||
2370 | LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, | ||
2371 | "select"); | ||
2372 | #if USE_LSOF | ||
2373 | char lsof[512]; | ||
2374 | |||
2375 | snprintf (lsof, | ||
2376 | sizeof(lsof), | ||
2377 | "lsof -p %d", | ||
2378 | getpid ()); | ||
2379 | (void) close (1); | ||
2380 | (void) dup2 (2, 1); | ||
2381 | if (0 != system (lsof)) | ||
2382 | LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, | ||
2383 | "system"); | ||
2384 | #endif | ||
2385 | #if DEBUG_FDS | ||
2386 | for (struct Scheduled *s = context->scheduled_head; | ||
2387 | NULL != s; | ||
2388 | s = s->next) | ||
2389 | { | ||
2390 | int flags = fcntl (s->fdi->sock, | ||
2391 | F_GETFD); | ||
2392 | |||
2393 | if ((flags == -1) && | ||
2394 | (EBADF == errno)) | ||
2395 | { | ||
2396 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
2397 | "Got invalid file descriptor %d!\n", | ||
2398 | s->fdi->sock); | ||
2399 | #if EXECINFO | ||
2400 | dump_backtrace (s->task); | ||
2401 | #endif | ||
2402 | } | ||
2403 | } | ||
2404 | #endif | ||
2405 | GNUNET_assert (0); | ||
2406 | GNUNET_NETWORK_fdset_destroy (rs); | ||
2407 | GNUNET_NETWORK_fdset_destroy (ws); | ||
2408 | return GNUNET_SYSERR; | ||
2409 | } | ||
2410 | if (select_result > 0) | ||
2411 | { | ||
2412 | for (struct Scheduled *pos = context->scheduled_head; | ||
2413 | NULL != pos; | ||
2414 | pos = pos->next) | ||
2415 | { | ||
2416 | int is_ready = GNUNET_NO; | ||
2417 | |||
2418 | if ((0 != (GNUNET_SCHEDULER_ET_IN & pos->et)) && | ||
2419 | (GNUNET_YES == | ||
2420 | GNUNET_NETWORK_fdset_test_native (rs, | ||
2421 | pos->fdi->sock)) ) | ||
2422 | { | ||
2423 | pos->fdi->et |= GNUNET_SCHEDULER_ET_IN; | ||
2424 | is_ready = GNUNET_YES; | ||
2425 | } | ||
2426 | if ((0 != (GNUNET_SCHEDULER_ET_OUT & pos->et)) && | ||
2427 | (GNUNET_YES == | ||
2428 | GNUNET_NETWORK_fdset_test_native (ws, | ||
2429 | pos->fdi->sock)) ) | ||
2430 | { | ||
2431 | pos->fdi->et |= GNUNET_SCHEDULER_ET_OUT; | ||
2432 | is_ready = GNUNET_YES; | ||
2433 | } | ||
2434 | if (GNUNET_YES == is_ready) | ||
2435 | { | ||
2436 | GNUNET_SCHEDULER_task_ready (pos->task, | ||
2437 | pos->fdi); | ||
2438 | } | ||
2439 | } | ||
2440 | } | ||
2441 | if (GNUNET_YES == GNUNET_SCHEDULER_do_work (sh)) | ||
2442 | { | ||
2443 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
2444 | "scheduler has more tasks ready!\n"); | ||
2445 | } | ||
2446 | } | ||
2447 | GNUNET_NETWORK_fdset_destroy (rs); | ||
2448 | GNUNET_NETWORK_fdset_destroy (ws); | ||
2449 | return GNUNET_OK; | ||
2450 | } | ||
2451 | |||
2452 | |||
2453 | static int | ||
2454 | select_add (void *cls, | ||
2455 | struct GNUNET_SCHEDULER_Task *task, | ||
2456 | struct GNUNET_SCHEDULER_FdInfo *fdi) | ||
2457 | { | ||
2458 | struct DriverContext *context = cls; | ||
2459 | |||
2460 | GNUNET_assert (NULL != context); | ||
2461 | GNUNET_assert (NULL != task); | ||
2462 | GNUNET_assert (NULL != fdi); | ||
2463 | GNUNET_assert (0 != (GNUNET_SCHEDULER_ET_IN & fdi->et) || | ||
2464 | 0 != (GNUNET_SCHEDULER_ET_OUT & fdi->et)); | ||
2465 | |||
2466 | if (! ((NULL != fdi->fd) ^ (NULL != fdi->fh)) || (fdi->sock < 0)) | ||
2467 | { | ||
2468 | /* exactly one out of {fd, hf} must be != NULL and the OS handle must be valid */ | ||
2469 | return GNUNET_SYSERR; | ||
2470 | } | ||
2471 | |||
2472 | struct Scheduled *scheduled = GNUNET_new (struct Scheduled); | ||
2473 | scheduled->task = task; | ||
2474 | scheduled->fdi = fdi; | ||
2475 | scheduled->et = fdi->et; | ||
2476 | |||
2477 | GNUNET_CONTAINER_DLL_insert (context->scheduled_head, | ||
2478 | context->scheduled_tail, | ||
2479 | scheduled); | ||
2480 | return GNUNET_OK; | ||
2481 | } | ||
2482 | |||
2483 | |||
2484 | static int | ||
2485 | select_del (void *cls, | ||
2486 | struct GNUNET_SCHEDULER_Task *task) | ||
2487 | { | ||
2488 | struct DriverContext *context; | ||
2489 | struct Scheduled *pos; | ||
2490 | int ret; | ||
2491 | |||
2492 | GNUNET_assert (NULL != cls); | ||
2493 | |||
2494 | context = cls; | ||
2495 | ret = GNUNET_SYSERR; | ||
2496 | pos = context->scheduled_head; | ||
2497 | while (NULL != pos) | ||
2498 | { | ||
2499 | struct Scheduled *next = pos->next; | ||
2500 | if (pos->task == task) | ||
2501 | { | ||
2502 | GNUNET_CONTAINER_DLL_remove (context->scheduled_head, | ||
2503 | context->scheduled_tail, | ||
2504 | pos); | ||
2505 | GNUNET_free (pos); | ||
2506 | ret = GNUNET_OK; | ||
2507 | } | ||
2508 | pos = next; | ||
2509 | } | ||
2510 | return ret; | ||
2511 | } | ||
2512 | |||
2513 | |||
2514 | static void | ||
2515 | select_set_wakeup (void *cls, | ||
2516 | struct GNUNET_TIME_Absolute dt) | ||
2517 | { | ||
2518 | struct DriverContext *context = cls; | ||
2519 | |||
2520 | GNUNET_assert (NULL != context); | ||
2521 | context->timeout = dt; | ||
2522 | } | ||
2523 | |||
2524 | |||
2525 | /** | ||
2526 | * Obtain the driver for using select() as the event loop. | ||
2527 | * | ||
2528 | * @return NULL on error | ||
2529 | */ | ||
2530 | struct GNUNET_SCHEDULER_Driver * | ||
2531 | GNUNET_SCHEDULER_driver_select () | ||
2532 | { | ||
2533 | struct GNUNET_SCHEDULER_Driver *select_driver; | ||
2534 | |||
2535 | select_driver = GNUNET_new (struct GNUNET_SCHEDULER_Driver); | ||
2536 | |||
2537 | select_driver->add = &select_add; | ||
2538 | select_driver->del = &select_del; | ||
2539 | select_driver->set_wakeup = &select_set_wakeup; | ||
2540 | |||
2541 | return select_driver; | ||
2542 | } | ||
2543 | |||
2544 | |||
2545 | /** | ||
2546 | * Change the async scope for the currently executing task and (transitively) | ||
2547 | * for all tasks scheduled by the current task after calling this function. | ||
2548 | * Nested tasks can begin their own nested async scope. | ||
2549 | * | ||
2550 | * Once the current task is finished, the async scope ID is reset to | ||
2551 | * its previous value. | ||
2552 | * | ||
2553 | * Must only be called from a running task. | ||
2554 | * | ||
2555 | * @param aid the asynchronous scope id to enter | ||
2556 | */ | ||
2557 | void | ||
2558 | GNUNET_SCHEDULER_begin_async_scope (struct GNUNET_AsyncScopeId *aid) | ||
2559 | { | ||
2560 | struct GNUNET_AsyncScopeSave dummy_old_scope; | ||
2561 | |||
2562 | GNUNET_assert (NULL != active_task); | ||
2563 | /* Since we're in a task, the context will be automatically | ||
2564 | restored by the scheduler. */ | ||
2565 | GNUNET_async_scope_enter (aid, &dummy_old_scope); | ||
2566 | } | ||
2567 | |||
2568 | |||
2569 | /* end of scheduler.c */ | ||