]> git.tuebingen.mpg.de Git - paraslash.git/blob - sched.c
7b5ca63d29dc78aeda551bd11fb63819dce7bbad
[paraslash.git] / sched.c
1 /*
2  * Copyright (C) 2006-2011 Andre Noll <maan@systemlinux.org>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file sched.c Paraslash's scheduling functions. */
8
9 #include <regex.h>
10 #include <assert.h>
11 #include <sys/time.h>
12
13 #include "para.h"
14 #include "ipc.h"
15 #include "fd.h"
16 #include "list.h"
17 #include "sched.h"
18 #include "string.h"
19 #include "time.h"
20 #include "error.h"
21
22 static struct list_head pre_select_list, post_select_list;
23 static int initialized;
24
25 static struct timeval now_struct;
26 struct timeval *now = &now_struct;
27
28 /*
29  * Remove a task from the scheduler.
30  *
31  * \param t The task to remove.
32  *
33  * If the pre_select pointer of \a t is not \p NULL, it is removed from
34  * the pre_select list of the scheduler. Same goes for \a post_select.
35  */
36 static void unregister_task(struct task *t)
37 {
38         if (!initialized)
39                 return;
40         assert(t->error < 0);
41         PARA_INFO_LOG("unregistering %s (%s)\n", t->status,
42                 para_strerror(-t->error));
43         if (t->pre_select)
44                 list_del(&t->pre_select_node);
45         if (t->post_select)
46                 list_del(&t->post_select_node);
47 }
48
49 static void sched_preselect(struct sched *s)
50 {
51         struct task *t, *tmp;
52         list_for_each_entry_safe(t, tmp, &pre_select_list, pre_select_node) {
53                 if (t->pre_select)
54                         t->pre_select(s, t);
55                 if (t->error < 0)
56                         unregister_task(t);
57         }
58 }
59
60 //#define SCHED_DEBUG 1
61 static inline void call_post_select(struct sched *s, struct task *t)
62 {
63 #ifndef SCHED_DEBUG
64         t->post_select(s, t);
65 #else
66         struct timeval t1, t2, diff;
67         unsigned long pst;
68
69         gettimeofday(&t1, NULL);
70         t->post_select(s, t);
71         gettimeofday(&t2, NULL);
72         tv_diff(&t1, &t2, &diff);
73         pst = tv2ms(&diff);
74         if (pst > 50)
75                 PARA_WARNING_LOG("%s: post_select time: %lums\n",
76                         t->status, pst);
77 #endif
78 }
79
80 static void sched_post_select(struct sched *s)
81 {
82         struct task *t, *tmp;
83
84         list_for_each_entry_safe(t, tmp, &post_select_list, post_select_node) {
85                 if (t->error >= 0)
86                         call_post_select(s, t);
87 //              PARA_INFO_LOG("%s: %d\n", t->status, t->ret);
88                 if (t->error >= 0)
89                         continue;
90                 /*
91                  * We have to check whether the list is empty because the call
92                  * to ->post_select() might have called sched_shutdown(). In
93                  * this case t has been unregistered already, so we must not
94                  * unregister it again.
95                  */
96                 if (list_empty(&post_select_list))
97                         return;
98                 unregister_task(t);
99         }
100 }
101
102 /**
103  * The core function for all paraslash programs.
104  *
105  * \param s Pointer to the scheduler struct.
106  *
107  * This function updates the global \a now pointer, calls all registered
108  * pre_select hooks which may set the timeout and add any file descriptors to
109  * the fd sets of \a s.  Next, it calls para_select() and makes the result available
110  * to the registered tasks by calling their post_select hook.
111  *
112  * \return Zero if no more tasks are left in either of the two lists, negative
113  * if para_select returned an error.
114  *
115  * \sa task, now.
116  */
117 int schedule(struct sched *s)
118 {
119         int ret;
120
121         if (!initialized)
122                 return -E_NOT_INITIALIZED;
123         if (!s->select_function)
124                 s->select_function = para_select;
125 again:
126         FD_ZERO(&s->rfds);
127         FD_ZERO(&s->wfds);
128         s->select_timeout = s->default_timeout;
129         s->max_fileno = -1;
130         gettimeofday(now, NULL);
131         sched_preselect(s);
132         if (list_empty(&pre_select_list) && list_empty(&post_select_list))
133                 return 0;
134         ret = s->select_function(s->max_fileno + 1, &s->rfds, &s->wfds,
135                 &s->select_timeout);
136         if (ret < 0)
137                 return ret;
138         if (ret == 0) {
139                 /*
140                  * APUE: Be careful not to check the descriptor sets on return
141                  * unless the return value is greater than zero. The return
142                  * state of the descriptor sets is implementation dependent if
143                  * either a signal is caught or the timer expires.
144                  */
145                 FD_ZERO(&s->rfds);
146                 FD_ZERO(&s->wfds);
147         }
148         gettimeofday(now, NULL);
149         sched_post_select(s);
150         if (list_empty(&pre_select_list) && list_empty(&post_select_list))
151                 return 0;
152         goto again;
153 }
154
155 /*
156  * Initialize the paraslash scheduler.
157  */
158 static void init_sched(void)
159 {
160         PARA_INFO_LOG("initializing scheduler\n");
161         INIT_LIST_HEAD(&pre_select_list);
162         INIT_LIST_HEAD(&post_select_list);
163         initialized = 1;
164 }
165
166 /**
167  * Add a task to the scheduler.
168  *
169  * \param t the task to add
170  *
171  * If the pre_select pointer of \a t is not \p NULL, it is added to
172  * the pre_select list of the scheduler. Same goes for post_select.
173  *
174  * \sa task::pre_select, task::post_select
175  */
176 void register_task(struct task *t)
177 {
178         if (!initialized)
179                 init_sched();
180         PARA_INFO_LOG("registering %s (%p)\n", t->status, t);
181         if (t->pre_select) {
182                 PARA_DEBUG_LOG("pre_select: %p\n", &t->pre_select);
183                 list_add_tail(&t->pre_select_node, &pre_select_list);
184         }
185         if (t->post_select) {
186                 PARA_DEBUG_LOG("post_select: %p\n", &t->post_select);
187                 list_add_tail(&t->post_select_node, &post_select_list);
188         }
189 }
190
191 /**
192  * Unregister all tasks.
193  *
194  * This will cause \a schedule() to return immediately because both the
195  * \a pre_select_list and the \a post_select_list are empty. This function
196  * must be called from the post_select (rather than the pre_select) method.
197  */
198 void sched_shutdown(void)
199 {
200         struct task *t, *tmp;
201
202         if (!initialized)
203                 return;
204         list_for_each_entry_safe(t, tmp, &pre_select_list, pre_select_node) {
205                 t->error = -E_SCHED_SHUTDOWN;
206                 unregister_task(t);
207         }
208         list_for_each_entry_safe(t, tmp, &post_select_list, post_select_node) {
209                 t->error = -E_SCHED_SHUTDOWN;
210                 unregister_task(t);
211         }
212         initialized = 0;
213 }
214
215 /**
216  * Get the list of all registered tasks.
217  *
218  * \return The task list.
219  *
220  * Each entry of the list contains an identifier which is simply a hex number.
221  * The result is dynamically allocated and must be freed by the caller.
222  */
223 char *get_task_list(void)
224 {
225         struct task *t, *tmp;
226         char *msg = NULL;
227
228         if (!initialized)
229                 return NULL;
230         list_for_each_entry_safe(t, tmp, &pre_select_list, pre_select_node) {
231                 char *tmp_msg;
232                 tmp_msg = make_message("%s%p\tpre\t%s\n", msg? msg : "", t, t->status);
233                 free(msg);
234                 msg = tmp_msg;
235         }
236         list_for_each_entry_safe(t, tmp, &post_select_list, post_select_node) {
237                 char *tmp_msg;
238 //              if (t->pre_select)
239 //                      continue;
240                 tmp_msg = make_message("%s%p\tpost\t%s\n", msg? msg : "", t, t->status);
241                 free(msg);
242                 msg = tmp_msg;
243         }
244         //PARA_DEBUG_LOG("task list:\n%s", msg);
245         return msg;
246 }
247
248 /**
249  * Set the select timeout to the minimal possible value.
250  *
251  * \param s Pointer to the scheduler struct.
252  *
253  * This causes the next select() call to return immediately.
254  */
255 void sched_min_delay(struct sched *s)
256 {
257         s->select_timeout.tv_sec = s->select_timeout.tv_usec = 0;
258 }
259
260 /**
261  * Impose an upper bound for the timeout of the next select() call.
262  *
263  * \param to Maximal allowed timeout.
264  * \param s Pointer to the scheduler struct.
265  *
266  * If the current scheduler timeout is already smaller than \a to, this
267  * function does nothing. Otherwise the timeout for the next select() call is
268  * set to the given value.
269  *
270  * \sa sched_request_timeout_ms().
271  */
272 void sched_request_timeout(struct timeval *to, struct sched *s)
273 {
274         if (tv_diff(&s->select_timeout, to, NULL) > 0)
275                 s->select_timeout = *to;
276 }
277
278 /**
279  * Force the next select() call to return before the given amount of milliseconds.
280  *
281  * \param ms The maximal allowed timeout in milliseconds.
282  * \param s Pointer to the scheduler struct.
283  *
284  * Like sched_request_timeout() this imposes an upper bound on the timeout
285  * value for the next select() call.
286  */
287 void sched_request_timeout_ms(long unsigned ms, struct sched *s)
288 {
289         struct timeval tv;
290         ms2tv(ms, &tv);
291         sched_request_timeout(&tv, s);
292 }
293
294 /**
295  * Force the next select() call to return before the given future time.
296  *
297  * \param barrier Absolute time before select() should return.
298  * \param s Pointer to the scheduler struct.
299  *
300  * If \a barrier is in the past, this function does nothing.
301  *
302  * \sa sched_request_barrier_or_min_delay().
303  */
304 void sched_request_barrier(struct timeval *barrier, struct sched *s)
305 {
306         struct timeval diff;
307
308         if (tv_diff(now, barrier, &diff) > 0)
309                 return;
310         sched_request_timeout(&diff, s);
311 }
312
313 /**
314  * Force the next select() call to return before the given time.
315  *
316  * \param barrier Absolute time before select() should return.
317  * \param s Pointer to the scheduler struct.
318  *
319  * If \a barrier is in the past, this function requests a minimal timeout.
320  *
321  * \sa sched_min_delay(), sched_request_barrier().
322  */
323 void sched_request_barrier_or_min_delay(struct timeval *barrier, struct sched *s)
324 {
325         struct timeval diff;
326
327         if (tv_diff(now, barrier, &diff) > 0)
328                 return sched_min_delay(s);
329         sched_request_timeout(&diff, s);
330 }