/*
- * Copyright (C) 2006-2013 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2006-2014 Andre Noll <maan@systemlinux.org>
*
* Licensed under the GPL v2. For licencing details see COPYING.
*/
#include <regex.h>
#include <assert.h>
-#include <sys/time.h>
#include "para.h"
#include "ipc.h"
static struct timeval now_struct;
struct timeval *now = &now_struct;
-/*
- * Remove a task from the scheduler.
- *
- * \param t The task to remove.
- *
- * If the pre_select pointer of \a t is not \p NULL, it is removed from
- * the pre_select list of the scheduler. Same goes for \a post_select.
- */
-static void unregister_task(struct task *t)
-{
- assert(t->error < 0);
- PARA_INFO_LOG("unregistering %s (%s)\n", t->status,
- para_strerror(-t->error));
- if (t->pre_select)
- list_del(&t->pre_select_node);
- if (t->post_select)
- list_del(&t->post_select_node);
-}
-
static inline bool timeout_is_zero(struct sched *s)
{
struct timeval *tv = &s->select_timeout;
static void sched_preselect(struct sched *s)
{
struct task *t, *tmp;
- list_for_each_entry_safe(t, tmp, &s->pre_select_list, pre_select_node) {
- if (t->error < 0) {
- unregister_task(t);
+
+ list_for_each_entry_safe(t, tmp, &s->task_list, node) {
+ if (t->error < 0)
continue;
- }
- if (t->notification != 0) {
+ if (t->notification != 0)
sched_min_delay(s);
- break;
- }
- if (!t->pre_select)
- continue;
- t->pre_select(s, t);
- if (timeout_is_zero(s))
- break;
+ if (t->pre_select)
+ t->pre_select(s, t);
}
}
+static void unlink_and_free_task(struct task *t)
+{
+ PARA_INFO_LOG("freeing task %s\n", t->status);
+ list_del(&t->node);
+ if (t->owned_by_sched)
+ free(t);
+}
+
//#define SCHED_DEBUG 1
static inline void call_post_select(struct sched *s, struct task *t)
{
#ifndef SCHED_DEBUG
- t->post_select(s, t);
+ t->error = t->post_select(s, t);
#else
struct timeval t1, t2, diff;
unsigned long pst;
- gettimeofday(&t1, NULL);
- t->post_select(s, t);
- gettimeofday(&t2, NULL);
+ clock_get_realtime(&t1);
+ t->error = t->post_select(s, t);
+ clock_get_realtime(&t2);
tv_diff(&t1, &t2, &diff);
pst = tv2ms(&diff);
if (pst > 50)
#endif
}
-static void sched_post_select(struct sched *s)
+static unsigned sched_post_select(struct sched *s)
{
struct task *t, *tmp;
+ unsigned num_running_tasks = 0;
- list_for_each_entry_safe(t, tmp, &s->post_select_list, post_select_node) {
- if (t->error >= 0)
- call_post_select(s, t);
-// PARA_INFO_LOG("%s: %d\n", t->status, t->ret);
- t->notification = 0;
- if (t->error >= 0)
+ list_for_each_entry_safe(t, tmp, &s->task_list, node) {
+ if (t->error < 0) {
+ if (t->dead) /* task has been reaped */
+ unlink_and_free_task(t);
continue;
- unregister_task(t);
+ }
+ call_post_select(s, t);
+ t->notification = 0;
+ if (t->error < 0) {
+ if (!t->owned_by_sched)
+ list_del(&t->node);
+ } else
+ num_running_tasks++;
}
+ return num_running_tasks;
}
/**
- * The core function for all paraslash programs.
+ * The core function of all paraslash programs.
*
* \param s Pointer to the scheduler struct.
*
* the fd sets of \a s. Next, it calls para_select() and makes the result available
* to the registered tasks by calling their post_select hook.
*
- * \return Zero if no more tasks are left in either of the two lists, negative
- * if para_select returned an error.
+ * \return Zero if no more tasks are left in the task list, negative if the
+ * select function returned an error.
*
- * \sa task, now.
+ * \sa \ref task, \ref now.
*/
int schedule(struct sched *s)
{
int ret;
+ unsigned num_running_tasks;
if (!s->select_function)
s->select_function = para_select;
FD_ZERO(&s->wfds);
s->select_timeout = s->default_timeout;
s->max_fileno = -1;
- gettimeofday(now, NULL);
+ clock_get_realtime(now);
sched_preselect(s);
- if (list_empty(&s->pre_select_list) && list_empty(&s->post_select_list))
- return 0;
- if (!timeout_is_zero(s)) {
- ret = s->select_function(s->max_fileno + 1, &s->rfds, &s->wfds,
- &s->select_timeout);
- if (ret < 0)
- return ret;
- if (ret == 0) {
- /*
- * APUE: Be careful not to check the descriptor sets on return
- * unless the return value is greater than zero. The return
- * state of the descriptor sets is implementation dependent if
- * either a signal is caught or the timer expires.
- */
- FD_ZERO(&s->rfds);
- FD_ZERO(&s->wfds);
- }
- gettimeofday(now, NULL);
- } else {
+ ret = s->select_function(s->max_fileno + 1, &s->rfds, &s->wfds,
+ &s->select_timeout);
+ if (ret < 0)
+ return ret;
+ if (ret == 0) {
+ /*
+ * APUE: Be careful not to check the descriptor sets on return
+ * unless the return value is greater than zero. The return
+ * state of the descriptor sets is implementation dependent if
+ * either a signal is caught or the timer expires.
+ */
FD_ZERO(&s->rfds);
FD_ZERO(&s->wfds);
}
- sched_post_select(s);
- if (list_empty(&s->pre_select_list) && list_empty(&s->post_select_list))
+ clock_get_realtime(now);
+ num_running_tasks = sched_post_select(s);
+ if (num_running_tasks == 0)
return 0;
goto again;
}
/**
- * Add a task to the scheduler.
+ * Obtain the error status of a task and deallocate its resources.
+ *
+ * \param tptr Identifies the task to reap.
+ *
+ * This function is similar to wait(2) in that it returns information about a
+ * terminated task and allows to release the resources associated with the
+ * task. Until this function is called, the terminated task remains in a zombie
+ * state.
+ *
+ * \return If \a tptr is \p NULL, or \a *tptr is \p NULL, the function does
+ * nothing and returns zero. Otherwise, it is checked whether the task
+ * identified by \a tptr is still running. If it is, the function returns zero
+ * and again, no action is taken. Otherwise the (negative) error code of the
+ * terminated task is returned and \a *tptr is set to \p NULL. The task will
+ * then be removed removed from the scheduler task list.
+ *
+ * \sa \ref sched_shutdown(), wait(2).
+ */
+int task_reap(struct task **tptr)
+{
+ struct task *t;
+
+ if (!tptr)
+ return 0;
+ t = *tptr;
+ if (!t)
+ return 0;
+ if (!t->owned_by_sched)
+ return 0;
+ if (t->error >= 0)
+ return 0;
+ if (t->dead) /* will be freed in sched_post_select() */
+ return 0;
+ /*
+ * 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_select() function, freeing the
+ * given task here would result in use-after-free bugs in schedule().
+ * So we only set t->dead which tells schedule() to free the task in
+ * the next iteration of its loop.
+ */
+ t->dead = true;
+ *tptr = NULL;
+ return t->error;
+}
+
+/**
+ * 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->error >= 0)
+ /* The task list should contain only terminated tasks. */
+ PARA_WARNING_LOG("shutting down running task %s\n",
+ t->status);
+ unlink_and_free_task(t);
+ }
+}
+
+/**
+ * Add a task to the scheduler. Deprecated.
*
* \param t The task to add.
* \param s The scheduler instance to add the task to.
*
- * If the pre_select pointer of \a t is not \p NULL, it is added to
- * the pre_select list of the scheduler. Same goes for post_select.
- *
* \sa task::pre_select, task::post_select
*/
void register_task(struct sched *s, struct task *t)
{
PARA_INFO_LOG("registering %s (%p)\n", t->status, t);
+ assert(t->post_select);
t->notification = 0;
- if (!s->pre_select_list.next)
- INIT_LIST_HEAD(&s->pre_select_list);
- if (!s->post_select_list.next)
- INIT_LIST_HEAD(&s->post_select_list);
- if (t->pre_select) {
- PARA_DEBUG_LOG("pre_select: %p\n", &t->pre_select);
- list_add_tail(&t->pre_select_node, &s->pre_select_list);
- }
- if (t->post_select) {
- PARA_DEBUG_LOG("post_select: %p\n", &t->post_select);
- list_add_tail(&t->post_select_node, &s->post_select_list);
- }
+ t->owned_by_sched = false;
+ if (!s->task_list.next)
+ INIT_LIST_HEAD(&s->task_list);
+ list_add_tail(&t->node, &s->task_list);
+}
+
+/**
+ * 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 structure. It will be
+ * freed by sched_shutdown().
+ */
+struct task *task_register(struct task_info *info, struct sched *s)
+{
+ struct task *t = para_malloc(sizeof(*t));
+
+ assert(info->post_select);
+
+ if (!s->task_list.next)
+ INIT_LIST_HEAD(&s->task_list);
+
+ snprintf(t->status, sizeof(t->status) - 1, "%s", info->name);
+ t->status[sizeof(t->status) - 1] = '\0';
+ t->notification = 0;
+ t->error = 0;
+ t->dead = false;
+ t->pre_select = info->pre_select;
+ t->post_select = info->post_select;
+ t->context = info->context;
+ t->owned_by_sched = true;
+ list_add_tail(&t->node, &s->task_list);
+ return t;
+}
+
+/**
+ * Obtain the context pointer of a task.
+ *
+ * \param t Return this task's context pointer.
+ *
+ * \return A pointer to the memory location specified previously as \a
+ * task_info->context when the task was registered with \ref task_register().
+ */
+void *task_context(struct task *t)
+{
+ assert(t->owned_by_sched);
+ return t->context;
}
/**
struct task *t, *tmp;
char *msg = NULL;
- list_for_each_entry_safe(t, tmp, &s->pre_select_list, pre_select_node) {
+ list_for_each_entry_safe(t, tmp, &s->task_list, node) {
char *tmp_msg;
- tmp_msg = make_message("%s%p\tpre\t%s\n", msg? msg : "", t, t->status);
+ tmp_msg = make_message("%s%p\t%s\t%s\n", msg? msg : "", t,
+ t->error < 0? (t->dead? "dead" : "zombie") : "running",
+ t->status);
free(msg);
msg = tmp_msg;
}
- list_for_each_entry_safe(t, tmp, &s->post_select_list, post_select_node) {
- char *tmp_msg;
-// if (t->pre_select)
-// continue;
- tmp_msg = make_message("%s%p\tpost\t%s\n", msg? msg : "", t, t->status);
- free(msg);
- msg = tmp_msg;
- }
- //PARA_DEBUG_LOG("task list:\n%s", msg);
return msg;
}
*
* \sa \ref task_notify().
*/
-int task_get_notification(struct task *t)
+int task_get_notification(const struct task *t)
{
return t->notification;
}
{
struct task *t;
- list_for_each_entry(t, &s->pre_select_list, pre_select_node)
- task_notify(t, err);
- list_for_each_entry(t, &s->post_select_list, post_select_node)
+ list_for_each_entry(t, &s->task_list, node)
task_notify(t, err);
}