-
Notifications
You must be signed in to change notification settings - Fork 345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
dumb-init child sometimes receives SIGHUP and/or SIGCONT right after start in setsid mode due to race #136
Comments
Just to put some numbers to signals here:
Exit status 129 (128+n): killed by signal 1 The default posix behaviour for I don't have any answers and I haven't looked into anything, this might help someone else do that though :) |
|
This reverts commit 49cd429. dumb-init v1.2.0 busts the container with unknown reason now and then. There is an upstream issue tracking this[0]. [0] Yelp/dumb-init#136 Partial-Bug: #1662383 Change-Id: I310e78307b6577110dc867d7524b5ac98f625c8e
We're seeing this too, very occasionally. The only difference between success and failure is |
@chriswessels are you also using OpenStack Kolla when you see this, or something else? Probably the easiest way to fix this is if we could reproduce it easily. I can spend some time testing with Kolla, but if there's an easier reproduction, that'd be cool :) I'm also not seeing anything strange in the diff between |
@chriskuehl No, we don't use OpenStack Kolla. We do however see this error across plain |
@chriswessels do you have easier way to reproduce this issue? |
We saw a really similar build failure in #164 running on our own Travis tests (we never saw this with CircleCI):
>>> os.WEXITSTATUS(33024)
129 This is really interesting, though would be a lot easier to dig into if we could repro this locally :( This is the diff from 1.1.3 (works according to the initial report) and 1.2.0: diff --git a/dumb-init.c b/dumb-init.c
index cb0cbec..65e69ef 100644
--- a/dumb-init.c
+++ b/dumb-init.c
@@ -15,6 +15,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -250,6 +251,15 @@ int main(int argc, char *argv[]) {
for (i = 1; i <= MAXSIG; i++)
signal(i, dummy);
+ /* detach dumb-init from controlling tty */
+ if (use_setsid && ioctl(STDIN_FILENO, TIOCNOTTY) == -1) {
+ DEBUG(
+ "Unable to detach from controlling tty (errno=%d %s).\n",
+ errno,
+ strerror(errno)
+ );
+ }
+
child_pid = fork();
if (child_pid < 0) {
PRINTERR("Unable to fork. Exiting.\n");
@@ -266,6 +276,14 @@ int main(int argc, char *argv[]) {
);
exit(1);
}
+
+ if (ioctl(STDIN_FILENO, TIOCSCTTY, 0) == -1) {
+ DEBUG(
+ "Unable to attach to controlling tty (errno=%d %s).\n",
+ errno,
+ strerror(errno)
+ );
+ }
DEBUG("setsid complete.\n");
}
execvp(cmd[0], &cmd[0]); I bet the SIGHUP and then SIGCONT received at the start are related to this:
|
I'm able to reproduce this in a test now and working on writing a decent regression test; it seems like there's a race involved here: if the parent half of the fork in dumb-init executes early enough, the SIGHUP and SIGCONT get forwarded early and the child doesn't die (maybe because it hasn't exec'd the child process yet?). If enough of the child half happens "before" the parent half, the SIGHUP/SIGCONT arrive later and typically kill the child. You can force the race and repro this bug reliably by doing this: diff --git a/dumb-init.c b/dumb-init.c
index f437b10..e499717 100644
--- a/dumb-init.c
+++ b/dumb-init.c
@@ -294,6 +294,9 @@ int main(int argc, char *argv[]) {
} else {
/* parent */
DEBUG("Child spawned with PID %d.\n", child_pid);
+ sleep(1);
for (;;) {
int signum;
sigwait(&all_signals, &signum); My guess is that some environments (like Travis and the OpenStack environment mentioned in this ticket) for some reason tend to have the child half working first, maybe due to scheduler quirks. I think @asottile also mentioned seeing something like this extremely rarely (like 1/1,000,000 times on "regular" EC2 hosts) . |
Digging through docs on how ttys work, it appears that any time you detach a session from the controlling tty (ctty), that session is sent a SIGHUP and then SIGCONT with no way to avoid it. The Linux source seems to back this up: https://github.com/torvalds/linux/blob/894025f24bd028942da3e602b87d9f7223109b14/drivers/tty/tty_jobctrl.c#L233-L244 Currently, the reason dumb-init disassociates itself from the ctty is so that it can make the child's new session the tty's primary session ID later on so that job control works. If we want to preserve this behavior, does the parent need to wait for (and drop) the first HUP and CONT it gets...? I wonder if |
Yeah we were seeing this on regular EC2 hosts (c4 and c5 families) at a very low rate and really only at high load. |
These are catchable signals, so there's a way to avoid it. Perhaps I'm missing something obvious, but why not catch (and wait for) the signals before forking? Then the child can't possibly be killed by them, regardless of races. Upon closer reading, I see that's exactly what Chris suggested. 👌 |
Please edit "behavior wired" in the title to (perhaps) "SIGHUP race". |
Investigating https://github.com/bminor/glibc/blob/09533208febe923479261a27b7691abef297d604/termios/tcsetpgrp.c |
@bukzor yeah, here's a WIP patch I was playing with: commit 3c66203dc9d7e66dbcf148ad6d678b9d3d392f32 (HEAD -> fix-race)
Author: Chris Kuehl <ckuehl@yelp.com>
AuthorDate: Mon Jun 4 12:39:14 2018 -0700
Commit: Chris Kuehl <ckuehl@yelp.com>
CommitDate: Mon Jun 4 12:39:14 2018 -0700
WIP fix race
diff --git a/dumb-init.c b/dumb-init.c
index f437b10..c4fd171 100644
--- a/dumb-init.c
+++ b/dumb-init.c
@@ -18,6 +18,7 @@
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <termios.h>
#include <unistd.h>
#include "VERSION.h"
@@ -251,13 +252,50 @@ int main(int argc, char *argv[]) {
for (i = 1; i <= MAXSIG; i++)
signal(i, dummy);
- /* detach dumb-init from controlling tty */
- if (use_setsid && ioctl(STDIN_FILENO, TIOCNOTTY) == -1) {
- DEBUG(
- "Unable to detach from controlling tty (errno=%d %s).\n",
- errno,
- strerror(errno)
- );
+ /*
+ * Detach dumb-init from controlling tty, so that the child's session can
+ * attach to it instead.
+ *
+ * We want the child to be able to be the session leader of the TTY so that
+ * it can do normal job control.
+ */
+ if (use_setsid) {
+ if (ioctl(STDIN_FILENO, TIOCNOTTY) == -1) {
+ DEBUG(
+ "Unable to detach from controlling tty (errno=%d %s).\n",
+ errno,
+ strerror(errno)
+ );
+ } else {
+ DEBUG("Detached from controlling tty, now waiting for SIGHUP + SIGCONT before forking.\n");
+
+ sigset_t hup_only;
+ sigemptyset(&hup_only);
+ sigaddset(&hup_only, SIGHUP);
+
+ sigset_t cont_only;
+ sigemptyset(&cont_only);
+ sigaddset(&cont_only, SIGCONT);
+
+ int signum;
+
+ if (
+ sigwait(&hup_only, &signum) != 0 ||
+ sigwait(&cont_only, &signum) != 0
+ ) {
+ PRINTERR(
+ "Unable to wait for SIGHUP + SIGCONT after detaching from ctty (errno=%d %s). Exiting.\n",
+ errno,
+ strerror(errno)
+ );
+ exit(1);
+ }
+
+ DEBUG("Waited for both SIGHUP and SIGCONT successfully.\n");
+ }
}
child_pid = fork();
@@ -294,6 +332,8 @@ int main(int argc, char *argv[]) {
} else {
/* parent */
DEBUG("Child spawned with PID %d.\n", child_pid);
+ // TODO: just for debug, forcing the race
+ sleep(1);
+ DEBUG("Done sleeping.\n");
for (;;) {
int signum;
sigwait(&all_signals, &signum); This appears to work but I haven't had time to dig into it in detail to ensure there aren't any edge cases or other issues. Edit: We can probably actually go ahead and fork without waiting for the SIGHUP/SIGCONT as long as dumb-init knows to drop (not forward) the first two signals it gets. May be slightly faster? |
Here's the musl implementatio of The implementation is here: https://github.com/torvalds/linux/blob/894025f24bd028942da3e602b87d9f7223109b14/drivers/tty/tty_jobctrl.c#L459 |
I get suspicious (I'm sure you do too) when a problem starts adding more and more code. |
iirc we do it this way to avoid a race (we want the child to be foregrounded from the start, not some time after the fork). If we did the above, I think we'd either have a slight race (child could exec the new process before it gets the controlling TTY), or we'd have to coordinate between the child and parent dumb-init processes to delay the |
Agreed, that's why I've been kind of hesitant about that patch I posted above :\ |
Just released v1.2.2 of dumb-init which has a patch that should prevent this: https://github.com/Yelp/dumb-init/releases/tag/v1.2.2 If you're able to try it out, would love to hear if this fixes things for you. |
OpenStack Kolla[0] project is using dumb-init v1.1.3 before, which works very well. Recently, we upgrade to v1.2.0. But the container failed now and then. After some debug, I found the difference.
here is dumb-init v1.1.3's debug log[2]
here is dumb-init v1.2.0's debug log[3], which container run successfully.
No idea why it received a signal 18.
Here is dumb-init v1.2.0's debug log[4], which container run failed.
it received singal 18, then signal 17, and children are killed.
I compared the source code of dumb-init v1.1.3 and v 1.2.0. But can not explain it. Any idea on this?
all these test is based on the same kolla code. the only variable is dumb-init version.
[0] https://github.com/openstack/kolla
[1] https://review.openstack.org/#/c/424832/
[2] http://logs.openstack.org/08/429908/3/check/gate-kolla-ansible-dsvm-deploy-ubuntu-source-ubuntu-xenial-nv/260697e/console.html#_2017-02-07_01_09_52_334863
[3] http://logs.openstack.org/81/429681/3/check/gate-kolla-ansible-dsvm-deploy-centos-source-centos-7-nv/d2b0b1b/console.html#_2017-02-07_01_06_13_845614
[4] http://logs.openstack.org/81/429681/3/check/gate-kolla-ansible-dsvm-deploy-centos-source-centos-7-nv/d2b0b1b/console.html#_2017-02-07_01_06_13_814198
The text was updated successfully, but these errors were encountered: