From 8574e10c2af1ceb49652b5a2a266c68105ae198a Mon Sep 17 00:00:00 2001 From: zimbatm Date: Fri, 19 Jan 2018 12:40:20 +0000 Subject: [PATCH] add a -w option to warn on reaping children Well designed software should not produce any zombie or re-parenting processes. This adds an option to warn in the logs when reaping of zombies is happening so that it can be monitored and fixed in subsequent releases of the software. --- ci/run_build.sh | 2 +- src/tini.c | 13 +++++++++++-- test/run_inner_tests.py | 14 +++++++++++++- test/run_outer_tests.py | 1 + 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/ci/run_build.sh b/ci/run_build.sh index 652320e..d0e05e0 100755 --- a/ci/run_build.sh +++ b/ci/run_build.sh @@ -105,7 +105,7 @@ if [[ -n "${ARCH_NATIVE-}" ]]; then # We try running binaries named after flags (both valid and invalid # flags) and test that they run. - for flag in h s x; do + for flag in h s w x; do bin="-${flag}" echo "Testing $tini can run binary: ${bin}" cp "$(which true)" "${BIN_TEST_DIR}/${bin}" diff --git a/src/tini.c b/src/tini.c index 90ee3e8..b6d4fce 100644 --- a/src/tini.c +++ b/src/tini.c @@ -45,11 +45,11 @@ static unsigned int verbosity = DEFAULT_VERBOSITY; #ifdef PR_SET_CHILD_SUBREAPER #define HAS_SUBREAPER 1 -#define OPT_STRING "hsvgl" +#define OPT_STRING "hsvwgl" #define SUBREAPER_ENV_VAR "TINI_SUBREAPER" #else #define HAS_SUBREAPER 0 -#define OPT_STRING "hvgl" +#define OPT_STRING "hvwgl" #endif #define VERBOSITY_ENV_VAR "TINI_VERBOSITY" @@ -62,6 +62,8 @@ static unsigned int subreaper = 0; #endif static unsigned int kill_process_group = 0; +static unsigned int warn_on_reap = 0; + static struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 }; static const char reaper_warning[] = "Tini is not running as PID 1 " @@ -195,6 +197,7 @@ void print_usage(char* const name, FILE* const file) { fprintf(file, " -s: Register as a process subreaper (requires Linux >= 3.4).\n"); #endif fprintf(file, " -v: Generate more verbose output. Repeat up to 3 times.\n"); + fprintf(file, " -w: Print a warning when processes are getting reaped.\n"); fprintf(file, " -g: Send signals to the child's process group.\n"); fprintf(file, " -l: Show license and exit.\n"); #endif @@ -247,6 +250,10 @@ int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[ verbosity++; break; + case 'w': + warn_on_reap++; + break; + case 'g': kill_process_group++; break; @@ -470,6 +477,8 @@ int reap_zombies(const pid_t child_pid, int* const child_exitcode_ptr) { PRINT_FATAL("Main child exited for unknown reason"); return 1; } + } else if (warn_on_reap > 0) { + PRINT_WARNING("Reaped zombie process with pid=%i", current_pid); } // Check if other childs have been reaped. diff --git a/test/run_inner_tests.py b/test/run_inner_tests.py index 653e6f2..9fb99c8 100755 --- a/test/run_inner_tests.py +++ b/test/run_inner_tests.py @@ -57,9 +57,22 @@ def main(): # and will output the error message here. assert "zombie reaping won't work" not in err, "Warning message was output!" ret = p.wait() + assert "Reaped zombie process with pid=" not in err, "Warning message was output!" assert ret == 0, "Reaping test failed!\nOUT: %s\nERR: %s" % (out, err) + if not args_disabled: + print "Running reaping display test ({0} with env {1})".format(" ".join(target), env) + p = subprocess.Popen(target + ["-w", os.path.join(src, "test", "reaping", "stage_1.py")], + env=dict(os.environ, **env), + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + out, err = p.communicate() + ret = p.wait() + assert "Reaped zombie process with pid=" in err, "Warning message was output!" + assert ret == 0, "Reaping display test failed!\nOUT: %s\nERR: %s" % (out, err) + + # Run the signals test for signum in [signal.SIGTERM, signal.SIGUSR1, signal.SIGUSR2]: print "running signal test for: {0} ({1} with env {2})".format(signum, " ".join(target), env) @@ -94,7 +107,6 @@ def main(): ret = p.wait() assert ret == 1, "Reaping test succeeded (it should have failed)!" - # Test that the signals are properly in place here. print "running signal configuration test" diff --git a/test/run_outer_tests.py b/test/run_outer_tests.py index 35c3c5a..b500ded 100755 --- a/test/run_outer_tests.py +++ b/test/run_outer_tests.py @@ -158,6 +158,7 @@ def main(): Command(functional_base_cmd + ["-z"], fail_cmd).run(retcode=127 if args_disabled else 1) Command(functional_base_cmd + ["-h"], fail_cmd).run(retcode=127 if args_disabled else 0) Command(functional_base_cmd + ["zzzz"], fail_cmd).run(retcode=127) + Command(functional_base_cmd + ["-w"], fail_cmd).run(retcode=127 if args_disabled else 0) # Valgrind test (we only run this on the dynamic version, because otherwise Valgrind may bring up plenty of errors that are # actually from libc)