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