chiark / gitweb /
udevd: handle SIGCHLD before the worker event message
authorKay Sievers <kay.sievers@vrfy.org>
Mon, 13 Jul 2009 01:09:05 +0000 (03:09 +0200)
committerKay Sievers <kay.sievers@vrfy.org>
Mon, 13 Jul 2009 01:09:05 +0000 (03:09 +0200)
We may need to handle SIGCHLD before the queued worker message. The last
reference, from the SIGCHLD or the worker message will clean up the worker
context. In case we receive an unexpected SIGCHLD with an error, we let
the event fail and clean up the worker context.

udev/udevd.c

index 876d2ec1df0133f0f78c1cf847a2ceb9094ea673..14736366d0d299d88d0a3288f5ef0b42357c3774 100644 (file)
@@ -128,6 +128,8 @@ enum worker_state {
 
 struct worker {
        struct udev_list_node node;
+       struct udev *udev;
+       int refcount;
        pid_t pid;
        struct udev_monitor *monitor;
        enum worker_state state;
@@ -176,9 +178,22 @@ static void event_sig_handler(int signum)
        }
 }
 
+static struct worker *worker_ref(struct worker *worker)
+{
+       worker->refcount++;
+       return worker;
+}
+
 static void worker_unref(struct worker *worker)
 {
+       worker->refcount--;
+       if (worker->refcount > 0)
+               return;
+
+       udev_list_node_remove(&worker->node);
        udev_monitor_unref(worker->monitor);
+       childs--;
+       info(worker->udev, "worker [%u] cleaned up\n", worker->pid);
        free(worker);
 }
 
@@ -201,6 +216,9 @@ static void worker_new(struct event *event)
        worker = calloc(1, sizeof(struct worker));
        if (worker == NULL)
                return;
+       /* worker + event reference */
+       worker->refcount = 2;
+       worker->udev = event->udev;
 
        pid = fork();
        switch (pid) {
@@ -339,18 +357,17 @@ static void event_run(struct event *event)
                if (worker->state != WORKER_IDLE)
                        continue;
 
-               worker->event = event;
-               worker->state = WORKER_RUNNING;
-               event->state = EVENT_RUNNING;
                count = udev_monitor_send_device(monitor, worker->monitor, event->dev);
                if (count < 0) {
-                       event->state = EVENT_QUEUED;
-                       worker->event = NULL;
                        err(event->udev, "worker [%u] did not accept message %zi (%m), kill it\n", worker->pid, count);
-                       worker->state = WORKER_KILLED;
                        kill(worker->pid, SIGKILL);
+                       worker->state = WORKER_KILLED;
                        continue;
                }
+               worker_ref(worker);
+               worker->event = event;
+               worker->state = WORKER_RUNNING;
+               event->state = EVENT_RUNNING;
                return;
        }
 
@@ -544,6 +561,7 @@ static void worker_returned(void)
                        worker->event = NULL;
                        if (worker->state != WORKER_KILLED)
                                worker->state = WORKER_IDLE;
+                       worker_unref(worker);
                        break;
                }
        }
@@ -702,17 +720,18 @@ static void handle_signal(struct udev *udev, int signo)
                                if (worker->pid != pid)
                                        continue;
 
-                               /* fail event, if worker died unexpectedly */
-                               if (worker->event != NULL) {
-                                       worker->event->exitcode = status;
+                               info(udev, "worker [%u] exit\n", pid);
+                               if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
                                        err(udev, "worker [%u] unexpectedly returned with status 0x%04x\n", pid, status);
-                                       event_queue_delete(worker->event);
+                                       if (worker->event != NULL) {
+                                               err(udev, "worker [%u] failed while handling '%s'\n", pid, worker->event->devpath);
+                                               worker->event->exitcode = -32;
+                                               event_queue_delete(worker->event);
+                                               /* drop reference from running event */
+                                               worker_unref(worker);
+                                       }
                                }
-
-                               udev_list_node_remove(&worker->node);
                                worker_unref(worker);
-                               childs--;
-                               info(udev, "worker [%u] exit\n", pid);
                                break;
                        }
                }