Last active
April 1, 2016 19:53
-
-
Save rhcarvalho/4655784edee4ec863b059c8c0e01f673 to your computer and use it in GitHub Desktop.
Insistent cleanup of terminating Go program
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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