]> git.tuebingen.mpg.de Git - paraslash.git/blobdiff - sched.c
task_register() conversion: signal task
[paraslash.git] / sched.c
diff --git a/sched.c b/sched.c
index 101cc4c0dad3706e841881b2a6d2989825fc58b0..6b8e09108308336676e48c4d8c9e84eb6e388431 100644 (file)
--- a/sched.c
+++ b/sched.c
@@ -75,8 +75,11 @@ static unsigned sched_post_select(struct sched *s)
        unsigned num_running_tasks = 0;
 
        list_for_each_entry_safe(t, tmp, &s->task_list, node) {
-               if (t->error < 0)
+               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) {
@@ -138,6 +141,53 @@ again:
        goto again;
 }
 
+/**
+ * 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.
  *
@@ -199,6 +249,7 @@ struct task *task_register(struct task_info *info, struct sched *s)
        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;
@@ -239,7 +290,7 @@ 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\t%s\n", msg? msg : "", t,
-                       t->error < 0? "zombie" : "running",
+                       t->error < 0? (t->dead? "dead" : "zombie") : "running",
                        t->status);
                free(msg);
                msg = tmp_msg;