Browse Source

Handle SIGCHLD ourselves instead of through GLib

GLib is very careful not to "fetch" any children that it was not asked
to fetch. Thus, if awesome inherits some children, it does not reap them
and starts collecting zombies.

Thus, this commit makes awesome handle SIGCHLD directly: On SIGCHLD, a
byte is written to a pipe. The main loop reads from this pipe and
collects children via waitpid(-1). Unknown children cause a warning to
be printed. We might want to remove this warning if it turns out to be
annoying.

This commit adds 79 lines and removes 89 lines. Thus, this is a net
reduction in lines of codes. This is because previously, we gave the
list of still-running children that we know about to the next awesome
instance we a --reap command line argument. This was added so that
awesome does not get zombie children. However, this commit fixes this
problem and makes all the code for this 'feature' unnecessary.

Fixes: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=886393
Signed-off-by: Uli Schlachter <psychon@znc.in>
Uli Schlachter 1 year ago
parent
commit
6c559e188c
3 changed files with 79 additions and 89 deletions
  1. 50
    2
      awesome.c
  2. 28
    85
      spawn.c
  3. 1
    2
      spawn.h

+ 50
- 2
awesome.c View File

@@ -67,6 +67,9 @@ static struct timeval last_wakeup;
67 67
 /** current limit for the main loop's runtime */
68 68
 static float main_loop_iteration_limit = 0.1;
69 69
 
70
+/** A pipe that is used to asynchronously handle SIGCHLD */
71
+static int sigchld_pipe[2];
72
+
70 73
 /** Call before exiting.
71 74
  */
72 75
 void
@@ -115,6 +118,9 @@ awesome_atexit(bool restart)
115 118
     /* Disconnect *after* closing lua */
116 119
     xcb_cursor_context_free(globalconf.cursor_ctx);
117 120
     xcb_disconnect(globalconf.connection);
121
+
122
+    close(sigchld_pipe[0]);
123
+    close(sigchld_pipe[1]);
118 124
 }
119 125
 
120 126
 /** Restore the client order after a restart */
@@ -448,6 +454,34 @@ signal_fatal(int signum)
448 454
     fatal("signal %d, dumping backtrace\n%s", signum, buf.s);
449 455
 }
450 456
 
457
+/* Signal handler for SIGCHLD. Causes reap_children() to be called. */
458
+static void
459
+signal_child(int signum)
460
+{
461
+    assert(signum == SIGCHLD);
462
+    int res = write(sigchld_pipe[1], " ", 1);
463
+    assert(res == 1);
464
+}
465
+
466
+/* There was a SIGCHLD signal. Read from sigchld_pipe and reap children. */
467
+static gboolean
468
+reap_children(GIOChannel *channel, GIOCondition condition, gpointer user_data)
469
+{
470
+    pid_t child;
471
+    int status;
472
+    char buffer[1024];
473
+    ssize_t result = read(sigchld_pipe[0], &buffer[0], sizeof(buffer));
474
+    if (result < 0)
475
+        fatal("Error reading from signal pipe: %s", strerror(errno));
476
+
477
+    while ((child = waitpid(-1, &status, WNOHANG)) > 0) {
478
+        spawn_child_exited(child, status);
479
+    }
480
+    if (child < 0 && errno != ECHILD)
481
+        warn("waitpid(-1) failed: %s", strerror(errno));
482
+    return TRUE;
483
+}
484
+
451 485
 /** Function to exit on some signals.
452 486
  * \param data currently unused
453 487
  */
@@ -462,7 +496,7 @@ void
462 496
 awesome_restart(void)
463 497
 {
464 498
     awesome_atexit(true);
465
-    execvp(awesome_argv[0], spawn_transform_commandline(awesome_argv));
499
+    execvp(awesome_argv[0], awesome_argv);
466 500
     fatal("execv() failed: %s", strerror(errno));
467 501
 }
468 502
 
@@ -577,7 +611,7 @@ main(int argc, char **argv)
577 611
             replace_wm = true;
578 612
             break;
579 613
           case '\1':
580
-            spawn_handle_reap(optarg);
614
+            /* Silently ignore --reap and its argument */
581 615
             break;
582 616
           default:
583 617
             exit_help(EXIT_FAILURE);
@@ -630,6 +664,16 @@ main(int argc, char **argv)
630 664
         }
631 665
     }
632 666
 
667
+    /* Setup pipe for SIGCHLD processing */
668
+    {
669
+        if (!g_unix_open_pipe(sigchld_pipe, FD_CLOEXEC, NULL))
670
+            fatal("Failed to create pipe");
671
+
672
+        GIOChannel *channel = g_io_channel_unix_new(sigchld_pipe[0]);
673
+        g_io_add_watch(channel, G_IO_IN, reap_children, NULL);
674
+        g_io_channel_unref(channel);
675
+    }
676
+
633 677
     /* register function for signals */
634 678
     g_unix_signal_add(SIGINT, exit_on_signal, NULL);
635 679
     g_unix_signal_add(SIGTERM, exit_on_signal, NULL);
@@ -644,6 +688,10 @@ main(int argc, char **argv)
644 688
     sigaction(SIGSEGV, &sa, 0);
645 689
     signal(SIGPIPE, SIG_IGN);
646 690
 
691
+    sa.sa_handler = signal_child;
692
+    sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
693
+    sigaction(SIGCHLD, &sa, 0);
694
+
647 695
     /* We have no clue where the input focus is right now */
648 696
     globalconf.focus.need_update = true;
649 697
 

+ 28
- 85
spawn.c View File

@@ -62,12 +62,6 @@
62 62
 /** 20 seconds timeout */
63 63
 #define AWESOME_SPAWN_TIMEOUT 20.0
64 64
 
65
-static int
66
-compare_pids(const void *a, const void *b)
67
-{
68
-    return *(GPid *) a - *(GPid *)b;
69
-}
70
-
71 65
 /** Wrapper for unrefing startup sequence.
72 66
  */
73 67
 static inline void
@@ -81,9 +75,20 @@ DO_ARRAY(SnStartupSequence *, SnStartupSequence, a_sn_startup_sequence_unref)
81 75
 /** The array of startup sequence running */
82 76
 static SnStartupSequence_array_t sn_waits;
83 77
 
84
-DO_BARRAY(GPid, pid, DO_NOTHING, compare_pids)
78
+typedef struct {
79
+    GPid pid;
80
+    int exit_callback;
81
+} running_child_t;
82
+
83
+static int
84
+compare_pids(const void *a, const void *b)
85
+{
86
+    return ((running_child_t *) a)->pid - ((running_child_t *) b)->pid;
87
+}
88
+
89
+DO_BARRAY(running_child_t, running_child, DO_NOTHING, compare_pids)
85 90
 
86
-static pid_array_t running_children;
91
+static running_child_array_t running_children;
87 92
 
88 93
 /** Remove a SnStartupSequence pointer from an array and forget about it.
89 94
  * \param s The startup sequence to found, remove and unref.
@@ -264,20 +269,6 @@ spawn_start_notify(client_t *c, const char * startup_id)
264 269
     }
265 270
 }
266 271
 
267
-static void
268
-remove_running_child(GPid pid, gint status, gpointer user_data)
269
-{
270
-    (void) status;
271
-    (void) user_data;
272
-
273
-    GPid *pid_in_array = pid_array_lookup(&running_children, &pid);
274
-    if (pid_in_array != NULL) {
275
-        pid_array_remove(&running_children, pid_in_array);
276
-    } else {
277
-        warn("(Partially) unknown child %d exited?!", (int) pid);
278
-    }
279
-}
280
-
281 272
 /** Initialize program spawner.
282 273
  */
283 274
 void
@@ -291,61 +282,6 @@ spawn_init(void)
291 282
                                                   NULL, NULL);
292 283
 }
293 284
 
294
-void
295
-spawn_handle_reap(const char *arg)
296
-{
297
-    GPid pid = atoll(arg);
298
-    pid_array_insert(&running_children, pid);
299
-    g_child_watch_add((GPid) pid, remove_running_child, NULL);
300
-}
301
-
302
-/** Called right before exit, serialise state in case of a restart.
303
- */
304
-char * const *
305
-spawn_transform_commandline(char **argv)
306
-{
307
-    size_t offset = 0;
308
-    size_t length = 0;
309
-    while(argv[offset] != NULL)
310
-    {
311
-        if(A_STREQ(argv[offset], "--reap"))
312
-            offset += 2;
313
-        else {
314
-            length++;
315
-            offset++;
316
-        }
317
-    }
318
-
319
-    length += 2*running_children.len;
320
-
321
-    const char ** transformed = p_new(const char *, length+1);
322
-    size_t index = 0;
323
-    offset = 0;
324
-    while(argv[index + offset] != NULL)
325
-    {
326
-        if(A_STREQ(argv[index + offset], "--reap"))
327
-            offset += 2;
328
-        else {
329
-            transformed[index] = argv[index + offset];
330
-            index++;
331
-        }
332
-    }
333
-
334
-    foreach(pid, running_children)
335
-    {
336
-        buffer_t buffer;
337
-        buffer_init(&buffer);
338
-        buffer_addf(&buffer, "%d", (int) *pid);
339
-
340
-        transformed[index++] = "--reap";
341
-        transformed[index++] = buffer_detach(&buffer);
342
-        buffer_wipe(&buffer);
343
-    }
344
-    transformed[index++] = NULL;
345
-
346
-    return (char * const *) transformed;
347
-}
348
-
349 285
 static gboolean
350 286
 spawn_launchee_timeout(gpointer context)
351 287
 {
@@ -440,12 +376,20 @@ parse_command(lua_State *L, int idx, GError **error)
440 376
 }
441 377
 
442 378
 /** Callback for when a spawned process exits. */
443
-static void
444
-child_exit_callback(GPid pid, gint status, gpointer user_data)
379
+void
380
+spawn_child_exited(pid_t pid, int status)
445 381
 {
382
+    int exit_callback;
383
+    running_child_t needle = { .pid = pid };
446 384
     lua_State *L = globalconf_get_lua_State();
447
-    int exit_callback = GPOINTER_TO_INT(user_data);
448
-    remove_running_child(pid, status, user_data);
385
+
386
+    running_child_t *child = running_child_array_lookup(&running_children, &needle);
387
+    if (child == NULL) {
388
+        warn("Unknown child %d exited with status %d", (int) pid, status);
389
+        return;
390
+    }
391
+    exit_callback = child->exit_callback;
392
+    running_child_array_remove(&running_children, child);
449 393
 
450 394
     /* 'Decode' the exit status */
451 395
     if (WIFEXITED(status)) {
@@ -570,11 +514,10 @@ luaA_spawn(lua_State *L)
570 514
 
571 515
     if(flags & G_SPAWN_DO_NOT_REAP_CHILD)
572 516
     {
573
-        int exit_callback = LUA_REFNIL;
574 517
         /* Only do this down here to avoid leaks in case of errors */
575
-        luaA_registerfct(L, 6, &exit_callback);
576
-        pid_array_insert(&running_children, pid);
577
-        g_child_watch_add(pid, child_exit_callback, GINT_TO_POINTER(exit_callback));
518
+        running_child_t child = { .pid = pid, .exit_callback = LUA_REFNIL };
519
+        luaA_registerfct(L, 6, &child.exit_callback);
520
+        running_child_array_insert(&running_children, child);
578 521
     }
579 522
 
580 523
     /* push pid on stack */

+ 1
- 2
spawn.h View File

@@ -27,10 +27,9 @@
27 27
 #include <lua.h>
28 28
 
29 29
 void spawn_init(void);
30
-void spawn_handle_reap(const char *);
31
-char * const * spawn_transform_commandline(char **);
32 30
 void spawn_start_notify(client_t *, const char *);
33 31
 int luaA_spawn(lua_State *);
32
+void spawn_child_exited(pid_t, int);
34 33
 
35 34
 #endif
36 35
 // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

Loading…
Cancel
Save