diff --git a/.gitignore b/.gitignore index 552a1b1..02812de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ runsvinit runsvinit-*-* examples/runsvinit-linux-amd64* +zombietest/zombie +zombietest/runsvinit +*.uptodate # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o diff --git a/main.go b/main.go index 93b8af1..82f1dcc 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "flag" "log" "os" "os/exec" @@ -13,6 +14,9 @@ import ( const etcService = "/etc/service" func main() { + reap := flag.Bool("reap", true, "reap orphan children") + flag.Parse() + log.SetFlags(0) runsvdir, err := exec.LookPath("runsvdir") @@ -35,7 +39,12 @@ func main() { log.Printf("warning: I'm not PID 1, I'm PID %d", pid) } - go reapAll() + if *reap { + log.Print("reaping zombies") + go reapLoop() + } else { + log.Print("NOT reaping zombies") + } supervisor := cmd(runsvdir, etcService) if err := supervisor.Start(); err != nil { @@ -53,31 +62,33 @@ func main() { } } -func reapAll() { +// From https://github.com/ramr/go-reaper/blob/master/reaper.go +func reapLoop() { c := make(chan os.Signal) signal.Notify(c, syscall.SIGCHLD) for range c { - go reapOne() + reapChildren() } } -// From https://github.com/ramr/go-reaper/blob/master/reaper.go -func reapOne() { - var ( - ws syscall.WaitStatus - pid int - err error - ) +func reapChildren() { for { - pid, err = syscall.Wait4(-1, &ws, 0, nil) - if err != syscall.EINTR { - break + var ( + ws syscall.WaitStatus + pid int + err error + ) + for { + pid, err = syscall.Wait4(-1, &ws, 0, nil) + if err != syscall.EINTR { + break + } } + if err == syscall.ECHILD { + return // done + } + log.Printf("reaped child process %d (%+v)", pid, ws) } - if err == syscall.ECHILD { - return - } - log.Printf("reaped child process %d (%+v)", pid, ws) } type signaler interface { diff --git a/zombietest/Dockerfile b/zombietest/Dockerfile new file mode 100644 index 0000000..3012c1a --- /dev/null +++ b/zombietest/Dockerfile @@ -0,0 +1,11 @@ +FROM alpine:latest +RUN echo "http://dl-4.alpinelinux.org/alpine/edge/testing" >>/etc/apk/repositories && \ + apk add --update runit && \ + rm -rf /var/cache/apk/* + +COPY zombie / +RUN mkdir -p /etc/service/zombie +COPY run-zombie /etc/service/zombie/run + +COPY /runsvinit / + diff --git a/zombietest/Makefile b/zombietest/Makefile new file mode 100644 index 0000000..757398d --- /dev/null +++ b/zombietest/Makefile @@ -0,0 +1,23 @@ +.PHONY: test clean + +test: .test.uptodate + ./test.bash + +.test.uptodate: runsvinit zombie run-zombie Dockerfile + docker build -t zombietest . + touch $@ + +runsvinit: ../*.go + env GOOS=linux GOARCH=amd64 go build -o $@ github.com/peterbourgon/runsvinit + +zombie: .build.uptodate + docker run --rm -v $(shell pwd):/mount zombietest-build cc -Wall -Werror -o /mount/zombie /zombie.c + +.build.uptodate: build/zombie.c build/Dockerfile + docker build -t zombietest-build build/ + touch $@ + +clean: + rm -rf .test.uptodate .build.uptodate runsvinit zombie + docker stop zombietest zombietest-build >/dev/null 2>&1 || true + docker rm zombietest zombietest-build >/dev/null 2>&1 || true diff --git a/zombietest/README.md b/zombietest/README.md new file mode 100644 index 0000000..c2a1067 --- /dev/null +++ b/zombietest/README.md @@ -0,0 +1,24 @@ +# zombietest + +This directory contains an integration test to prove runsvinit is actually +reaping zombies. `make` builds and executes the test as follows: + +1. We produce a linux/amd64 runsvinit binary by setting GOOS/GOARCH and + invoking the Go compiler. Requires Go 1.5, or Go 1.4 built with the + appropriate cross-compile options. + +2. The build/zombie.c program spawns five zombies and exits. We compile it for + linux/amd64 via a zombietest-build container. We do this so `make` works + from a Mac. This requires a working Docker installation. + +3. Once we have linux/amd64 runsvinit and zombie binaries, we produce a + zombietest container via the Dockerfile. That container contains a single + runit service, /etc/service/zombie, which supervises the zombie binary. We + provide no default ENTRYPOINT, so we can supply it at runtime. + +4. Once the zombietest container is built, we invoke the test.bash script. + That launches a version of the container with runsvinit set to NOT reap + zombies, and after 1 second, verifies that zombies exist. Then, it launches + a version of the container with runsvinit set to reap zombies, and after 1 + second, verifies that no zombies exist. + diff --git a/zombietest/build/Dockerfile b/zombietest/build/Dockerfile index 6672cf8..2828f5e 100644 --- a/zombietest/build/Dockerfile +++ b/zombietest/build/Dockerfile @@ -1,9 +1,3 @@ -FROM golang:1.5.1 - -COPY ../../*.go /go/src/github.com/peterbourgon/runsvinit -RUN go install -v github.com/peterbourgon/runsvinit -RUN mv /go/bin/runsvinit /mount/ - +FROM alpine:latest +RUN apk add --update gcc musl-dev && rm -rf /var/cache/apk/* COPY zombie.c / -RUN cc -Wall -Werror -o /zombie /zombie.c -RUN mv /zombie /mount/ diff --git a/zombietest/build/Makefile b/zombietest/build/Makefile deleted file mode 100644 index 25878cd..0000000 --- a/zombietest/build/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -.PHONY: all -all: zombie runsvinit - -zombie runsvinit: .uptodate - docker run -ti --rm -v $(shell pwd):/mount zombie-build - -.uptodate: Dockerfile ../../*.go - docker build -t zombie-build . - touch $@ diff --git a/zombietest/build/zombie.c b/zombietest/build/zombie.c index 631333b..0859123 100644 --- a/zombietest/build/zombie.c +++ b/zombietest/build/zombie.c @@ -1,18 +1,18 @@ #include -#include +#include #include -int main () -{ - pid_t child_pid; - - child_pid = fork (); - if (child_pid > 0) { - sleep (60); - } - else { - exit (0); - } - return 0; +int main() { + pid_t pid; + int i; + for (i = 0; i<5; i++) { + pid = fork(); + if (pid > 0) { + printf("Zombie #%d born\n", i + 1); + } else { + printf("Brains...\n"); + exit(0); + } + } + return 0; } - diff --git a/zombietest/run-zombie b/zombietest/run-zombie new file mode 100755 index 0000000..198fbc2 --- /dev/null +++ b/zombietest/run-zombie @@ -0,0 +1,3 @@ +#!/bin/sh + +exec /zombie diff --git a/zombietest/test.bash b/zombietest/test.bash new file mode 100755 index 0000000..ca04214 --- /dev/null +++ b/zombietest/test.bash @@ -0,0 +1,34 @@ +#!/bin/bash + +RC=0 +SLEEP=1 + +C=$(docker run -d zombietest /runsvinit -reap=false) +sleep $SLEEP +NOREAP=$(docker exec $C ps -o pid,stat | grep Z | wc -l) +echo -n without reaping, we have $NOREAP zombies... +if [ "$NOREAP" -le "0" ] +then + echo " FAIL" + RC=1 +else + echo " good" +fi +docker stop $C >/dev/null +docker rm $C >/dev/null + +C=$(docker run -d zombietest /runsvinit) +sleep $SLEEP +YESREAP=$(docker exec $C ps -o pid,stat | grep Z | wc -l) +echo -n with reaping, we have $YESREAP zombies... +if [ "$YESREAP" -gt "0" ] +then + echo " FAIL" + RC=1 +else + echo " good" +fi +docker stop $C >/dev/null +docker rm $C >/dev/null + +exit $RC