-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
128 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# Terminator | ||
|
||
<img src="https://user-images.githubusercontent.com/8205547/162049312-28e505f3-100c-47b4-a168-78d4ff9dd19f.jpg" /> | ||
|
||
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. | ||
|
||
## Usage: | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"log" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/cogolabs/terminator" | ||
) | ||
|
||
const ( | ||
timeout = 30 * time.Second | ||
) | ||
|
||
var ( | ||
shutdownAfter = flag.Duration("shutdownAfter", 12*time.Hour, "Duration to wait before shutting down (with jitter)") | ||
shutdownJitter = flag.Duration("shutdownJitter", 2*time.Hour, "Jitter duration in either direction of shutdownAfter") | ||
) | ||
|
||
func main() { | ||
flag.Parse() | ||
|
||
srv := &http.Server{ | ||
Addr: "0.0.0.0:8080", | ||
Handler: nil, // your handler here | ||
ReadTimeout: timeout, | ||
WriteTimeout: timeout, | ||
} | ||
|
||
log.Println("starting server") | ||
err := terminator.ServeAndShutdownAfter(&terminator.Options{ | ||
Server: srv, | ||
ShutdownAfter: *shutdownAfter, | ||
Jitter: *shutdownJitter, | ||
GracefulShutdownPeriod: 30 * time.Second, | ||
}) | ||
if err != nil { | ||
log.Fatalf("error in server: %v\n", err) | ||
} | ||
|
||
log.Println("server done") | ||
} | ||
``` |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package terminator | ||
|
||
import ( | ||
"context" | ||
"math/rand" | ||
"net/http" | ||
"os" | ||
"os/signal" | ||
"sync" | ||
"syscall" | ||
"time" | ||
) | ||
|
||
type Options struct { | ||
// Server is the HTTP server that will be started and eventually | ||
// gracefully shut down | ||
Server *http.Server | ||
|
||
// ShutdownAfter indicates how long we should keep the HTTP | ||
// server alive before starting to shutdown | ||
ShutdownAfter time.Duration | ||
|
||
// Jitter represents a random offset in either direction of | ||
// ShutdownAfter that we'll add to ShutdownAfter. This ensures | ||
// that not all servers go down at once, thus avoiding outages. | ||
Jitter time.Duration | ||
|
||
// GracefulShutdownPeriod represents the maximum amount of time | ||
// we allow in-flight requests to finish before we force shutdown | ||
GracefulShutdownPeriod time.Duration | ||
} | ||
|
||
// ServeAndShutdownAfter starts the given HTTP server, waits for the given shutdownAfter duration, | ||
// then gracefully shuts the server down and returns to the caller. The maximum amount of time | ||
// in-progress requests are given is represented by gracefulShutdownTimeout. | ||
func ServeAndShutdownAfter(opts *Options) error { | ||
sig := make(chan os.Signal, 1) | ||
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) | ||
|
||
wg := sync.WaitGroup{} | ||
wg.Add(1) | ||
|
||
go func() { | ||
defer wg.Done() | ||
|
||
// add a random jitter in the range of (-n, n) seconds to the max shutdown | ||
// time to ensure not all servers in a deployment go down at the same time | ||
rng := rand.New(rand.NewSource(time.Now().UnixNano())) | ||
jitterSecs := int(opts.Jitter.Seconds()) | ||
offset := rng.Intn(2*jitterSecs) - jitterSecs | ||
offsetSecs := time.Second * time.Duration(offset) | ||
waitFor := opts.ShutdownAfter + offsetSecs | ||
|
||
// wait for a SIGINT to arrive or for the wait duration to elapse | ||
select { | ||
case <-sig: | ||
case <-time.After(waitFor): | ||
} | ||
|
||
ctx, cancel := context.WithTimeout(context.Background(), opts.GracefulShutdownPeriod) | ||
defer cancel() | ||
|
||
// gracefully shut down the server | ||
opts.Server.SetKeepAlivesEnabled(false) | ||
opts.Server.Shutdown(ctx) | ||
}() | ||
|
||
err := opts.Server.ListenAndServe() | ||
if err != nil && err != http.ErrServerClosed { | ||
return err | ||
} | ||
|
||
wg.Wait() | ||
return nil | ||
} |