Merge pull request #6 from chriskuehl/process-groups
Add process group support
This commit is contained in:
commit
7a455de4e3
7 changed files with 107 additions and 31 deletions
|
@ -3,7 +3,7 @@ FROM debian:jessie
|
||||||
MAINTAINER Chris Kuehl <ckuehl@yelp.com>
|
MAINTAINER Chris Kuehl <ckuehl@yelp.com>
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
build-essential devscripts equivs && \
|
build-essential devscripts equivs procps psmisc && \
|
||||||
apt-get clean
|
apt-get clean
|
||||||
WORKDIR /mnt
|
WORKDIR /mnt
|
||||||
|
|
||||||
|
|
8
Makefile
8
Makefile
|
@ -1,7 +1,9 @@
|
||||||
|
CFLAGS=-std=gnu99 -static -Wall -Werror
|
||||||
|
|
||||||
DOCKER_RUN_TEST := docker run -v $(PWD):/mnt:ro
|
DOCKER_RUN_TEST := docker run -v $(PWD):/mnt:ro
|
||||||
DOCKER_DEB_TEST := sh -euxc ' \
|
DOCKER_DEB_TEST := sh -euxc ' \
|
||||||
apt-get update \
|
apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends procps \
|
&& apt-get install -y --no-install-recommends procps psmisc \
|
||||||
&& (which timeout || apt-get install -y --no-install-recommends timeout) \
|
&& (which timeout || apt-get install -y --no-install-recommends timeout) \
|
||||||
&& dpkg -i /mnt/dist/*.deb \
|
&& dpkg -i /mnt/dist/*.deb \
|
||||||
&& cd /mnt \
|
&& cd /mnt \
|
||||||
|
@ -9,7 +11,7 @@ DOCKER_DEB_TEST := sh -euxc ' \
|
||||||
'
|
'
|
||||||
DOCKER_PYTHON_TEST := sh -uexc ' \
|
DOCKER_PYTHON_TEST := sh -uexc ' \
|
||||||
apt-get update \
|
apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends python-pip build-essential procps \
|
&& apt-get install -y --no-install-recommends python-pip build-essential procps psmisc \
|
||||||
&& (which timeout || apt-get install -y --no-install-recommends timeout) \
|
&& (which timeout || apt-get install -y --no-install-recommends timeout) \
|
||||||
&& tmp=$$(mktemp -d) \
|
&& tmp=$$(mktemp -d) \
|
||||||
&& cp -r /mnt/* "$$tmp" \
|
&& cp -r /mnt/* "$$tmp" \
|
||||||
|
@ -22,7 +24,7 @@ DOCKER_PYTHON_TEST := sh -uexc ' \
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build:
|
build:
|
||||||
$(CC) -static -Wall -Werror -o dumb-init dumb-init.c
|
$(CC) $(CFLAGS) -o dumb-init dumb-init.c
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean: clean-tox
|
clean: clean-tox
|
||||||
|
|
59
dumb-init.c
59
dumb-init.c
|
@ -5,9 +5,10 @@
|
||||||
* Usage:
|
* Usage:
|
||||||
* ./dumb-init python -c 'while True: pass'
|
* ./dumb-init python -c 'while True: pass'
|
||||||
*
|
*
|
||||||
* To get debug output on stderr, run with DUMB_INIT_DEBUG=1.
|
* To get debug output on stderr, run with DUMB_INIT_DEBUG=1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
@ -16,18 +17,24 @@
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define DEBUG(...) do { \
|
||||||
|
if (debug) { \
|
||||||
|
fprintf(stderr, __VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
pid_t child = -1;
|
pid_t child = -1;
|
||||||
char debug = 0;
|
char debug = 0;
|
||||||
|
char use_process_group = 1;
|
||||||
|
|
||||||
void signal_handler(int signum) {
|
void signal_handler(int signum) {
|
||||||
if (debug)
|
DEBUG("Received signal %d.\n", signum);
|
||||||
fprintf(stderr, "Received signal %d.\n", signum);
|
|
||||||
|
|
||||||
if (child > 0) {
|
if (child > 0) {
|
||||||
kill(child, signum);
|
kill(use_process_group ? -child : child, signum);
|
||||||
|
DEBUG("Forwarded signal to child.\n");
|
||||||
if (debug)
|
} else {
|
||||||
fprintf(stderr, "Forwarded signal to child.\n");
|
DEBUG("Didn't forward signal, no child exists yet.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,14 +56,18 @@ void print_help(char *argv[]) {
|
||||||
"\n"
|
"\n"
|
||||||
"The proxy dies when your process dies, so it must not double-fork or do other\n"
|
"The proxy dies when your process dies, so it must not double-fork or do other\n"
|
||||||
"weird things (this is basically a requirement for doing things sanely in\n"
|
"weird things (this is basically a requirement for doing things sanely in\n"
|
||||||
"Docker anyway).\n",
|
"Docker anyway).\n"
|
||||||
|
"\n"
|
||||||
|
"By default, dumb-init starts a process group and kills all processes in it.\n"
|
||||||
|
"This is usually useful behavior, but if for some reason you wish to disable\n"
|
||||||
|
"it, run with DUMB_INIT_PROCESS_GROUP=0.\n",
|
||||||
argv[0]
|
argv[0]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
int signum, exit_status, status = 0;
|
int signum, exit_status, status = 0;
|
||||||
char *debug_env;
|
char *debug_env, *pgroup_env;
|
||||||
|
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
print_help(argv);
|
print_help(argv);
|
||||||
|
@ -64,9 +75,16 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_env = getenv("DUMB_INIT_DEBUG");
|
debug_env = getenv("DUMB_INIT_DEBUG");
|
||||||
if (debug_env && strcmp(debug_env, "1") == 0)
|
if (debug_env && strcmp(debug_env, "1") == 0) {
|
||||||
debug = 1;
|
debug = 1;
|
||||||
|
DEBUG("Running in debug mode.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
pgroup_env = getenv("DUMB_INIT_PROCESS_GROUP");
|
||||||
|
if (pgroup_env && strcmp(pgroup_env, "0") == 0) {
|
||||||
|
use_process_group = 0;
|
||||||
|
DEBUG("Not running in process group mode.\n");
|
||||||
|
}
|
||||||
|
|
||||||
/* register signal handlers */
|
/* register signal handlers */
|
||||||
for (signum = 1; signum < 32; signum++) {
|
for (signum = 1; signum < 32; signum++) {
|
||||||
|
@ -88,16 +106,29 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child == 0) {
|
if (child == 0) {
|
||||||
|
if (use_process_group) {
|
||||||
|
pid_t result = setpgid(0, 0);
|
||||||
|
if (result != 0) {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"Unable to create process group (errno=%d %s). Exiting.\n",
|
||||||
|
errno,
|
||||||
|
strerror(errno)
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
DEBUG("Set process group ID of child to its own PID.\n");
|
||||||
|
}
|
||||||
|
|
||||||
execvp(argv[1], &argv[1]);
|
execvp(argv[1], &argv[1]);
|
||||||
} else {
|
} else {
|
||||||
if (debug)
|
DEBUG("Child spawned with PID %d.\n", child);
|
||||||
fprintf(stderr, "Child spawned with PID %d.\n", child);
|
|
||||||
|
|
||||||
|
/* wait for child to exit */
|
||||||
waitpid(child, &status, 0);
|
waitpid(child, &status, 0);
|
||||||
exit_status = WEXITSTATUS(status);
|
exit_status = WEXITSTATUS(status);
|
||||||
|
|
||||||
if (debug)
|
DEBUG("Child exited with status %d. Goodbye.\n", exit_status);
|
||||||
fprintf(stderr, "Child exited with status %d, goodbye.\n", exit_status);
|
|
||||||
|
|
||||||
return exit_status;
|
return exit_status;
|
||||||
}
|
}
|
||||||
|
|
6
test
6
test
|
@ -11,9 +11,15 @@ fi
|
||||||
echo "Running with dumb-init at '$dumb_init_bin'"
|
echo "Running with dumb-init at '$dumb_init_bin'"
|
||||||
|
|
||||||
run_tests() {
|
run_tests() {
|
||||||
|
export DUMB_INIT_PROCESS_GROUP
|
||||||
|
for DUMB_INIT_PROCESS_GROUP in 0 1; do
|
||||||
./test-proxies-signals "$dumb_init_bin"
|
./test-proxies-signals "$dumb_init_bin"
|
||||||
./test-exit-status "$dumb_init_bin"
|
./test-exit-status "$dumb_init_bin"
|
||||||
./test-help-message "$dumb_init_bin"
|
./test-help-message "$dumb_init_bin"
|
||||||
|
done
|
||||||
|
|
||||||
|
DUMB_INIT_PROCESS_GROUP=0 ./test-pgroup "$dumb_init_bin" 4
|
||||||
|
DUMB_INIT_PROCESS_GROUP=1 ./test-pgroup "$dumb_init_bin" 0
|
||||||
}
|
}
|
||||||
|
|
||||||
cd tests
|
cd tests
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
#!/bin/bash -eux
|
#!/bin/sh -eux
|
||||||
|
# XXX: We use /bin/sh instead of /bin/bash since some old versions of bash
|
||||||
|
# exhibit an issue where they seem to receive the same signal twice.
|
||||||
|
# With /bin/sh, this does not seem to happen.
|
||||||
|
|
||||||
# Print received signals into a file, one per line
|
# Print received signals into a file, one per line
|
||||||
file="$1"
|
file="$1"
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,11 @@ dumb_init="$1"
|
||||||
|
|
||||||
status=$($dumb_init > /dev/null 2>&1; echo $?)
|
status=$($dumb_init > /dev/null 2>&1; echo $?)
|
||||||
|
|
||||||
if [ "$status" -ne 0 ]; then
|
if [ "$status" -eq 0 ]; then
|
||||||
|
echo "Error: Expected dumb-init with no arguments to return nonzero, but it returned ${status}."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
msg=$($dumb_init 2>&1 || true)
|
msg=$($dumb_init 2>&1 || true)
|
||||||
msg_len=${#msg}
|
msg_len=${#msg}
|
||||||
|
|
||||||
|
@ -14,7 +18,3 @@ if [ "$status" -ne 0 ]; then
|
||||||
echo "Error: Expected dumb-init with no arguments to print a useful message, but it was only ${msg_len} chars long."
|
echo "Error: Expected dumb-init with no arguments to print a useful message, but it was only ${msg_len} chars long."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
echo "Error: Expected dumb-init with no arguments to return nonzero, but it returned ${status}."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
33
tests/test-pgroup
Executable file
33
tests/test-pgroup
Executable file
|
@ -0,0 +1,33 @@
|
||||||
|
#!/bin/bash -eux
|
||||||
|
# dumb-init should proxy signals to a process group rooted at its child when
|
||||||
|
# requested.
|
||||||
|
dumb_init="$1"
|
||||||
|
after_count="$2"
|
||||||
|
|
||||||
|
$dumb_init sh -c "yes 'oh, hi' | tail & yes error | tail >&2" &
|
||||||
|
pid="$!"
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
pstree -p "$pid"
|
||||||
|
pids=$(pstree -p "$pid" | grep -Po '(\d+)' | grep -Po '\d+')
|
||||||
|
|
||||||
|
# ensure processes are running
|
||||||
|
child_count=$(ps -o pid= $pids | wc -l) || true
|
||||||
|
|
||||||
|
if [ "$child_count" -ne 6 ]; then
|
||||||
|
echo "Error: Expected 6 children, instead we had ${child_count}."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ensure processes are dead after signal
|
||||||
|
kill -TERM "$pid"
|
||||||
|
sleep 1
|
||||||
|
child_count=$(ps -o pid= $pids | wc -l) || true
|
||||||
|
|
||||||
|
if [ "$child_count" -ne "$after_count" ]; then
|
||||||
|
echo "Error: Expected $after_count children, instead we had ${child_count}."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 'Killing any leftover processes.'
|
||||||
|
xargs kill -9 <<< "$pids" || true
|
Loading…
Reference in a new issue