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