Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new entry point: Start which takes configuration #3

Merged
merged 2 commits into from
Aug 14, 2017
Merged
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
61 changes: 41 additions & 20 deletions reaper.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import "os"
import "os/signal"
import "syscall"

type Config struct {
Pid int
Options int
}

// Handle death of child (SIGCHLD) messages. Pushes the signal onto the
// notifications channel if there is a waiter.
Expand All @@ -16,9 +20,9 @@ func sigChildHandler(notifications chan os.Signal) {
signal.Notify(sigs, syscall.SIGCHLD)

for {
var sig = <- sigs
var sig = <-sigs
select {
case notifications <-sig: /* published it. */
case notifications <- sig: /* published it. */
default:
/*
* Notifications channel full - drop it to the
Expand All @@ -29,15 +33,17 @@ func sigChildHandler(notifications chan os.Signal) {
}
}

} /* End of function sigChildHandler. */

} /* End of function sigChildHandler. */

// Be a good parent - clean up behind the children.
func reapChildren() {
func reapChildren(config Config) {
var notifications = make(chan os.Signal, 1)

go sigChildHandler(notifications)

pid := config.Pid
opts := config.Options

for {
var sig = <-notifications
fmt.Printf(" - Received signal %v\n", sig)
Expand All @@ -48,9 +54,9 @@ func reapChildren() {
* Reap 'em, so that zombies don't accumulate.
* Plants vs. Zombies!!
*/
pid, err := syscall.Wait4(-1, &wstatus, 0, nil)
pid, err := syscall.Wait4(pid, &wstatus, opts, nil)
for syscall.EINTR == err {
pid, err = syscall.Wait4(-1, &wstatus, 0, nil)
pid, err = syscall.Wait4(pid, &wstatus, opts, nil)
}

if syscall.ECHILD == err {
Expand All @@ -63,31 +69,46 @@ func reapChildren() {
}
}

} /* End of function reapChildren. */


} /* End of function reapChildren. */

/*
* ======================================================================
* Section: Exported functions
* ======================================================================
*/

// Entry point for the reaper code. Start reaping children in the
// Normal entry point for the reaper code. Start reaping children in the
// background inside a goroutine.
func Reap() {
/*
* Only reap processes if we are taking over init's duties aka
* we are running as pid 1 inside a docker container.
*/
if 1 == os.Getpid() {
/*
* Ok, we are the grandma of 'em all, so we get to play
* the grim reaper.
* You will be missed, Terry Pratchett!! RIP
*/
go reapChildren()
}
if 1 == os.Getpid() {
config := Config{Pid: -1, Options: 0}

/*
* Ok, we are the grandma of 'em all, so we get to play
* the grim reaper.
* You will be missed, Terry Pratchett!! RIP
*/
go reapChildren(config)
}

} /* End of [exported] function Reap. */

} /* End of [exported] function Reap. */
// Entry point for invoking the reaper code bypassing pid 1 checks and
// with a specific configuration. Starts reaping children in the background
// inside a goroutine.
func Start(config Config) {
/*
* Start the Reaper with configuration options. This allows you to
* reap processes even if the current pid isn't running as pid 1.
* So ... use with caution!!
*
* In most cases, you are better off just using Reap() as that
* checks if we are running as Pid 1.
*/
go reapChildren(config)

} /* End of [exported] function Start. */
1 change: 1 addition & 0 deletions test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dockerfile
14 changes: 10 additions & 4 deletions test/Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
#!/usr/bin/env make

TEST_IMAGE = "reaper/test"
CONFIG_TEST_IMAGE = "reaper/config-test"

all: tests

test: tests

tests:
(go build testpid1.go; docker build -t reaper/test .; ./runtests.sh)



(go build testpid1.go; \
cp docker-files/no-config/Dockerfile .; \
docker build -t $(TEST_IMAGE) .; \
./runtests.sh; \
cp docker-files/json-config/Dockerfile .; \
docker build -t $(CONFIG_TEST_IMAGE) .; \
./runtests.sh "$(CONFIG_TEST_IMAGE)" )
4 changes: 4 additions & 0 deletions test/config/reaper.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"Pid": 0,
"Options": 0
}
File renamed without changes.
16 changes: 14 additions & 2 deletions test/runtests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
readonly MAX_SLEEP_TIME=$((5 + 2))
readonly IMAGE="reaper/test"

logfile="/tmp/reaper_test.log"


function get_sleepers() {
ps -ef -p $1 | grep sleep | grep -v grep
Expand Down Expand Up @@ -32,14 +34,24 @@ function check_orphans() {


function terminate_container() {
docker logs "$1" > "$logfile"
echo " - Container logs saved to $logfile"

echo " - Terminated container $(docker rm -f "$1")"

} # End of function terminate_container.


function run_tests() {
echo " - Starting docker container running image $IMAGE ..."
local elcid=$(docker run -dit $IMAGE)
local image=${1:-"${IMAGE}"}

logfile="/tmp/$(echo ${image} | sed 's#/#_#g').log"

echo " - Removing any existing log file $logfile ... "
rm -f "$logfile"

echo " - Starting docker container running image ${image} ..."
local elcid=$(docker run -dit "${image}")

echo " - Docker container name=$elcid"
local pid1=$(docker inspect --format '{{.State.Pid}}' $elcid)
Expand Down
94 changes: 77 additions & 17 deletions test/testpid1.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,53 @@
package main

import "encoding/json"
import "fmt"
import "os"
import "os/signal"
import "os/exec"
import "path/filepath"
import "syscall"
import "time"

import reaper "github.com/ramr/go-reaper"

const NWORKERS = 3
const REAPER_JSON_CONFIG = "/reaper/config/reaper.json"

const NWORKERS=3
func sleeper_test(set_proc_attributes bool) {
fmt.Printf(" - Set process attributes: %+v\n", set_proc_attributes)

cmd := exec.Command("sleep", "1")
if set_proc_attributes {
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Pgid: 0,
}
}

err := cmd.Start()
if err != nil {
fmt.Printf(" - Error starting sleep command: %s\n", err)
return
}

fmt.Printf("Set proc attributes: %+v\n", set_proc_attributes)

// Sleep for a wee bit longer to allow the reaper to reap the
// command on a slow system.
time.Sleep(4 * time.Second)

err = cmd.Wait()
if err != nil {
if set_proc_attributes {
fmt.Printf(" - Error waiting for command: %s\n",
err)
} else {
fmt.Printf(" - Expected wait failure: %s\n", err)
}
}

} /* End of function sleeper_test. */

func start_workers() {
// Starts up workers - which in turn start up kids that get
Expand All @@ -28,34 +64,58 @@ func start_workers() {
fmt.Printf(" - Error getting script - %s\n", scriptFile)
return
}

var args = fmt.Sprintf("%d", NWORKERS)
var cmd = exec.Command(script, args)
cmd.Start()

fmt.Printf(" - Started worker: %s %s\n", script, args)

} /* End of function start_workers. */
} /* End of function start_workers. */

func main() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGUSR1)

useConfig := false
config := reaper.Config{}

configFile, err := os.Open(REAPER_JSON_CONFIG)
if err == nil {
decoder := json.NewDecoder(configFile)
err = decoder.Decode(&config)
if err == nil {
useConfig = true
} else {
fmt.Printf(" - Error: Invalid json config: %s", err)
fmt.Printf(" - Using defaults ... ")
}
}

/* Start the grim reaper ... */
if useConfig {
go reaper.Start(config)

func main() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGUSR1)
/* Run the sleeper test setting the process attributes. */
go sleeper_test(true)

/* Start the grim reaper ... */
go reaper.Reap()
/* And run test without setting process attributes. */
go sleeper_test(false)

/* Start the initial set of workers ... */
start_workers()
} else {
go reaper.Reap()
}

for {
select {
case <-sig:
fmt.Println(" - Got SIGUSR1, starting more workers ...")
/* Start the initial set of workers ... */
start_workers()
}

} /* End of while doomsday ... */
for {
select {
case <-sig:
fmt.Println(" - Got SIGUSR1, adding workers ...")
start_workers()
}

} /* End of while doomsday ... */

} /* End of function main. */
} /* End of function main. */