1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
|
/* SPDX-License-Identifier: GPL-2.0 */
/** \file sched.c Paraslash's scheduling functions. */
#include "para.h"
#include "ipc.h"
#include "fd.h"
#include "list.h"
#include "sched.h"
#include "string.h"
#include "time.h"
#include "error.h"
/**
* The possible states of a task.
*
* In addition to the states listed here, a task may also enter zombie state.
* This happens when its ->post_monitor function returns negative, the ->status
* field is then set to this return value. Such tasks are not scheduled any
* more (i.e. ->pre_monitor() and ->post_monitor() are no longer called), but
* they stay on the scheduler task list until \ref task_reap() or
* \ref sched_shutdown() is called.
*/
enum task_status {
/** Task has been reaped and may be removed from the task list. */
TS_DEAD,
/** Task is active. */
TS_RUNNING,
};
struct task {
/** A copy of the task name supplied when the task was registered. */
char *name;
/** Copied during task_register(). */
struct task_info info;
/* TS_RUNNING, TS_DEAD, or zombie (negative value). */
int status;
/** Position of the task in the task list of the scheduler. */
struct list_head node;
/** If less than zero, the task was notified by another task. */
int notification;
};
static struct timeval now_struct;
const struct timeval *now = &now_struct;
/* Internal representation of a paraslash scheduler instance. */
struct sched {
/* Initial value (in milliseconds) before any pre_monitor call. */
int default_timeout;
/* The timeout (also in milliseconds) for the next iteration. */
int timeout;
/* Passed to poll(2). */
struct pollfd *pfd;
/* Number of elements in the above array, passed to poll(2). */
unsigned pfd_array_len;
/* Number of fds registered for monitoring so far. */
unsigned num_pfds;
/* Maps fds to indices of the pfd array. */
unsigned *pidx;
/* Number of elements in the above pidx array. */
unsigned pidx_array_len;
/* Either the application-supplied function or xpoll() of fd.c. */
int (*poll_function)(struct pollfd *fds, nfds_t nfds, int timeout);
/* Tasks which have been registered to the scheduler. */
struct list_head task_list;
};
/**
* Allocate and initialize a scheduler instance.
*
* \param poll_function Optional.
*
* If NULL is passed as the poll function pointer, the \ref xpoll() wrapper
* is used to wait for events on the file descriptors to be monitored.
*
* \return A pointer to the new instance that can be used to register tasks
* or to start scheduling. The only possible error is allocation failure,
* in which case the function aborts. Thus, it never returns NULL.
*/
struct sched *sched_new(int (*poll_function)(struct pollfd *, nfds_t, int))
{
struct sched *s = zalloc(sizeof(*s));
s->default_timeout = 1000;
init_list_head(&s->task_list);
s->poll_function = poll_function? poll_function : xpoll;
return s;
}
static void sched_pre_monitor(struct sched *s)
{
struct task *t, *tmp;
list_for_each_entry_safe(t, tmp, &s->task_list, node) {
if (t->status < 0)
continue;
if (t->notification != 0)
sched_min_delay(s);
if (t->info.pre_monitor)
t->info.pre_monitor(s, t->info.context);
}
}
static void unlink_and_free_task(struct task *t)
{
list_del(&t->node);
free(t->name);
free(t);
}
//#define SCHED_DEBUG 1
static inline void call_post_monitor(struct sched *s, struct task *t)
{
int ret;
#ifndef SCHED_DEBUG
ret = t->info.post_monitor(s, t->info.context);
#else
struct timeval t1, t2, diff;
unsigned long pst;
clock_get_realtime(&t1);
ret = t->info.post_monitor(s, t->info.context);
clock_get_realtime(&t2);
tv_diff(&t1, &t2, &diff);
pst = tv2ms(&diff);
if (pst > 50)
PARA_WARNING_LOG("%s: post_monitor time: %lums\n",
t->name, pst);
#endif
t->status = ret < 0? ret : TS_RUNNING;
}
static unsigned sched_post_monitor(struct sched *s)
{
struct task *t, *tmp;
unsigned num_running_tasks = 0;
list_for_each_entry_safe(t, tmp, &s->task_list, node) {
if (t->status == TS_DEAD) /* task has been reaped */
unlink_and_free_task(t);
else if (t->status == TS_RUNNING) {
call_post_monitor(s, t); /* sets t->status */
t->notification = 0;
if (t->status == TS_RUNNING)
num_running_tasks++;
}
}
return num_running_tasks;
}
/**
* The core function of all paraslash programs.
*
* \param s Pointer to the scheduler struct.
*
* This function updates the global now pointer, calls all registered
* pre_monitor hooks which may set the timeout and add any file descriptors to
* the pollfd array. Next, it calls the poll function and makes the result
* available to the registered tasks by calling their post_monitor hook.
*
* \return Zero if no more tasks are left in the task list, negative if the
* poll function returned an error.
*
* \sa \ref now.
*/
int schedule(struct sched *s)
{
int ret;
unsigned num_running_tasks;
again:
s->num_pfds = 0;
if (s->pidx)
memset(s->pidx, 0xff, s->pidx_array_len * sizeof(unsigned));
s->timeout = s->default_timeout;
clock_get_realtime(&now_struct);
sched_pre_monitor(s);
ret = s->poll_function(s->pfd, s->num_pfds, s->timeout);
if (ret < 0)
return ret;
clock_get_realtime(&now_struct);
num_running_tasks = sched_post_monitor(s);
if (num_running_tasks == 0)
return 0;
goto again;
}
/**
* Obtain the error status of a task and deallocate its resources.
*
* \param tptr Identifies the task to reap.
*
* When a task's post-monitor method returns negative, the task enters zombie
* state. The {pre,post}-monitor methods will no longer be called, but some
* amount of task-specific memory remains in use until this function is called.
*
* \return If tptr or *tptr is NULL, or if the given task is still running,
* the function does nothing and returns zero. Otherwise it resets *tptr
* and returns the (negative) error code of the terminated task.
*
* \sa \ref sched_shutdown(), wait(2).
*/
int task_reap(struct task **tptr)
{
struct task *t;
int ret;
if (!tptr)
return 0;
t = *tptr;
if (!t)
return 0;
if (t->status >= 0)
return 0;
ret = t->status;
PARA_INFO_LOG("reaping %s: %s\n", t->name, para_strerror(-ret));
/*
* With list_for_each_entry_safe() it is only safe to remove the
* _current_ list item. Since we are being called from the loop in
* schedule() via some task's ->post_monitor() function, freeing the
* given task here would result in use-after-free bugs in schedule().
* So we only set the task status to TS_DEAD which tells schedule() to
* free the task in the next iteration of its loop.
*/
t->status = TS_DEAD;
*tptr = NULL;
return ret;
}
/**
* Deallocate all resources of all tasks of a scheduler instance.
*
* \param s The scheduler instance.
*
* This should only be called after \ref schedule() has returned.
*/
void sched_shutdown(struct sched *s)
{
struct task *t, *tmp;
list_for_each_entry_safe(t, tmp, &s->task_list, node) {
if (t->status == TS_RUNNING)
/* The task list should contain only terminated tasks. */
PARA_WARNING_LOG("shutting down running task %s\n",
t->name);
unlink_and_free_task(t);
}
free(s->pfd);
free(s->pidx);
free(s);
}
/**
* Add a task to the scheduler task list.
*
* \param info Task information supplied by the caller.
* \param s The scheduler instance.
*
* \return A pointer to a newly allocated task. The layout of the task
* structure is only known to the scheduler core. The structure can be freed
* after \ref schedule() returned ba calling \ref sched_shutdown().
*/
struct task *task_register(struct task_info *info, struct sched *s)
{
struct task *t = alloc(sizeof(*t));
assert(info->post_monitor);
t->info = *info;
t->name = para_strdup(info->name);
t->notification = 0;
t->status = TS_RUNNING;
list_add_tail(&t->node, &s->task_list);
return t;
}
/**
* Get the list of all registered tasks.
*
* \param s The scheduler instance to get the task list from.
*
* \return The task list.
*
* Each entry of the list contains an identifier which is simply a hex number.
* The result is dynamically allocated and must be freed by the caller.
*/
char *get_task_list(struct sched *s)
{
struct task *t, *tmp;
char *msg = NULL;
list_for_each_entry_safe(t, tmp, &s->task_list, node) {
char *tmp_msg;
tmp_msg = make_message("%s%p\t%s\t%s\n", msg? msg : "", t,
t->status == TS_DEAD? "dead" :
(t->status == TS_RUNNING? "running" : "zombie"),
t->name);
free(msg);
msg = tmp_msg;
}
return msg;
}
/**
* Set the notification value of a task.
*
* \param t The task to notify.
* \param err A positive error code.
*
* Tasks which honor notifications are supposed to call \ref
* task_get_notification() in their post_monitor function and act on the
* returned notification value.
*
* If the scheduler detects during its pre_monitor loop that at least one task
* has been notified, the loop terminates, and the post_monitor methods of all
* taks are immediately called again.
*
* The notification for a task is reset after the call to its post_monitor
* method.
*
* \sa \ref task_get_notification().
*/
void task_notify(struct task *t, int err)
{
assert(err > 0);
if (t->notification == -err) /* ignore subsequent notifications */
return;
PARA_INFO_LOG("notifying task %s: %s\n", t->name, para_strerror(err));
t->notification = -err;
}
/**
* Return the notification value of a task.
*
* \param t The task to get the notification value from.
*
* \return The notification value. If this is negative, the task has been
* notified by another task. Tasks are supposed to check for notifications by
* calling this function from their post_monitor method.
*
* \sa \ref task_notify().
*/
int task_get_notification(const struct task *t)
{
return t->notification;
}
/**
* Return the status value of a task.
*
* \param t The task to get the status value from.
*
* \return Zero if task does not exist, one if task is running, negative error
* code if task has terminated.
*/
int task_status(const struct task *t)
{
if (!t)
return 0;
if (t->status == TS_DEAD) /* pretend dead tasks don't exist */
return 0;
if (t->status == TS_RUNNING)
return 1;
return t->status;
}
/**
* Set the notification value of all tasks of a scheduler instance.
*
* \param s The scheduler instance whose tasks should be notified.
* \param err A positive error code.
*
* This simply iterates over all existing tasks of \a s and sets each
* task's notification value to \p -err.
*/
void task_notify_all(struct sched *s, int err)
{
struct task *t;
list_for_each_entry(t, &s->task_list, node)
task_notify(t, err);
}
/**
* Set the I/O timeout to the minimal possible value.
*
* \param s Pointer to the scheduler struct.
*
* This causes the next poll() call to return immediately.
*/
void sched_min_delay(struct sched *s)
{
s->timeout = 0;
}
/**
* Impose an upper bound for the I/O timeout.
*
* \param to Maximal allowed timeout.
* \param s Pointer to the scheduler struct.
*
* If the current I/O timeout is already smaller than to, this function does
* nothing. Otherwise the timeout is set to the given value.
*
* \sa \ref sched_request_timeout_ms().
*/
void sched_request_timeout(struct timeval *to, struct sched *s)
{
long unsigned ms = tv2ms(to);
if (s->timeout > ms)
s->timeout = ms;
}
/**
* Bound the I/O timeout to at most the given amount of milliseconds.
*
* \param ms The maximal allowed timeout in milliseconds.
* \param s Pointer to the scheduler struct.
*
* Like \ref sched_request_timeout() this imposes an upper bound on the I/O
* timeout.
*/
void sched_request_timeout_ms(long unsigned ms, struct sched *s)
{
struct timeval tv;
ms2tv(ms, &tv);
sched_request_timeout(&tv, s);
}
/**
* Bound the I/O timeout by an absolute time in the future.
*
* \param barrier Defines the upper bound for the timeout.
* \param s Pointer to the scheduler struct.
*
* \return If the barrier is in the past, this function does nothing and
* returns zero. Otherwise it returns one.
*
* \sa \ref sched_request_barrier_or_min_delay().
*/
int sched_request_barrier(struct timeval *barrier, struct sched *s)
{
struct timeval diff;
if (tv_diff(now, barrier, &diff) > 0)
return 0;
sched_request_timeout(&diff, s);
return 1;
}
/**
* Bound the I/O timeout or request a minimal delay.
*
* \param barrier Absolute time as in \ref sched_request_barrier().
* \param s Pointer to the scheduler struct.
*
* \return If the barrier is in the past, this function requests a minimal
* timeout and returns zero. Otherwise it returns one.
*
* \sa \ref sched_min_delay(), \ref sched_request_barrier().
*/
int sched_request_barrier_or_min_delay(struct timeval *barrier, struct sched *s)
{
struct timeval diff;
if (tv_diff(now, barrier, &diff) > 0) {
sched_min_delay(s);
return 0;
}
sched_request_timeout(&diff, s);
return 1;
}
static void add_pollfd(int fd, struct sched *s, short events)
{
assert(fd >= 0);
#if 0
{
int flags = fcntl(fd, F_GETFL);
if (!(flags & O_NONBLOCK)) {
PARA_EMERG_LOG("fd %d is a blocking file descriptor\n", fd);
exit(EXIT_FAILURE);
}
}
#endif
if (s->pidx_array_len > fd) { /* is fd already registered? */
if (s->pidx[fd] < s->pfd_array_len) { /* yes, it is */
assert(s->pfd[s->pidx[fd]].fd == fd);
s->pfd[s->pidx[fd]].events |= events;
return;
}
} else { /* need to extend the index array */
unsigned old_len = s->pidx_array_len;
while (s->pidx_array_len <= fd)
s->pidx_array_len = s->pidx_array_len * 2 + 1;
PARA_INFO_LOG("pidx array len: %u\n", s->pidx_array_len);
s->pidx = para_realloc(s->pidx,
s->pidx_array_len * sizeof(unsigned));
memset(s->pidx + old_len, 0xff,
(s->pidx_array_len - old_len) * sizeof(unsigned));
}
/*
* The given fd is not part of the pfd array yet. Initialize pidx[fd]
* to point at the next unused slot of this array and initialize the
* slot.
*/
s->pidx[fd] = s->num_pfds;
if (s->pfd_array_len <= s->num_pfds) {
unsigned old_len = s->pfd_array_len;
s->pfd_array_len = old_len * 2 + 1;
PARA_INFO_LOG("pfd array len: %u\n", s->pfd_array_len);
s->pfd = para_realloc(s->pfd,
s->pfd_array_len * sizeof(struct pollfd));
memset(s->pfd + old_len, 0,
(s->pfd_array_len - old_len) * sizeof(struct pollfd));
}
s->pfd[s->num_pfds].fd = fd;
s->pfd[s->num_pfds].events = events;
s->pfd[s->num_pfds].revents = 0;
s->num_pfds++;
}
/**
* Instruct the scheduler to monitor an fd for readiness for reading.
*
* \param fd The file descriptor.
* \param s The scheduler.
*
* \sa \ref sched_monitor_writefd().
*/
void sched_monitor_readfd(int fd, struct sched *s)
{
add_pollfd(fd, s, POLLIN);
}
/**
* Instruct the scheduler to monitor an fd for readiness for writing.
*
* \param fd The file descriptor.
* \param s The scheduler.
*
* \sa \ref sched_monitor_readfd().
*/
void sched_monitor_writefd(int fd, struct sched *s)
{
add_pollfd(fd, s, POLLOUT);
}
static int get_revents(int fd, const struct sched *s)
{
if (fd < 0)
return 0;
if (fd >= s->pidx_array_len)
return 0;
if (s->pidx[fd] >= s->num_pfds)
return 0;
if (s->pfd[s->pidx[fd]].fd != fd)
return 0;
assert((s->pfd[s->pidx[fd]].revents & POLLNVAL) == 0);
return s->pfd[s->pidx[fd]].revents;
}
/**
* Check whether there is data to read on the given fd.
*
* To be called from the ->post_monitor() method of a task.
*
* \param fd Should have been monitored with \ref sched_monitor_readfd().
* \param s The scheduler instance.
*
* \return True if the file descriptor is ready for reading, false otherwise.
* If fd is negative, or has not been monitored in the current iteration of the
* scheduler's main loop, the function also returns false.
*
* \sa \ref sched_write_ok().
*/
bool sched_read_ok(int fd, const struct sched *s)
{
return get_revents(fd, s) & (POLLIN | POLLERR | POLLHUP);
}
/**
* Check whether writing is possible (i.e., does not block).
*
* \param fd Should have been monitored with \ref sched_monitor_writefd().
* \param s The scheduler instance.
*
* \return True if the file descriptor is ready for writing, false otherwise.
* The comment in \ref sched_read_ok() about invalid file descriptors applies
* to this function as well.
*/
bool sched_write_ok(int fd, const struct sched *s)
{
return get_revents(fd, s) & (POLLOUT | POLLERR | POLLHUP);
}
|