Skip to content

Commit

Permalink
Merge pull request #17 from nicolas-van/children
Browse files Browse the repository at this point in the history
Children
  • Loading branch information
nicolas-van authored Apr 17, 2022
2 parents 1ae40a5 + b3ffe13 commit 64b0659
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 71 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ A simple Unix utility in C to run multiple commands concurrently.
* A very light alternative to classic init processes or supervisord to run multiple services in the same Docker container.
* Is dead-simple to use.
* Can be run without root permissions.
* Cleanly kills all the processes it starts.
* Cleanly kills all the processes it starts, including their subprocesses.
* Delegates the restart duty to the upper level.
* Forwards stdout and stderr for proper logging with Docker.
* Forwards stdout and stderr for proper logging with Docker or systemd.

Usage: `multirun "command1" "command2" ...`

Expand Down Expand Up @@ -88,18 +88,21 @@ Here is an example of bad use case:

A common cause for these problems is invalid usage of shell scripts that causes signals to not be propagated properly.

If you call multirun in a shell script, check that you launch it with the `exec` sh command. Example:
If you call multirun in a shell script, check that you launch it with the [`exec`](https://ss64.com/bash/exec.html) sh command. Example:

```bash
#!/bin/sh

# any init code

exec multirun arg1 arg2
```

If you don't use `exec` your main process will likely remain `/bin/sh` which won't forward signals correctly to your multirun process.

This advice is not specific to multirun and does apply to most containers that have a shell script as entrypoint.

Also, if you launch scripts with multirun that will launch the service you want, be sure to add `exec` in these scripts as well. Example:
Also, if you launch scripts with multirun that will launch the service you want, it is recommended to add `exec` in these scripts as well. Example:

```bash
# multirun call
Expand Down
113 changes: 63 additions & 50 deletions multirun.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,29 +63,29 @@ int main(int argc, char *const *argv) {
default:
printf("Unknown option: %c\n", (char)optopt);
print_help();
exit(-1);
exit(-2);
}
}
if (optind >= argc) {
print_help();
exit(-1);
exit(-2);
}
commands = &argv[optind];
nbr_processes = argc - optind;
launch_processes();
}

void launch_processes() {
int wstatus;
struct sigaction ssig;

subprocesses = malloc(sizeof(subprocess) * nbr_processes);

for (int i = 0; i < nbr_processes; i++) {
pid_t pid = fork();
if (pid == 0) {
// declare a new group
setpgrp();
// execute subcommand
sub_exec(commands[i]);
exit(-1); // should not happen
exit(-2); // should not happen
} else {
if (verbose) {
printf("multirun: launched command %s with pid %d\n", commands[i], pid);
Expand All @@ -99,21 +99,38 @@ void launch_processes() {
}
}

ssig.sa_handler = sig_receive;
if (sigaction(SIGINT, &ssig, NULL))
exit(-1);
if (sigaction(SIGTERM, &ssig, NULL))
exit(-1);
if (signal(SIGINT, sig_receive) == SIG_ERR) {
exit(-2);
}
if (signal(SIGTERM, sig_receive) == SIG_ERR) {
exit(-2);
}

while (1) {
pid_t pid = wait(&wstatus);
int wstatus;
pid_t pid = waitpid(-1, &wstatus, 0);

if (pid == -1) {
if (errno == ECHILD) {
break; // no more children
} if (errno == EINTR) {
continue; // interrupted
} else {
fprintf(stderr, "multirun: error during wait: %d\n", errno);
exit(-2);
}
} else if (pid == 0) {
break; // no more children
}

subprocess* process = NULL;
for (int i = 0; i < nbr_processes; i++) {
if (subprocesses[i].pid == pid) {
process = &subprocesses[i];
break;
}
}

if (process != NULL) {
// check if process is down
if (WIFEXITED(wstatus) || WIFSIGNALED(wstatus)) {
Expand All @@ -122,11 +139,11 @@ void launch_processes() {
|| (WIFSIGNALED(wstatus) && WTERMSIG(wstatus) != SIGINT && WTERMSIG(wstatus) != SIGTERM)) {
process->error = 1;
if (verbose) {
printf("multirun: command %s with pid %d exited abnormally\n", process->command, pid);
printf("multirun: command %s with pid %d exited abnormally\n", process->command, process->pid);
}
} else {
if (verbose) {
printf("multirun: command %s with pid %d exited normally\n", process->command, pid);
printf("multirun: command %s with pid %d exited normally\n", process->command, process->pid);
}
}
if (! closing) {
Expand All @@ -140,23 +157,33 @@ void launch_processes() {
}
} else {
if (verbose) {
printf("multirun: reaped zombie process with pid %d\n", pid);
printf("multirun: subchild process with pid %d ended\n", pid);
}
}
// check if all processes are stopped
int running = 0;
for (int i = 0; i < nbr_processes; i++) {
if (subprocesses[i].up) {
running = 1;
break;
}

// ensure all child died in all groups
for (int i = 0; i < nbr_processes; i++) {
while (1) {
int wstatus;
int pid = waitpid(-subprocesses[i].pid, &wstatus, 0);
if (pid == -1) {
if (errno == ECHILD) {
break; // no more children in group
} if (errno == EINTR) {
continue; // interrupted
} else {
fprintf(stderr, "multirun: error during wait: %d\n", errno);
exit(-2);
}
} else if (pid == 0) {
break; // no more children
}
// some more child must still exit
}
if (! running)
break;
}
// reap all potential zombies
reap_zombies();
// check if there are errors

// check if there were errors
int error = 0;
for (int i = 0; i < nbr_processes; i++) {
if (subprocesses[i].error) {
Expand Down Expand Up @@ -199,13 +226,21 @@ void sub_exec(const char* command) {
ret = execlp("sh", "sh", "-c", ccommand, (char*) NULL);
if (ret != 0) {
fprintf(stderr, "multirun: error launching the subprocess: %s\n", strerror(errno));
exit(-1);
exit(-2);
}
}

void kill_all(int signal) {
for (int i = 0; i < nbr_processes; i++) {
kill(subprocesses[i].pid, signal);
int ret = kill(-subprocesses[i].pid, signal);
if (ret != 0) {
if (errno == ESRCH) {
// ignore
} else {
fprintf(stderr, "multirun: error %d while killing processes\n", errno);
exit(-2);
}
}
}
}

Expand All @@ -218,25 +253,3 @@ void sig_receive(int signum) {
}
kill_all(signum);
}

void reap_zombies() {
while (1) {
pid_t childpid;
int wstatus;
childpid = waitpid(-1, &wstatus, WNOHANG);
if (childpid == -1) {
if (errno == ECHILD) {
break;
} else {
fprintf(stderr, "multirun: error while reaping zombies\n");
exit(-1);
}
} else if (childpid == 0) {
break;
} else {
if (verbose) {
printf("multirun: reaped zombie process with pid %d\n", childpid);
}
}
}
}
67 changes: 50 additions & 17 deletions zombiemaster/zombiemaster.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,69 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>


void sig_receive_zombie_master(int signum);
void sig_receive_zombie(int signum);

int stop = 0;

int main(int argc, char* const* argv) {
int nbr_processes = 3;
struct sigaction ssig;
memset(&ssig, 0, sizeof ssig);
sigemptyset(&ssig.sa_mask);

int nbr_processes = 1;
printf("Executing zombie master\n");
for (int i = 0; i < nbr_processes; i++) {
pid_t pid = fork();
if (pid == 0) {
printf("Executing secondary zombie master %d\n", i);
for (int j = 0; j < nbr_processes; j++) {
pid_t pid2 = fork();
if (pid2 == 0) {
printf("Executing zombie %d.%d\n", i, j);
while (1) {
sleep(1);
printf("Hi! I'm zombie %d.%d and I die\n", i, j);
exit(-1);
}
} else {
// nothing
}
printf("Executing zombie %d\n", i);
ssig.sa_handler = sig_receive_zombie;
if (sigaction(SIGINT, &ssig, NULL))
exit(-2);
if (sigaction(SIGTERM, &ssig, NULL))
exit(-2);
while (!stop) {
sleep(1);
}
sleep(2);
printf("Hi! I'm secondary zombie master %d and I die\n", i);
exit(-1);
printf("Zombie %d sleeps a little\n", i);
sleep(3);
printf("Hi! I'm zombie %d and I die\n", i);
exit(0);
} else {
// nothing
}
}

ssig.sa_handler = sig_receive_zombie_master;
if (sigaction(SIGINT, &ssig, NULL))
exit(-2);
if (sigaction(SIGTERM, &ssig, NULL))
exit(-2);

while (!stop) {
sleep(1);
}
printf("Zombie master sleeps a little\n");
sleep(3);

printf("Hi! I'm zombie master and I die\n");
exit(0);
}

void sig_receive_zombie_master(int signum) {
printf("Me zombie master, me received %s bullet in the head\n", strsignal(signum));
exit(0);
stop = 1;
}

void sig_receive_zombie(int signum) {
printf("Me zombie, me received %s bullet in the head\n", strsignal(signum));
stop = 1;
}

0 comments on commit 64b0659

Please sign in to comment.