Skip to content

Commit 8dd98ef

Browse files
committed
cogolabs/terminator
1 parent 8813233 commit 8dd98ef

File tree

2 files changed

+128
-0
lines changed

2 files changed

+128
-0
lines changed

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Terminator
2+
3+
<img src="https://user-images.githubusercontent.com/8205547/162049312-28e505f3-100c-47b4-a168-78d4ff9dd19f.jpg" />
4+
5+
Library to automatically run and kill HTTP servers at regular intervals. Includes a jitter on the kill interval to ensure that a group of server processes started at roughly the same time don't all go down at once.
6+
7+
## Usage:
8+
9+
```go
10+
package main
11+
12+
import (
13+
"flag"
14+
"log"
15+
"net/http"
16+
"time"
17+
18+
"github.com/cogolabs/terminator"
19+
)
20+
21+
const (
22+
timeout = 30 * time.Second
23+
)
24+
25+
var (
26+
shutdownAfter = flag.Duration("shutdownAfter", 12*time.Hour, "Duration to wait before shutting down (with jitter)")
27+
shutdownJitter = flag.Duration("shutdownJitter", 2*time.Hour, "Jitter duration in either direction of shutdownAfter")
28+
)
29+
30+
func main() {
31+
flag.Parse()
32+
33+
srv := &http.Server{
34+
Addr: "0.0.0.0:8080",
35+
Handler: nil, // your handler here
36+
ReadTimeout: timeout,
37+
WriteTimeout: timeout,
38+
}
39+
40+
log.Println("starting server")
41+
err := terminator.ServeAndShutdownAfter(&terminator.Options{
42+
Server: srv,
43+
ShutdownAfter: *shutdownAfter,
44+
Jitter: *shutdownJitter,
45+
GracefulShutdownPeriod: 30 * time.Second,
46+
})
47+
if err != nil {
48+
log.Fatalf("error in server: %v\n", err)
49+
}
50+
51+
log.Println("server done")
52+
}
53+
```

terminator.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package terminator
2+
3+
import (
4+
"context"
5+
"math/rand"
6+
"net/http"
7+
"os"
8+
"os/signal"
9+
"sync"
10+
"syscall"
11+
"time"
12+
)
13+
14+
type Options struct {
15+
// Server is the HTTP server that will be started and eventually
16+
// gracefully shut down
17+
Server *http.Server
18+
19+
// ShutdownAfter indicates how long we should keep the HTTP
20+
// server alive before starting to shutdown
21+
ShutdownAfter time.Duration
22+
23+
// Jitter represents a random offset in either direction of
24+
// ShutdownAfter that we'll add to ShutdownAfter. This ensures
25+
// that not all servers go down at once, thus avoiding outages.
26+
Jitter time.Duration
27+
28+
// GracefulShutdownPeriod represents the maximum amount of time
29+
// we allow in-flight requests to finish before we force shutdown
30+
GracefulShutdownPeriod time.Duration
31+
}
32+
33+
// ServeAndShutdownAfter starts the given HTTP server, waits for the given shutdownAfter duration,
34+
// then gracefully shuts the server down and returns to the caller. The maximum amount of time
35+
// in-progress requests are given is represented by gracefulShutdownTimeout.
36+
func ServeAndShutdownAfter(opts *Options) error {
37+
sig := make(chan os.Signal, 1)
38+
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
39+
40+
wg := sync.WaitGroup{}
41+
wg.Add(1)
42+
43+
go func() {
44+
defer wg.Done()
45+
46+
// add a random jitter in the range of (-n, n) seconds to the max shutdown
47+
// time to ensure not all servers in a deployment go down at the same time
48+
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
49+
jitterSecs := int(opts.Jitter.Seconds())
50+
offset := rng.Intn(2*jitterSecs) - jitterSecs
51+
offsetSecs := time.Second * time.Duration(offset)
52+
waitFor := opts.ShutdownAfter + offsetSecs
53+
54+
// wait for a SIGINT to arrive or for the wait duration to elapse
55+
select {
56+
case <-sig:
57+
case <-time.After(waitFor):
58+
}
59+
60+
ctx, cancel := context.WithTimeout(context.Background(), opts.GracefulShutdownPeriod)
61+
defer cancel()
62+
63+
// gracefully shut down the server
64+
opts.Server.SetKeepAlivesEnabled(false)
65+
opts.Server.Shutdown(ctx)
66+
}()
67+
68+
err := opts.Server.ListenAndServe()
69+
if err != nil && err != http.ErrServerClosed {
70+
return err
71+
}
72+
73+
wg.Wait()
74+
return nil
75+
}

0 commit comments

Comments
 (0)