diff options
author | Christian Grothoff <christian@grothoff.org> | 2017-03-27 11:44:46 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2017-03-28 02:27:25 +0200 |
commit | d486f5da0f495d817a19efd077501c25e4eef6fa (patch) | |
tree | bb785996088c46ed38012b23698d6354f8da7cfe /src/util/scheduler.c | |
parent | 802f89d2979d3a92228543d42cee6bb8e3e786d1 (diff) | |
download | gnunet-d486f5da0f495d817a19efd077501c25e4eef6fa.tar.gz gnunet-d486f5da0f495d817a19efd077501c25e4eef6fa.zip |
initial ideas for improving the scheduler API (far from finished)
Diffstat (limited to 'src/util/scheduler.c')
-rw-r--r-- | src/util/scheduler.c | 328 |
1 files changed, 325 insertions, 3 deletions
diff --git a/src/util/scheduler.c b/src/util/scheduler.c index 409a0942f..a7b1d8e2a 100644 --- a/src/util/scheduler.c +++ b/src/util/scheduler.c | |||
@@ -1,6 +1,6 @@ | |||
1 | /* | 1 | /* |
2 | This file is part of GNUnet | 2 | This file is part of GNUnet |
3 | Copyright (C) 2009-2016 GNUnet e.V. | 3 | Copyright (C) 2009-2017 GNUnet e.V. |
4 | 4 | ||
5 | GNUnet is free software; you can redistribute it and/or modify | 5 | GNUnet is free software; you can redistribute it and/or modify |
6 | it under the terms of the GNU General Public License as published | 6 | it under the terms of the GNU General Public License as published |
@@ -17,7 +17,6 @@ | |||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
18 | Boston, MA 02110-1301, USA. | 18 | Boston, MA 02110-1301, USA. |
19 | */ | 19 | */ |
20 | |||
21 | /** | 20 | /** |
22 | * @file util/scheduler.c | 21 | * @file util/scheduler.c |
23 | * @brief schedule computations using continuation passing style | 22 | * @brief schedule computations using continuation passing style |
@@ -71,6 +70,35 @@ | |||
71 | 70 | ||
72 | 71 | ||
73 | /** | 72 | /** |
73 | * Argument to be passed from the driver to | ||
74 | * #GNUNET_SCHEDULER_run_from_driver(). Contains the | ||
75 | * scheduler's internal state. | ||
76 | */ | ||
77 | struct GNUNET_SCHEDULER_Handle | ||
78 | { | ||
79 | /** | ||
80 | * Passed here to avoid constantly allocating/deallocating | ||
81 | * this element, but generally we want to get rid of this. | ||
82 | * @deprecated | ||
83 | */ | ||
84 | struct GNUNET_NETWORK_FDSet *rs; | ||
85 | |||
86 | /** | ||
87 | * Passed here to avoid constantly allocating/deallocating | ||
88 | * this element, but generally we want to get rid of this. | ||
89 | * @deprecated | ||
90 | */ | ||
91 | struct GNUNET_NETWORK_FDSet *ws; | ||
92 | |||
93 | /** | ||
94 | * Driver we used for the event loop. | ||
95 | */ | ||
96 | const struct GNUNET_SCHEDULER_Driver *driver; | ||
97 | |||
98 | }; | ||
99 | |||
100 | |||
101 | /** | ||
74 | * Entry in list of pending tasks. | 102 | * Entry in list of pending tasks. |
75 | */ | 103 | */ |
76 | struct GNUNET_SCHEDULER_Task | 104 | struct GNUNET_SCHEDULER_Task |
@@ -96,6 +124,11 @@ struct GNUNET_SCHEDULER_Task | |||
96 | void *callback_cls; | 124 | void *callback_cls; |
97 | 125 | ||
98 | /** | 126 | /** |
127 | * Handle to the scheduler's state. | ||
128 | */ | ||
129 | const struct GNUNET_SCHEDULER_Handle *sh; | ||
130 | |||
131 | /** | ||
99 | * Set of file descriptors this task is waiting | 132 | * Set of file descriptors this task is waiting |
100 | * for for reading. Once ready, this is updated | 133 | * for for reading. Once ready, this is updated |
101 | * to reflect the set of file descriptors ready | 134 | * to reflect the set of file descriptors ready |
@@ -111,6 +144,18 @@ struct GNUNET_SCHEDULER_Task | |||
111 | struct GNUNET_NETWORK_FDSet *write_set; | 144 | struct GNUNET_NETWORK_FDSet *write_set; |
112 | 145 | ||
113 | /** | 146 | /** |
147 | * Information about which FDs are ready for this task (and why). | ||
148 | */ | ||
149 | const struct GNUNET_SCHEDULER_FdInfo *fds; | ||
150 | |||
151 | /** | ||
152 | * Storage location used for @e fds if we want to avoid | ||
153 | * a separate malloc() call in the common case that this | ||
154 | * task is only about a single FD. | ||
155 | */ | ||
156 | struct GNUNET_SCHEDULER_FdInfo fdx; | ||
157 | |||
158 | /** | ||
114 | * Absolute timeout value for the task, or | 159 | * Absolute timeout value for the task, or |
115 | * #GNUNET_TIME_UNIT_FOREVER_ABS for "no timeout". | 160 | * #GNUNET_TIME_UNIT_FOREVER_ABS for "no timeout". |
116 | */ | 161 | */ |
@@ -124,6 +169,11 @@ struct GNUNET_SCHEDULER_Task | |||
124 | #endif | 169 | #endif |
125 | 170 | ||
126 | /** | 171 | /** |
172 | * Size of the @e fds array. | ||
173 | */ | ||
174 | unsigned int fds_len; | ||
175 | |||
176 | /** | ||
127 | * Why is the task ready? Set after task is added to ready queue. | 177 | * Why is the task ready? Set after task is added to ready queue. |
128 | * Initially set to zero. All reasons that have already been | 178 | * Initially set to zero. All reasons that have already been |
129 | * satisfied (i.e. read or write ready) will be set over time. | 179 | * satisfied (i.e. read or write ready) will be set over time. |
@@ -1742,7 +1792,8 @@ GNUNET_SCHEDULER_add_select (enum GNUNET_SCHEDULER_Priority prio, | |||
1742 | GNUNET_CONTAINER_DLL_insert (pending_head, | 1792 | GNUNET_CONTAINER_DLL_insert (pending_head, |
1743 | pending_tail, | 1793 | pending_tail, |
1744 | t); | 1794 | t); |
1745 | max_priority_added = GNUNET_MAX (max_priority_added, t->priority); | 1795 | max_priority_added = GNUNET_MAX (max_priority_added, |
1796 | t->priority); | ||
1746 | LOG (GNUNET_ERROR_TYPE_DEBUG, | 1797 | LOG (GNUNET_ERROR_TYPE_DEBUG, |
1747 | "Adding task %p\n", | 1798 | "Adding task %p\n", |
1748 | t); | 1799 | t); |
@@ -1750,4 +1801,275 @@ GNUNET_SCHEDULER_add_select (enum GNUNET_SCHEDULER_Priority prio, | |||
1750 | return t; | 1801 | return t; |
1751 | } | 1802 | } |
1752 | 1803 | ||
1804 | |||
1805 | /** | ||
1806 | * Function used by event-loop implementations to signal the scheduler | ||
1807 | * that a particular @a task is ready due to an event of type @a et. | ||
1808 | * | ||
1809 | * This function will then queue the task to notify the application | ||
1810 | * that the task is ready (with the respective priority). | ||
1811 | * | ||
1812 | * @param task the task that is ready, NULL for wake up calls | ||
1813 | * @param et information about why the task is ready | ||
1814 | */ | ||
1815 | void | ||
1816 | GNUNET_SCHEDULER_task_ready (struct GNUNET_SCHEDULER_Task *task, | ||
1817 | enum GNUNET_SCHEDULER_EventType et) | ||
1818 | { | ||
1819 | enum GNUNET_SCHEDULER_Reason reason; | ||
1820 | struct GNUNET_TIME_Absolute now; | ||
1821 | |||
1822 | now = GNUNET_TIME_absolute_get (); | ||
1823 | reason = task->reason; | ||
1824 | if (now.abs_value_us >= task->timeout.abs_value_us) | ||
1825 | reason |= GNUNET_SCHEDULER_REASON_TIMEOUT; | ||
1826 | if ( (0 == (reason & GNUNET_SCHEDULER_REASON_READ_READY)) && | ||
1827 | (0 != (GNUNET_SCHEDULER_ET_IN & et)) ) | ||
1828 | reason |= GNUNET_SCHEDULER_REASON_READ_READY; | ||
1829 | if ( (0 == (reason & GNUNET_SCHEDULER_REASON_WRITE_READY)) && | ||
1830 | (0 != (GNUNET_SCHEDULER_ET_OUT & et)) ) | ||
1831 | reason |= GNUNET_SCHEDULER_REASON_WRITE_READY; | ||
1832 | reason |= GNUNET_SCHEDULER_REASON_PREREQ_DONE; | ||
1833 | task->reason = reason; | ||
1834 | task->fds = &task->fdx; | ||
1835 | task->fdx.et = et; | ||
1836 | task->fds_len = 1; | ||
1837 | queue_ready_task (task); | ||
1838 | } | ||
1839 | |||
1840 | |||
1841 | /** | ||
1842 | * Function called by the driver to tell the scheduler to run some of | ||
1843 | * the tasks that are ready. This function may return even though | ||
1844 | * there are tasks left to run just to give other tasks a chance as | ||
1845 | * well. If we return #GNUNET_YES, the driver should call this | ||
1846 | * function again as soon as possible, while if we return #GNUNET_NO | ||
1847 | * it must block until the operating system has more work as the | ||
1848 | * scheduler has no more work to do right now. | ||
1849 | * | ||
1850 | * @param sh scheduler handle that was given to the `loop` | ||
1851 | * @return #GNUNET_OK if there are more tasks that are ready, | ||
1852 | * and thus we would like to run more (yield to avoid | ||
1853 | * blocking other activities for too long) | ||
1854 | * #GNUNET_NO if we are done running tasks (yield to block) | ||
1855 | * #GNUNET_SYSERR on error | ||
1856 | */ | ||
1857 | int | ||
1858 | GNUNET_SCHEDULER_run_from_driver (struct GNUNET_SCHEDULER_Handle *sh) | ||
1859 | { | ||
1860 | enum GNUNET_SCHEDULER_Priority p; | ||
1861 | struct GNUNET_SCHEDULER_Task *pos; | ||
1862 | struct GNUNET_TIME_Absolute now; | ||
1863 | |||
1864 | /* check for tasks that reached the timeout! */ | ||
1865 | now = GNUNET_TIME_absolute_get (); | ||
1866 | while (NULL != (pos = pending_timeout_head)) | ||
1867 | { | ||
1868 | if (now.abs_value_us >= pos->timeout.abs_value_us) | ||
1869 | pos->reason |= GNUNET_SCHEDULER_REASON_TIMEOUT; | ||
1870 | if (0 == pos->reason) | ||
1871 | break; | ||
1872 | GNUNET_CONTAINER_DLL_remove (pending_timeout_head, | ||
1873 | pending_timeout_tail, | ||
1874 | pos); | ||
1875 | if (pending_timeout_last == pos) | ||
1876 | pending_timeout_last = NULL; | ||
1877 | queue_ready_task (pos); | ||
1878 | } | ||
1879 | |||
1880 | if (0 == ready_count) | ||
1881 | return GNUNET_NO; | ||
1882 | |||
1883 | /* find out which task priority level we are going to | ||
1884 | process this time */ | ||
1885 | max_priority_added = GNUNET_SCHEDULER_PRIORITY_KEEP; | ||
1886 | GNUNET_assert (NULL == ready_head[GNUNET_SCHEDULER_PRIORITY_KEEP]); | ||
1887 | /* yes, p>0 is correct, 0 is "KEEP" which should | ||
1888 | * always be an empty queue (see assertion)! */ | ||
1889 | for (p = GNUNET_SCHEDULER_PRIORITY_COUNT - 1; p > 0; p--) | ||
1890 | { | ||
1891 | pos = ready_head[p]; | ||
1892 | if (NULL != pos) | ||
1893 | break; | ||
1894 | } | ||
1895 | GNUNET_assert (NULL != pos); /* ready_count wrong? */ | ||
1896 | |||
1897 | /* process all tasks at this priority level, then yield */ | ||
1898 | while (NULL != (pos = ready_head[p])) | ||
1899 | { | ||
1900 | GNUNET_CONTAINER_DLL_remove (ready_head[p], | ||
1901 | ready_tail[p], | ||
1902 | pos); | ||
1903 | ready_count--; | ||
1904 | current_priority = pos->priority; | ||
1905 | current_lifeness = pos->lifeness; | ||
1906 | active_task = pos; | ||
1907 | #if PROFILE_DELAYS | ||
1908 | if (GNUNET_TIME_absolute_get_duration (pos->start_time).rel_value_us > | ||
1909 | DELAY_THRESHOLD.rel_value_us) | ||
1910 | { | ||
1911 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
1912 | "Task %p took %s to be scheduled\n", | ||
1913 | pos, | ||
1914 | GNUNET_STRINGS_relative_time_to_string (GNUNET_TIME_absolute_get_duration (pos->start_time), | ||
1915 | GNUNET_YES)); | ||
1916 | } | ||
1917 | #endif | ||
1918 | tc.reason = pos->reason; | ||
1919 | GNUNET_NETWORK_fdset_zero (sh->rs); | ||
1920 | GNUNET_NETWORK_fdset_zero (sh->ws); | ||
1921 | tc.fds_len = pos->fds_len; | ||
1922 | tc.fds = pos->fds; | ||
1923 | tc.read_ready = (NULL == pos->read_set) ? sh->rs : pos->read_set; | ||
1924 | if ( (-1 != pos->read_fd) && | ||
1925 | (0 != (pos->reason & GNUNET_SCHEDULER_REASON_READ_READY)) ) | ||
1926 | GNUNET_NETWORK_fdset_set_native (sh->rs, | ||
1927 | pos->read_fd); | ||
1928 | tc.write_ready = (NULL == pos->write_set) ? sh->ws : pos->write_set; | ||
1929 | if ((-1 != pos->write_fd) && | ||
1930 | (0 != (pos->reason & GNUNET_SCHEDULER_REASON_WRITE_READY))) | ||
1931 | GNUNET_NETWORK_fdset_set_native (sh->ws, | ||
1932 | pos->write_fd); | ||
1933 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
1934 | "Running task: %p\n", | ||
1935 | pos); | ||
1936 | pos->callback (pos->callback_cls); | ||
1937 | active_task = NULL; | ||
1938 | dump_backtrace (pos); | ||
1939 | destroy_task (pos); | ||
1940 | tasks_run++; | ||
1941 | } | ||
1942 | if (0 == ready_count) | ||
1943 | return GNUNET_NO; | ||
1944 | return GNUNET_OK; | ||
1945 | } | ||
1946 | |||
1947 | |||
1948 | /** | ||
1949 | * Initialize and run scheduler. This function will return when all | ||
1950 | * tasks have completed. On systems with signals, receiving a SIGTERM | ||
1951 | * (and other similar signals) will cause #GNUNET_SCHEDULER_shutdown | ||
1952 | * to be run after the active task is complete. As a result, SIGTERM | ||
1953 | * causes all shutdown tasks to be scheduled with reason | ||
1954 | * #GNUNET_SCHEDULER_REASON_SHUTDOWN. (However, tasks added | ||
1955 | * afterwards will execute normally!). Note that any particular | ||
1956 | * signal will only shut down one scheduler; applications should | ||
1957 | * always only create a single scheduler. | ||
1958 | * | ||
1959 | * @param driver drive to use for the event loop | ||
1960 | * @param task task to run first (and immediately) | ||
1961 | * @param task_cls closure of @a task | ||
1962 | * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure | ||
1963 | */ | ||
1964 | int | ||
1965 | GNUNET_SCHEDULER_run_with_driver (const struct GNUNET_SCHEDULER_Driver *driver, | ||
1966 | GNUNET_SCHEDULER_TaskCallback task, | ||
1967 | void *task_cls) | ||
1968 | { | ||
1969 | int ret; | ||
1970 | struct GNUNET_SIGNAL_Context *shc_int; | ||
1971 | struct GNUNET_SIGNAL_Context *shc_term; | ||
1972 | #if (SIGTERM != GNUNET_TERM_SIG) | ||
1973 | struct GNUNET_SIGNAL_Context *shc_gterm; | ||
1974 | #endif | ||
1975 | #ifndef MINGW | ||
1976 | struct GNUNET_SIGNAL_Context *shc_quit; | ||
1977 | struct GNUNET_SIGNAL_Context *shc_hup; | ||
1978 | struct GNUNET_SIGNAL_Context *shc_pipe; | ||
1979 | #endif | ||
1980 | struct GNUNET_SCHEDULER_Task tsk; | ||
1981 | const struct GNUNET_DISK_FileHandle *pr; | ||
1982 | struct GNUNET_SCHEDULER_Handle sh; | ||
1983 | |||
1984 | /* general set-up */ | ||
1985 | GNUNET_assert (NULL == active_task); | ||
1986 | GNUNET_assert (NULL == shutdown_pipe_handle); | ||
1987 | shutdown_pipe_handle = GNUNET_DISK_pipe (GNUNET_NO, | ||
1988 | GNUNET_NO, | ||
1989 | GNUNET_NO, | ||
1990 | GNUNET_NO); | ||
1991 | GNUNET_assert (NULL != shutdown_pipe_handle); | ||
1992 | pr = GNUNET_DISK_pipe_handle (shutdown_pipe_handle, | ||
1993 | GNUNET_DISK_PIPE_END_READ); | ||
1994 | GNUNET_assert (NULL != pr); | ||
1995 | my_pid = getpid (); | ||
1996 | |||
1997 | /* install signal handlers */ | ||
1998 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
1999 | "Registering signal handlers\n"); | ||
2000 | shc_int = GNUNET_SIGNAL_handler_install (SIGINT, | ||
2001 | &sighandler_shutdown); | ||
2002 | shc_term = GNUNET_SIGNAL_handler_install (SIGTERM, | ||
2003 | &sighandler_shutdown); | ||
2004 | #if (SIGTERM != GNUNET_TERM_SIG) | ||
2005 | shc_gterm = GNUNET_SIGNAL_handler_install (GNUNET_TERM_SIG, | ||
2006 | &sighandler_shutdown); | ||
2007 | #endif | ||
2008 | #ifndef MINGW | ||
2009 | shc_pipe = GNUNET_SIGNAL_handler_install (SIGPIPE, | ||
2010 | &sighandler_pipe); | ||
2011 | shc_quit = GNUNET_SIGNAL_handler_install (SIGQUIT, | ||
2012 | &sighandler_shutdown); | ||
2013 | shc_hup = GNUNET_SIGNAL_handler_install (SIGHUP, | ||
2014 | &sighandler_shutdown); | ||
2015 | #endif | ||
2016 | |||
2017 | /* Setup initial tasks */ | ||
2018 | current_priority = GNUNET_SCHEDULER_PRIORITY_DEFAULT; | ||
2019 | current_lifeness = GNUNET_YES; | ||
2020 | memset (&tsk, | ||
2021 | 0, | ||
2022 | sizeof (tsk)); | ||
2023 | active_task = &tsk; | ||
2024 | tsk.sh = &sh; | ||
2025 | GNUNET_SCHEDULER_add_with_reason_and_priority (task, | ||
2026 | task_cls, | ||
2027 | GNUNET_SCHEDULER_REASON_STARTUP, | ||
2028 | GNUNET_SCHEDULER_PRIORITY_DEFAULT); | ||
2029 | GNUNET_SCHEDULER_add_now_with_lifeness (GNUNET_NO, | ||
2030 | &GNUNET_OS_install_parent_control_handler, | ||
2031 | NULL); | ||
2032 | active_task = NULL; | ||
2033 | driver->set_wakeup (driver->cls, | ||
2034 | GNUNET_TIME_absolute_get ()); | ||
2035 | |||
2036 | /* begin main event loop */ | ||
2037 | sh.rs = GNUNET_NETWORK_fdset_create (); | ||
2038 | sh.ws = GNUNET_NETWORK_fdset_create (); | ||
2039 | sh.driver = driver; | ||
2040 | ret = driver->loop (driver->cls, | ||
2041 | &sh); | ||
2042 | GNUNET_NETWORK_fdset_destroy (sh.rs); | ||
2043 | GNUNET_NETWORK_fdset_destroy (sh.ws); | ||
2044 | |||
2045 | /* uninstall signal handlers */ | ||
2046 | GNUNET_SIGNAL_handler_uninstall (shc_int); | ||
2047 | GNUNET_SIGNAL_handler_uninstall (shc_term); | ||
2048 | #if (SIGTERM != GNUNET_TERM_SIG) | ||
2049 | GNUNET_SIGNAL_handler_uninstall (shc_gterm); | ||
2050 | #endif | ||
2051 | #ifndef MINGW | ||
2052 | GNUNET_SIGNAL_handler_uninstall (shc_pipe); | ||
2053 | GNUNET_SIGNAL_handler_uninstall (shc_quit); | ||
2054 | GNUNET_SIGNAL_handler_uninstall (shc_hup); | ||
2055 | #endif | ||
2056 | GNUNET_DISK_pipe_close (shutdown_pipe_handle); | ||
2057 | shutdown_pipe_handle = NULL; | ||
2058 | return ret; | ||
2059 | } | ||
2060 | |||
2061 | |||
2062 | /** | ||
2063 | * Obtain the driver for using select() as the event loop. | ||
2064 | * | ||
2065 | * @return NULL on error | ||
2066 | */ | ||
2067 | const struct GNUNET_SCHEDULER_Driver * | ||
2068 | GNUNET_SCHEDULER_driver_select () | ||
2069 | { | ||
2070 | GNUNET_break (0); // not implemented | ||
2071 | return NULL; | ||
2072 | } | ||
2073 | |||
2074 | |||
1753 | /* end of scheduler.c */ | 2075 | /* end of scheduler.c */ |