]> git.tuebingen.mpg.de Git - paraslash.git/blobdiff - sched.c
sched: Rename task->status to task->name.
[paraslash.git] / sched.c
diff --git a/sched.c b/sched.c
index 85f4238b1233de58adcf07e8ac3265a1cbab9f0d..ae6b2a9d3cb0ac6425d94482fc7892641fca5df8 100644 (file)
--- a/sched.c
+++ b/sched.c
 #include "time.h"
 #include "error.h"
 
+struct task {
+       /** The task name supplied when the task was registered(). */
+       char name[255];
+       /** Copied from the task_info struct during task_register(). */
+       void (*pre_select)(struct sched *s, struct task *t);
+       /** Copied from the task_info struct during task_register(). */
+       int (*post_select)(struct sched *s, struct task *t);
+       /** Whether this task is active (>=0) or in error state (<0). */
+       int error;
+       /** 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;
+       /** True if task is in error state and exit status has been queried. */
+       bool dead;
+       /** Usually a pointer to the struct containing this task. */
+       void *context;
+};
+
 static struct timeval now_struct;
 struct timeval *now = &now_struct;
 
@@ -41,6 +60,13 @@ static void sched_preselect(struct sched *s)
        }
 }
 
+static void unlink_and_free_task(struct task *t)
+{
+       PARA_INFO_LOG("freeing task %s\n", t->name);
+       list_del(&t->node);
+       free(t);
+}
+
 //#define SCHED_DEBUG 1
 static inline void call_post_select(struct sched *s, struct task *t)
 {
@@ -57,20 +83,27 @@ static inline void call_post_select(struct sched *s, struct task *t)
        pst = tv2ms(&diff);
        if (pst > 50)
                PARA_WARNING_LOG("%s: post_select time: %lums\n",
-                       t->status, pst);
+                       t->name, pst);
 #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->task_list, node) {
+               if (t->error < 0) {
+                       if (t->dead) /* task has been reaped */
+                               unlink_and_free_task(t);
+                       continue;
+               }
                call_post_select(s, t);
                t->notification = 0;
-               if (t->error < 0)
-                       list_del(&t->node);
+               if (t->error >= 0)
+                       num_running_tasks++;
        }
+       return num_running_tasks;
 }
 
 /**
@@ -91,6 +124,7 @@ static void sched_post_select(struct sched *s)
 int schedule(struct sched *s)
 {
        int ret;
+       unsigned num_running_tasks;
 
        if (!s->select_function)
                s->select_function = para_select;
@@ -116,28 +150,118 @@ again:
                FD_ZERO(&s->wfds);
        }
        clock_get_realtime(now);
-       sched_post_select(s);
-       if (list_empty(&s->task_list))
+       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.
  *
- * \param t The task to add.
- * \param s The scheduler instance to add the task to.
+ * 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.
  *
- * \sa task::pre_select, task::post_select
+ * \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).
  */
-void register_task(struct sched *s, struct task *t)
+int task_reap(struct task **tptr)
 {
-       PARA_INFO_LOG("registering %s (%p)\n", t->status, t);
-       assert(t->post_select);
-       t->notification = 0;
+       struct task *t;
+
+       if (!tptr)
+               return 0;
+       t = *tptr;
+       if (!t)
+               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->name);
+               unlink_and_free_task(t);
+       }
+}
+
+/**
+ * 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->name, sizeof(t->name) - 1, "%s", info->name);
+       t->name[sizeof(t->name) - 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;
        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)
+{
+       return t->context;
 }
 
 /**
@@ -157,7 +281,9 @@ char *get_task_list(struct sched *s)
 
        list_for_each_entry_safe(t, tmp, &s->task_list, node) {
                char *tmp_msg;
-               tmp_msg = make_message("%s%p\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->name);
                free(msg);
                msg = tmp_msg;
        }
@@ -188,7 +314,7 @@ 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->status, para_strerror(err));
+       PARA_INFO_LOG("notifying task %s: %s\n", t->name, para_strerror(err));
        t->notification = -err;
 }
 
@@ -208,6 +334,25 @@ 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->dead)
+               return 0;
+       if (t->error >= 0)
+               return 1;
+       return t->error;
+}
+
 /**
  * Set the notification value of all tasks of a scheduler instance.
  *