Skip to content

Instantly share code, notes, and snippets.

@rhcarvalho
Last active April 1, 2016 19:53
Show Gist options
  • Save rhcarvalho/4655784edee4ec863b059c8c0e01f673 to your computer and use it in GitHub Desktop.
Save rhcarvalho/4655784edee4ec863b059c8c0e01f673 to your computer and use it in GitHub Desktop.
Insistent cleanup of terminating Go program
$ go run panic.go
2016/04/01 21:51:12 start
2016/04/01 21:51:12 creating container "680b98d36ce2e667"...
^C2016/04/01 21:51:15 removing container "680b98d36ce2e667"...
2016/04/01 21:51:16 removed "680b98d36ce2e667"
2016/04/01 21:51:16 done
2016/04/01 21:51:16 run time panic: early termination
$ go run panic.go
2016/04/01 21:51:18 start
2016/04/01 21:51:18 creating container "7265daca397afe17"...
2016/04/01 21:51:22 WARN: if you interrupted the program before seeing this, extra work that shouldn't been done has been done!
2016/04/01 21:51:22 removing container "7265daca397afe17"...
2016/04/01 21:51:23 removed "7265daca397afe17"
2016/04/01 21:51:23 done
// panic is a little demo for catching signals and insisting on doing cleanup
// before program termination.
package main
import (
"fmt"
"log"
"math/rand"
"os"
"os/signal"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
protect(runContainer)
}
// handleEarlyTermination catches program termination signals and closes the
// stop channel to indicate early termination. Well-behaved callers should check
// if stop is closed to abort whatever pending tasks are left and allow for
// early termination. Well-behaved callers should call release to release
// allocated resources and stop the signal handlers.
func handleEarlyTermination() (stop chan struct{}, release func()) {
stop = make(chan struct{})
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
if s := <-c; s != nil {
close(stop)
}
}()
release = func() {
defer close(c)
defer signal.Stop(c)
select {
case <-stop:
// stop is closed, the function is terminating early.
// NOTE: we could call os.Exit(1) here, but we instead
// panic to allow pending deferred calls to run and the
// bubbling up of the panic up the call stack so that it
// can be handled if desirable.
panic("early termination")
default:
// stop was not closed, normal function termination.
}
}
return stop, release
}
func runContainer() {
id := fmt.Sprintf("%016x", rand.Int63())
defer removeContainer(id)
stop, release := handleEarlyTermination()
defer release()
log.Printf("creating container %q...", id)
time.Sleep(3500 * time.Millisecond)
// NOTE: before we continue doing more work, we need to check if we
// should terminate earlier.
select {
case <-stop:
return
default:
}
log.Println("WARN: if you interrupted the program before seeing this, extra work that shouldn't been done has been done!")
time.Sleep(500 * time.Millisecond)
}
func removeContainer(id string) {
defer log.Printf("removed %q", id)
log.Printf("removing container %q...", id)
time.Sleep(400 * time.Millisecond)
}
func protect(g func()) {
defer func() {
log.Println("done") // Println executes normally even if there is a panic
if x := recover(); x != nil {
log.Printf("run time panic: %v", x)
}
}()
log.Println("start")
g()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment