Add signal handling.
diff --git a/scaffold.go b/scaffold.go
index 0bfdc51..44fbcb2 100644
--- a/scaffold.go
+++ b/scaffold.go
@@ -2,17 +2,31 @@
import (
"encoding/json"
+ "errors"
+ "fmt"
"net"
"net/http"
+ "os"
+ "os/signal"
+ "runtime"
+ "syscall"
"time"
)
const (
- // DefaultGraceTimeout is the default amount of time to wait for a request to complete
+ // DefaultGraceTimeout is the default amount of time to wait for a request
+ // to complete. Default is 30 seconds, which is also the default grace period
+ // in Kubernetes.
DefaultGraceTimeout = 30 * time.Second
)
/*
+ErrSignalCaught is used in the "Shutdown" mechanism when the shutdown was
+caused by a SIGINT or SIGTERM.
+*/
+var ErrSignalCaught = errors.New("Caught shutdown signal")
+
+/*
An HTTPScaffold provides a set of features on top of a standard HTTP
listener. It includes an HTTP handler that may be plugged in to any
standard Go HTTP server. It is intended to be placed before any other
@@ -20,6 +34,7 @@
*/
type HTTPScaffold struct {
insecurePort int
+ open bool
tracker *requestTracker
insecureListener net.Listener
}
@@ -52,6 +67,9 @@
/*
Open opens up the port that was created when the scaffold was set up.
+This method is optional. It may be called before Listen so that we can
+retrieve the actual address where the server is listening before we actually
+start to listen.
*/
func (s *HTTPScaffold) Open() error {
s.tracker = startRequestTracker(DefaultGraceTimeout)
@@ -63,6 +81,7 @@
return err
}
s.insecureListener = il
+ s.open = true
return nil
}
@@ -80,15 +99,26 @@
Listen will block until the server is shutdown using "Shutdown" or one of
the other shutdown mechanisms. It must not be called until after "Open"
has been called.
+If shut down, Listen will return the error that was passed to the "shutdown"
+method.
*/
-func (s *HTTPScaffold) Listen(baseHandler http.Handler) {
+func (s *HTTPScaffold) Listen(baseHandler http.Handler) error {
+ if !s.open {
+ err := s.Open()
+ if err != nil {
+ return err
+ }
+ s.open = true
+ }
+
handler := &httpHandler{
s: s,
handler: baseHandler,
}
go http.Serve(s.insecureListener, handler)
- <-s.tracker.C
+ err := <-s.tracker.C
s.insecureListener.Close()
+ return err
}
/*
@@ -101,12 +131,47 @@
}
/*
-CatchSignals directs the scaffold to catch SIGINT and SIGTERM (the signals
-sent by "Control-C" and "kill" by default) to trigger the markdown
-logic. Using this logic, when these signals are caught, the server will
-catch
+CatchSignals directs the scaffold to listen for common signals. It catches
+three signals. SIGINT (aka control-C) and SIGTERM (what "kill" sends by default)
+will cause the program to be marked down, and "SignalCaught" will be returned
+by the "Listen" method. SIGHUP ("kill -1" or "kill -HUP") will cause the
+stack trace of all the threads to be printed to stderr, just like a Java program.
*/
func (s *HTTPScaffold) CatchSignals() {
+ sigChan := make(chan os.Signal, 1)
+ signal.Notify(sigChan, syscall.SIGINT)
+ signal.Notify(sigChan, syscall.SIGTERM)
+ signal.Notify(sigChan, syscall.SIGHUP)
+
+ go func() {
+ for {
+ sig := <-sigChan
+ switch sig {
+ case syscall.SIGINT, syscall.SIGTERM:
+ s.Shutdown(ErrSignalCaught)
+ signal.Reset()
+ return
+ case syscall.SIGHUP:
+ dumpStack()
+ }
+ }
+ }()
+}
+
+func dumpStack() {
+ stackSize := 32767
+ stackBuf := make([]byte, stackSize)
+ var w int
+
+ for w < stackSize {
+ w = runtime.Stack(stackBuf, true)
+ if w == stackSize {
+ stackSize *= 2
+ stackBuf = make([]byte, stackSize)
+ }
+ }
+
+ fmt.Fprint(os.Stderr, string(stackBuf[:w]))
}
type httpHandler struct {
diff --git a/scaffold_test.go b/scaffold_test.go
index 9422a94..8ba7c46 100644
--- a/scaffold_test.go
+++ b/scaffold_test.go
@@ -13,15 +13,15 @@
var _ = Describe("Scaffold Tests", func() {
It("Validate framework", func() {
s := CreateHTTPScaffold()
- stopChan := make(chan bool)
+ stopChan := make(chan error)
err := s.Open()
Expect(err).Should(Succeed())
go func() {
fmt.Fprintf(GinkgoWriter, "Gonna listen on %s\n", s.InsecureAddress())
- s.Listen(&testHandler{})
+ stopErr := s.Listen(&testHandler{})
fmt.Fprintf(GinkgoWriter, "Done listening\n")
- stopChan <- true
+ stopChan <- stopErr
}()
Eventually(func() bool {
@@ -30,8 +30,9 @@
resp, err := http.Get(fmt.Sprintf("http://%s", s.InsecureAddress()))
Expect(err).Should(Succeed())
Expect(resp.StatusCode).Should(Equal(200))
- s.Shutdown(errors.New("Validate"))
- Eventually(stopChan).Should(Receive(BeTrue()))
+ shutdownErr := errors.New("Validate")
+ s.Shutdown(shutdownErr)
+ Eventually(stopChan).Should(Receive(Equal(shutdownErr)))
})
It("Shutdown", func() {