From aad6f40572a0153fe0c049ee85f6199d48ed564c Mon Sep 17 00:00:00 2001 From: Brett Gardner Date: Wed, 28 Sep 2022 16:46:56 +1000 Subject: [PATCH 1/5] Added optional -d/--delay=s:t option to delay an external signal by a specified amount of seconds. --- dumb-init.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/dumb-init.c b/dumb-init.c index a97ab41..e4b789c 100644 --- a/dumb-init.c +++ b/dumb-init.c @@ -39,6 +39,8 @@ // Indices are one-indexed (signal 1 is at index 1). Index zero is unused. // User-specified signal rewriting. int signal_rewrite[MAXSIG + 1] = {[0 ... MAXSIG] = -1}; +// User-specified signal delay. +int signal_delay[MAXSIG + 1] = {[0 ... MAXSIG] = -1}; // One-time ignores due to TTY quirks. 0 = no skip, 1 = skip the next-received signal. char signal_temporary_ignores[MAXSIG + 1] = {[0 ... MAXSIG] = 0}; @@ -60,7 +62,15 @@ int translate_signal(int signum) { } } -void forward_signal(int signum) { +void forward_signal(int signum, int external_signal) { + + int delay = signal_delay[signum]; + + if (external_signal == 1 && delay != -1) { + DEBUG("Delay signal %d by %d seconds.\n", signum, delay); + sleep(delay); + } + signum = translate_signal(signum); if (signum != 0) { kill(use_setsid ? -child_pid : child_pid, signum); @@ -110,13 +120,13 @@ void handle_signal(int signum) { } if (killed_pid == child_pid) { - forward_signal(SIGTERM); // send SIGTERM to any remaining children + forward_signal(SIGTERM, 0); // send SIGTERM to any remaining children DEBUG("Child exited with status %d. Goodbye.\n", exit_status); exit(exit_status); } } } else { - forward_signal(signum); + forward_signal(signum, 1); if (signum == SIGTSTP || signum == SIGTTOU || signum == SIGTTIN) { DEBUG("Suspending self due to TTY signal.\n"); kill(getpid(), SIGSTOP); @@ -139,6 +149,9 @@ void print_help(char *argv[]) { " -r, --rewrite s:r Rewrite received signal s to new signal r before proxying.\n" " To ignore (not proxy) a signal, rewrite it to 0.\n" " This option can be specified multiple times.\n" + " -d, --delay s:t Delay received signal s by t seconds.\n" + " This is the incoming signal, before any rewrites.\n" + " This option can be specified multiple times.\n" " -v, --verbose Print debugging information to stderr.\n" " -h, --help Print this help message and exit.\n" " -V, --version Print the current version and exit.\n" @@ -149,6 +162,31 @@ void print_help(char *argv[]) { ); } +void print_delay_signum_help() { + fprintf( + stderr, + "Usage: -d option takes :, where " + "is between 1 and %d and is greater than zero\n" + "This option can be specified multiple times.\n" + "Use --help for full usage.\n", + MAXSIG + ); + exit(1); +} + +void parse_delay_signum(char *arg) { + int signum, delay; + if ( + sscanf(arg, "%d:%d", &signum, &delay) == 2 && + (signum >= 1 && signum <= MAXSIG) && + (delay > 0) + ) { + signal_delay[signum] = delay; + } else { + print_delay_signum_help(); + } +} + void print_rewrite_signum_help() { fprintf( stderr, @@ -186,11 +224,12 @@ char **parse_command(int argc, char *argv[]) { {"help", no_argument, NULL, 'h'}, {"single-child", no_argument, NULL, 'c'}, {"rewrite", required_argument, NULL, 'r'}, + {"delay", required_argument, NULL, 'd'}, {"verbose", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0}, }; - while ((opt = getopt_long(argc, argv, "+hvVcr:", long_options, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "+hvVcr:d:", long_options, NULL)) != -1) { switch (opt) { case 'h': print_help(argv); @@ -207,6 +246,9 @@ char **parse_command(int argc, char *argv[]) { case 'r': parse_rewrite_signum(optarg); break; + case 'd': + parse_delay_signum(optarg); + break; default: exit(1); } From 04c369c1cc88e038d82bf4206f64acb24ff5e06b Mon Sep 17 00:00:00 2001 From: Brett Gardner Date: Wed, 28 Sep 2022 16:49:35 +1000 Subject: [PATCH 2/5] Updated README for signal delay --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 87d6cf0..2c66ffd 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,15 @@ One caveat with this feature: for job control signals (`SIGTSTP`, `SIGTTIN`, `SIGTTOU`), dumb-init will always suspend itself after receiving the signal, even if you rewrite it to something else. +### Signal delaying + +dumb-init allows delaying incoming signals before proxying them. This is +useful in cases where a container would still be handling client requests +after being signaled for a small amount of time. eg Fargate Spot ECS tasks being +used as targets for an AWS ElasticLoadBalancing Targetgroup + +For example, to delay the signal SIGTERM (number 15) by 5 seconds +just add `--delay 15:5` on the command line. ## Installing inside Docker containers From 1b4b0ddb947abbf20034077c609647db67e12286 Mon Sep 17 00:00:00 2001 From: Brett Gardner Date: Thu, 29 Sep 2022 09:51:24 +1000 Subject: [PATCH 3/5] Changed signal delay to be non blocking --- README.md | 2 +- dumb-init.c | 65 +++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2c66ffd..a094bae 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ even if you rewrite it to something else. dumb-init allows delaying incoming signals before proxying them. This is useful in cases where a container would still be handling client requests -after being signaled for a small amount of time. eg Fargate Spot ECS tasks being +after being signaled for a small amount of time. eg Fargate Spot ECS tasks being used as targets for an AWS ElasticLoadBalancing Targetgroup For example, to delay the signal SIGTERM (number 15) by 5 seconds diff --git a/dumb-init.c b/dumb-init.c index e4b789c..699c648 100644 --- a/dumb-init.c +++ b/dumb-init.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "VERSION.h" #define PRINTERR(...) do { \ @@ -40,13 +41,16 @@ // User-specified signal rewriting. int signal_rewrite[MAXSIG + 1] = {[0 ... MAXSIG] = -1}; // User-specified signal delay. -int signal_delay[MAXSIG + 1] = {[0 ... MAXSIG] = -1}; +unsigned int signal_delay[MAXSIG + 1] = {[0 ... MAXSIG] = 0}; +time_t signal_alarms[MAXSIG + 1] = {[0 ... MAXSIG] = 0}; + // One-time ignores due to TTY quirks. 0 = no skip, 1 = skip the next-received signal. char signal_temporary_ignores[MAXSIG + 1] = {[0 ... MAXSIG] = 0}; pid_t child_pid = -1; char debug = 0; char use_setsid = 1; +char use_delay = 0; int translate_signal(int signum) { if (signum <= 0 || signum > MAXSIG) { @@ -62,15 +66,7 @@ int translate_signal(int signum) { } } -void forward_signal(int signum, int external_signal) { - - int delay = signal_delay[signum]; - - if (external_signal == 1 && delay != -1) { - DEBUG("Delay signal %d by %d seconds.\n", signum, delay); - sleep(delay); - } - +void forward_signal(int signum) { signum = translate_signal(signum); if (signum != 0) { kill(use_setsid ? -child_pid : child_pid, signum); @@ -101,6 +97,9 @@ void forward_signal(int signum, int external_signal) { * */ void handle_signal(int signum) { + + time_t epoch; + DEBUG("Received signal %d.\n", signum); if (signal_temporary_ignores[signum] == 1) { @@ -120,13 +119,48 @@ void handle_signal(int signum) { } if (killed_pid == child_pid) { - forward_signal(SIGTERM, 0); // send SIGTERM to any remaining children + forward_signal(SIGTERM); // send SIGTERM to any remaining children DEBUG("Child exited with status %d. Goodbye.\n", exit_status); exit(exit_status); } } + } else if (use_delay == 1 && signum == SIGALRM) { + //Look for any overdue signals and forward them + //Note this means that SIGLARM is NOT propagated to children + epoch = time(NULL); + + DEBUG("Forwarding delayed signals.\n"); + + for (int signum = 1; signum <= MAXSIG; signum++) { + if (signal_alarms[signum] != 0 && signal_alarms[signum] <= epoch) { + signal_alarms[signum] = 0; + forward_signal(signum); + } + } + } else { - forward_signal(signum, 1); + unsigned int delay = signal_delay[signum]; + + if (delay != 0) { + DEBUG("Delay signal %d by %d seconds.\n", signum, delay); + + if (signal_alarms[signum] != 0) { + DEBUG("Signal %d already received and waiting. Ignoring.\n", signum); + return; + } + + epoch = time(NULL); + epoch += delay; + + DEBUG("Will signal %d at %ld.\n", signum, epoch); + + signal_alarms[signum] = epoch; + + alarm(delay); + + } else { + forward_signal(signum); + } if (signum == SIGTSTP || signum == SIGTTOU || signum == SIGTTIN) { DEBUG("Suspending self due to TTY signal.\n"); kill(getpid(), SIGSTOP); @@ -152,6 +186,7 @@ void print_help(char *argv[]) { " -d, --delay s:t Delay received signal s by t seconds.\n" " This is the incoming signal, before any rewrites.\n" " This option can be specified multiple times.\n" + " If this option is used, SIGALRM is NOT propagated to the child process.\n" " -v, --verbose Print debugging information to stderr.\n" " -h, --help Print this help message and exit.\n" " -V, --version Print the current version and exit.\n" @@ -175,6 +210,7 @@ void print_delay_signum_help() { } void parse_delay_signum(char *arg) { + //TODO - Don't allow SIGLARM to be delayed? int signum, delay; if ( sscanf(arg, "%d:%d", &signum, &delay) == 2 && @@ -182,6 +218,7 @@ void parse_delay_signum(char *arg) { (delay > 0) ) { signal_delay[signum] = delay; + use_delay = 1; } else { print_delay_signum_help(); } @@ -302,6 +339,10 @@ int main(int argc, char *argv[]) { signal(i, dummy); } + if (use_delay) { + DEBUG("Delays specified. SIGALRM will not be propagated.\n"); + } + /* * Detach dumb-init from controlling tty, so that the child's session can * attach to it instead. From d03485346697404a0a6bb2ad0bead5222b364bfb Mon Sep 17 00:00:00 2001 From: Brett Gardner Date: Thu, 29 Sep 2022 13:23:52 +1000 Subject: [PATCH 4/5] ENCLOUD-1256: Scheduling different alarm doesn't destroy earlier schedule. Fixed cli tests --- dumb-init.c | 33 +++++++++++++++++++++++++++++---- tests/cli_test.py | 4 ++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/dumb-init.c b/dumb-init.c index 699c648..ed460f4 100644 --- a/dumb-init.c +++ b/dumb-init.c @@ -51,6 +51,7 @@ pid_t child_pid = -1; char debug = 0; char use_setsid = 1; char use_delay = 0; +time_t alarm_set = 0; int translate_signal(int signum) { if (signum <= 0 || signum > MAXSIG) { @@ -127,17 +128,38 @@ void handle_signal(int signum) { } else if (use_delay == 1 && signum == SIGALRM) { //Look for any overdue signals and forward them //Note this means that SIGLARM is NOT propagated to children + alarm_set = 0; epoch = time(NULL); DEBUG("Forwarding delayed signals.\n"); + time_t closest_epoch; + closest_epoch = 0; + for (int signum = 1; signum <= MAXSIG; signum++) { - if (signal_alarms[signum] != 0 && signal_alarms[signum] <= epoch) { - signal_alarms[signum] = 0; - forward_signal(signum); + if (signal_alarms[signum] != 0) { + + //If epoch now or in the past, forward, otherwise find the closest epoch in the future + + if (signal_alarms[signum] <= epoch) { + signal_alarms[signum] = 0; + forward_signal(signum); + } else { + if (closest_epoch == 0) { + closest_epoch = signal_alarms[signum]; + } else if (signal_alarms[signum] < closest_epoch) { + closest_epoch = signal_alarms[signum]; + } + } } } + if (closest_epoch != 0) { + DEBUG("Rescheduling alarm for %ld.\n", closest_epoch); + alarm_set = closest_epoch; + alarm((int)(closest_epoch - epoch)); + } + } else { unsigned int delay = signal_delay[signum]; @@ -156,7 +178,10 @@ void handle_signal(int signum) { signal_alarms[signum] = epoch; - alarm(delay); + if (alarm_set == 0 || alarm_set > epoch) { + alarm_set = epoch; + alarm(delay); + } } else { forward_signal(signum); diff --git a/tests/cli_test.py b/tests/cli_test.py index 0142963..04d3311 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -79,6 +79,10 @@ def test_help_message(flag, current_version): b' -r, --rewrite s:r Rewrite received signal s to new signal r before proxying.\n' b' To ignore (not proxy) a signal, rewrite it to 0.\n' b' This option can be specified multiple times.\n' + b' -d, --delay s:t Delay received signal s by t seconds.\n' + b' This is the incoming signal, before any rewrites.\n' + b' This option can be specified multiple times.\n' + b' If this option is used, SIGALRM is NOT propagated to the child process.\n' b' -v, --verbose Print debugging information to stderr.\n' b' -h, --help Print this help message and exit.\n' b' -V, --version Print the current version and exit.\n' From 46696df6aebe10bb86ecbd4840e00fceab0caa29 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 30 Sep 2022 05:21:39 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- dumb-init.c | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/dumb-init.c b/dumb-init.c index ed460f4..2e9cf5b 100644 --- a/dumb-init.c +++ b/dumb-init.c @@ -128,37 +128,37 @@ void handle_signal(int signum) { } else if (use_delay == 1 && signum == SIGALRM) { //Look for any overdue signals and forward them //Note this means that SIGLARM is NOT propagated to children - alarm_set = 0; + alarm_set = 0; epoch = time(NULL); DEBUG("Forwarding delayed signals.\n"); - time_t closest_epoch; - closest_epoch = 0; + time_t closest_epoch; + closest_epoch = 0; for (int signum = 1; signum <= MAXSIG; signum++) { if (signal_alarms[signum] != 0) { - //If epoch now or in the past, forward, otherwise find the closest epoch in the future - - if (signal_alarms[signum] <= epoch) { - signal_alarms[signum] = 0; - forward_signal(signum); - } else { - if (closest_epoch == 0) { - closest_epoch = signal_alarms[signum]; - } else if (signal_alarms[signum] < closest_epoch) { - closest_epoch = signal_alarms[signum]; - } - } + //If epoch now or in the past, forward, otherwise find the closest epoch in the future + + if (signal_alarms[signum] <= epoch) { + signal_alarms[signum] = 0; + forward_signal(signum); + } else { + if (closest_epoch == 0) { + closest_epoch = signal_alarms[signum]; + } else if (signal_alarms[signum] < closest_epoch) { + closest_epoch = signal_alarms[signum]; + } + } } } - if (closest_epoch != 0) { - DEBUG("Rescheduling alarm for %ld.\n", closest_epoch); - alarm_set = closest_epoch; - alarm((int)(closest_epoch - epoch)); - } + if (closest_epoch != 0) { + DEBUG("Rescheduling alarm for %ld.\n", closest_epoch); + alarm_set = closest_epoch; + alarm((int)(closest_epoch - epoch)); + } } else { unsigned int delay = signal_delay[signum]; @@ -178,10 +178,10 @@ void handle_signal(int signum) { signal_alarms[signum] = epoch; - if (alarm_set == 0 || alarm_set > epoch) { - alarm_set = epoch; - alarm(delay); - } + if (alarm_set == 0 || alarm_set > epoch) { + alarm_set = epoch; + alarm(delay); + } } else { forward_signal(signum);