grace

package module
v0.0.0-...-99673b6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 3, 2020 License: MIT Imports: 7 Imported by: 0

README

Grace - graceful shutdown made simple

Tiny library to gracefully shutdown your application by catching the OS signals using sync.errgroup.

I often find I have invoked one or more persistent blocking methods, and some other method is needed be invoked in another goroutine to tell it to gracefully shut down when an interrupt is received.

For instance, when ListenAndServe() is invoked, Shutdown needs to be called.

This library allows you to start zero or more concurrent goroutines, and trigger a graceful shutdown when an interrupt is received.

  • Go net/http package offers Shutdown function to gracefully shutdown your http server.
  • Go database/sql package offers Close function to gracefully close the connection to your SQL database.
  • Google google.golang.org/grpc package offers Server.GracefulStop, stops accepting new connections, and blocks until all the pending RPCs are finished

Alternatively, this library allows you to invoke zero or more concurrent goroutines with an optional timeout.

Documentation

Installation

go get -u github.com/StevenACoffman/grace

Usage

Simple Run until Interrupt signal received
package main

import (
	"log"
	"time"

	"github.com/StevenACoffman/grace"
)

func main() {

	wait, ctx := grace.NewWait()

	err := wait.WaitWithFunc(func() error {
		ticker := time.NewTicker(2 * time.Second)
		for {
			select {
			case <-ticker.C:
				log.Printf("ticker 2s ticked\n")
				// testcase what happens if an error occured
				//return fmt.Errorf("test error ticker 2s")
			case <-ctx.Done():
				log.Printf("closing ticker 2s goroutine\n")
				return nil
			}
		}
	})

	if err != nil {
		log.Println("finished clean")
	} else {
		log.Printf("received error: %v", err)
	}
}

Usage with a default timeout:
package main

import (
	"log"
	"time"

	"github.com/StevenACoffman/grace"
)

func main() {

	wait, ctx := grace.NewWait()

	err := wait.WaitWithTimeoutAndFunc(15*time.Second, func() error {
		ticker := time.NewTicker(2 * time.Second)
		for {
			select {
			case <-ticker.C:
				log.Printf("ticker 2s ticked\n")
				// testcase what happens if an error occured
				//return fmt.Errorf("test error ticker 2s")
			case <-ctx.Done():
				log.Printf("closing ticker 2s goroutine\n")
				return nil
			}
		}
	})

	if err != nil {
		log.Println("finished clean")
	} else {
		log.Printf("received error: %v", err)
	}
}

Usage with cleanup on shutdown

Bring your own cleanup function!

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/StevenACoffman/grace"
)

func main() {

	wait, ctx := grace.NewWait()
	var httpServer *http.Server

	err := wait.WaitWithFunc(
		func() error {
			http.HandleFunc("/", healthCheck)
			httpServer = newHTTPServer()

			if err := httpServer.ListenAndServe(); err != http.ErrServerClosed {
				return err
			}
			return nil
		},
		func() error { 
			//cleanup: on interrupt, shutdown server
			<-ctx.Done()
			log.Printf("closing http goroutine\n")
			return httpServer.Shutdown(ctx)
		})

	if err != nil {
		log.Println("finished clean")
	} else {
		log.Printf("received error: %v", err)
	}
}

func healthCheck(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	w.Header().Set("Content-Length", "0")
	w.WriteHeader(200)
}

func newHTTPServer() *http.Server {
	httpServer := &http.Server{
		Addr:         fmt.Sprintf(":8080"),
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
	log.Printf("HTTP Metrics server serving at %s", ":8080")
	return httpServer
}

Prior Art and Alternatives

This uses errgroup, but I found a number of other libraries that use other mechanisms:

Comparing them is pretty instructive. I wish I'd used some of their testing techniques!

Documentation

Overview

Package Grace provides graceful shutdown made simple for zero to many goroutines.

Tiny library to gracefully shutdown your application by catching the OS signals using [`sync.errgroup`](https://godoc.org/golang.org/x/sync/errgroup).

I often find I have invoked one or more persistent blocking methods, and some other method is needed be invoked in another goroutine to tell it to gracefully shut down when an interrupt is received.

For instance, when ListenAndServe() (https://golang.org/pkg/net/http/#ListenAndServe) is invoked, Shutdown (https://godoc.org/net/http#Server.Shutdown) needs to be called.

This library allows you to start zero or more concurrent goroutines, and trigger a graceful shutdown when an interrupt is received.

• Go net/http package offers Shutdown (https://godoc.org/net/http#Server.Shutdown) function to gracefully shutdown your http server.

• Go database/sql package offers Close(https://godoc.org/database/sql#DB.Close) function to gracefully close the connection to your SQL database.

• Google google.golang.org/grpc package offers Server.GracefulStop` (https://godoc.org/google.golang.org/grpc#Server.GracefulStop), stops accepting new connections, and blocks until all the pending RPCs are finished

Alternatively, this library allows you to invoke zero or more concurrent goroutines with an optional timeout.

Example Usage with ListenAndServe and Shutdown

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/StevenACoffman/grace"
)

func main() {

	wait, ctx := grace.NewWait()
	var httpServer *http.Server

	err := wait.WaitWithFunc(
		func() error {
			http.HandleFunc("/", healthCheck)
			httpServer = newHTTPServer()

			if err := httpServer.ListenAndServe(); err != http.ErrServerClosed {
				return err
			}
			return nil
		},
		func() error {
			//cleanup: on interrupt, shutdown server
			<-ctx.Done()
			log.Printf("closing http goroutine\n")
			return httpServer.Shutdown(ctx)
		})

	if err != nil {
		log.Println("finished clean")
	} else {
		log.Printf("received error: %v", err)
	}
}

func healthCheck(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	w.Header().Set("Content-Length", "0")
	w.WriteHeader(200)
}

func newHTTPServer() *http.Server {
	httpServer := &http.Server{
		Addr:         fmt.Sprintf(":8080"),
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
	log.Printf("HTTP Metrics server serving at %s", ":8080")
	return httpServer
}

Package grace provides functionality for handling system interrupts.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Wait

type Wait struct {
	// contains filtered or unexported fields
}

Wait struct represent wait functionality.

func NewWait

func NewWait() (*Wait, context.Context)

NewWait - Create a graceful shutdown Wait

func NewWaitWithContext

func NewWaitWithContext(context.Context) (*Wait, context.Context)

NewWaitWithContext - Create a graceful shutdown Wait but provide context

func (*Wait) Wait

func (w *Wait) Wait() error

Wait - simply wait for interruption.

Example
wait, _ := NewWait()
syscall.Kill(os.Getpid(), syscall.SIGTERM)
wait.Wait()
fmt.Println("Bye")
Output:

Bye

func (*Wait) WaitWithFunc

func (w *Wait) WaitWithFunc(functions ...func() error) error

WaitWithFunc receive function(s) as argument and call Wait() to wait interrupt signal. The function(s) will be called before Wait(). Returns the first non-nil error (if any) from the functions.

Example
wait, _ := NewWait()
syscall.Kill(os.Getpid(), syscall.SIGTERM)
wait.WaitWithFunc(func() error {
	// your logic here
	fmt.Println("Bye")
	return nil
})
Output:

Bye

func (*Wait) WaitWithTimeout

func (w *Wait) WaitWithTimeout(timeout time.Duration) error

WaitWithTimeout wait a given amount for time and then exit.

Example
wait, _ := NewWait()
syscall.Kill(os.Getpid(), syscall.SIGTERM)
wait.WaitWithTimeout(1 * time.Second)
fmt.Println("Bye")
Output:

Bye

func (*Wait) WaitWithTimeoutAndFunc

func (w *Wait) WaitWithTimeoutAndFunc(timeout time.Duration, functions ...func() error) error

WaitWithTimeoutAndFunc call function, wait a given amount of time and then exit. Returns the first non-nil error (if any) from the functions.

Example
wait, _ := NewWait()
wait.WaitWithTimeoutAndFunc(1*time.Second, func() error {
	// your logic here
	fmt.Println("Bye")
	return nil
})
Output:

Bye

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL