Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
112 changes: 110 additions & 2 deletions dumb-init.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <time.h>
#include "VERSION.h"

#define PRINTERR(...) do { \
Expand All @@ -39,12 +40,18 @@
// 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.
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;
time_t alarm_set = 0;

int translate_signal(int signum) {
if (signum <= 0 || signum > MAXSIG) {
Expand Down Expand Up @@ -91,6 +98,9 @@ void forward_signal(int signum) {
*
*/
void handle_signal(int signum) {

time_t epoch;

DEBUG("Received signal %d.\n", signum);

if (signal_temporary_ignores[signum] == 1) {
Expand All @@ -115,8 +125,67 @@ void handle_signal(int signum) {
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
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) {

//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 {
forward_signal(signum);
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;

if (alarm_set == 0 || alarm_set > epoch) {
alarm_set = 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);
Expand All @@ -139,6 +208,10 @@ 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"
" 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"
Expand All @@ -149,6 +222,33 @@ void print_help(char *argv[]) {
);
}

void print_delay_signum_help() {
fprintf(
stderr,
"Usage: -d option takes <signum>:<seconds>, where <signum> "
"is between 1 and %d and <seconds> 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) {
//TODO - Don't allow SIGLARM to be delayed?
int signum, delay;
if (
sscanf(arg, "%d:%d", &signum, &delay) == 2 &&
(signum >= 1 && signum <= MAXSIG) &&
(delay > 0)
) {
signal_delay[signum] = delay;
use_delay = 1;
} else {
print_delay_signum_help();
}
}

void print_rewrite_signum_help() {
fprintf(
stderr,
Expand Down Expand Up @@ -186,11 +286,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);
Expand All @@ -207,6 +308,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);
}
Expand Down Expand Up @@ -260,6 +364,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.
Expand Down
4 changes: 4 additions & 0 deletions tests/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down