lifecycle

package module
v0.0.0-...-582b1d5 Latest Latest
Warning

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

Go to latest
Published: Aug 12, 2020 License: MIT Imports: 8 Imported by: 0

README

Lifecycle

GoDoc reference example Build GitHub go.mod Go version of a Go module Go Report Card

Package lifecycle provides simple service management primitives.

A typical HTTP server could look like:

type MyHttpServer struct {
    server http.Server
}

func NewHttpServer() *MyHttpServer {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
        rw.Write([]byte("Hello!"))
    })
    server := http.Server{Addr: ":8090", Handler: mux}
    return &MyHttpServer{
        server: server,
    }
}
func (m *MyHttpServer) Start() error {
    return m.server.ListenAndServe()
}

This is pretty simple code for simple projects, but lacks powerful state management. To add signal handling, graceful shutdown with automatic termination after a certain delay or readiness probes, one would need to write boulerplate code.

Using lifecycle, you can modify this code to look like:

type MyHttpServer struct {
    *lifecycle.Worker
    server http.Server
}

func NewHttpServer() *MyHttpServer {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
        rw.Write([]byte("Hello!"))
    })
    server := http.Server{Addr: ":8090", Handler: mux}
    return &MyHttpServer{
        Worker: lifecycle.NewWorker(&lifecycle.Hooks{
            Start:     lifecycle.DropContext(server.ListenAndServe),
            Shutdown:  server.Shutdown,
            Terminate: lifecycle.DropContext(server.Close),
        }),
        server: server,
    }
}

// No need to add Start, Stop and other lifecycle controlling methods, which are
// inherited from Worker.

Out of the box, this provides:

  • Start, Stop and Terminate methods
  • StartBackground, providing a non-blocking alternative
  • Graceful termination timeout, terminating the service
  • Error handling and filtering, for example to ignore http.ErrServerClosed
  • Logging by providing your logger
  • Readiness probes
  • Observers to listen to the service state changes and errors

This package also provides a Host, which wraps multiple workers into a single startable unit exposing the same API.

Getting started

Install this package with:

go get -u go.tickamp.dev/lifecycle

The HTTP example provides a good place to get started with simple use-cases. You can fund additional documentation in the package Godoc.

Building

This package is built with Bazel. You can build it with:

bazel build //...

Standard go commands should be usable as well.


Released under the MIT License.

Documentation

Overview

Package lifecycle provides simple service management primitives.

A typical HTTP server could look like:

type MyHttpServer struct {
    server http.Server
}

func NewHttpServer() *MyHttpServer {
    mux := http.NewServeMux()
       mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
           rw.Write([]byte("Hello!"))
       })
       server := http.Server{Addr: ":8090", Handler: mux}
       return &MyHttpServer{
          server: server,
       }
}

func (m *MyHttpServer) Start() error {
    return m.server.ListenAndServe()
}

This is pretty simple code for simple projects, but lacks powerful state management. To add signal handling, graceful shutdown with automatic termination after a certain delay or readiness probes, one would need to write boulerplate code.

Using lifecycle, you can modify your code like:

type MyHttpServer struct {
    *lifecycle.Worker
    server http.Server
}

func NewHttpServer() *MyHttpServer {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
        rw.Write([]byte("Hello!"))
    })
    server := http.Server{Addr: ":8090", Handler: mux}
    return &MyHttpServer{
        Worker: lifecycle.NewWorker(&lifecycle.Hooks{
            Start:     lifecycle.DropContext(server.ListenAndServe),
            Shutdown:  server.Shutdown,
            Terminate: lifecycle.DropContext(server.Close),
        }),
        server: server,
    }
}

// No need to add Start, Stop and other lifecycle controlling methods,
// which are inherited from Worker.

Out of the box, this provides you with:

  • Start, Stop and Terminate methods
  • StartBackground, providing a non-blocking alternative
  • Graceful termination timeout, terminating the service
  • Error handling and filtering, for example to ignore http.ErrServerClosed
  • Logging by providing your logger
  • Readiness probes
  • Observers to listen to the service state changes and errors

This package also provides a Host, which wraps multiple workers into a single startable unit exposing the same API.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsInterrupted

func IsInterrupted(err error) bool

IsInterrupted returns true if the cause of the error is an interruption. This is for example returned by the service Start function when it is being shut down before the service is started.

func IsInvalidState

func IsInvalidState(err error) bool

IsInvalidState returns true if the cause of the error is an invalid initial state. This can be for example trying to start a stopped service, or stopping a stopped service.

func Wait

func Wait(duration time.Duration) func() <-chan error

Wait is a helper function replacing a readiness probe for cases where it is relevant to wait a specified amount of time before a service is ready. This function returns a channel that is closed after the specified duration.

Types

type Action

type Action uint8

Action defines the action to be taken when an event occurs.

const (
	// Undefined action.
	Undefined Action = iota
	// DoNothing instructs no action.
	DoNothing
	// Shutdown the service.
	Shutdown
	// Terminate the service.
	Terminate
)

type ContextHook

type ContextHook = func(context.Context) error

ContextHook is a context-aware hook.

func DropContext

func DropContext(hook Hook) ContextHook

DropContext is a helper function wrapping a context-naive hook as a context hook. The context provided to the resulting ContextHook is discarded.

type ErrorHook

type ErrorHook = func(event Event) error

ErrorHook is a hook aimed at receiving events and optionally transforming errors.

type Event

type Event struct {
	// The context of the event, typically the one passed to the function from
	// which the event originated.
	Context context.Context
	// The error that caused this status change, if any.
	Error error
	// The previous status of the service.
	From State
	// The new status of the service.
	To State
}

Event is a struct passed to a service observers on a state change. It contains contextual information about the event.

type Hook

type Hook = func() error

Hook is a naive hook.

type Hooks

type Hooks struct {
	// A friendly name for the service (optional)
	Name string
	// Start the service. This function is expected to block while the service
	// is running. Returning from this function will cause the service to
	// transition to Stopped if no error is returned or if the returned error is
	// ignored by the Error hook. In other cases, the service will transition to
	// an Error state.
	Start ContextHook
	// Gracefully shuts down the service. This function is expected to block
	// until the service is shut down. Returning an error from this function
	// will cause the service to transition to an Error state, unless the error
	// is ignored by the Error hook. In this case, the server will remain in a
	// ShuttingDown state until it is either terminated, or the Start hook
	// returns.
	Shutdown ContextHook
	// Terminates the service. This function is expected to quickly terminate
	// the service. The service will remain blocked until this function returns.
	// Returning an error from this function will cause the service to
	// transition to an Error state, unless the error is ignored by the Error
	// hook. In this case, the server will remain in a Terminating state until
	// it is either terminated, or the Start hook returns.
	Terminate ContextHook
	// Error receives error events. The event struct contains the context
	// passed to the hook from which it occurred, as well as the error itself.
	// The error returned by this function will be passed to the caller. If nil
	// is returned, the error is ignored. This can be useful to suppress errors,
	// for example http.ErrServerClosed when the service wraps an HTTP server.
	Error ErrorHook
}

Hooks contain the functions called by the worker to control the underlying service.

type Logger

type Logger interface {
	// Logs an info message.
	Info(msg string, keysAndValues ...interface{})
	// Logs an error.
	Error(err error, msg string, keysAndValues ...interface{})
}

Logger is a simple logger interface accepting key-value pair parameters.

type Service

type Service interface {
	// Name provides a user-friendly name for the service, that is used in
	// the logs.
	Name() string
	// Starts the service. This function blocks until the service is
	// stopped.
	Start() error
	// Starts the service providing context. This function blocks until the
	// service is stopped.
	StartCtx(ctx context.Context) error
	// StartBackground starts the service in the background. This function does
	// not block. It should typically be used in combination with Done.
	StartBackground() error
	// StartBackground starts the service in the background providing context.
	// This function does not block. It should typically be used in combination
	// with Done.
	StartBackgroundCtx(ctx context.Context) error
	// Shutdown shuts the service down gracefully.
	Shutdown() error
	// ShutdownCtx shuts the service down gracefully providing context.
	ShutdownCtx(ctx context.Context) error
	// Terminate forcefully terminates the service.
	Terminate() error
	// TerminateCtx forcefully terminates the service providing context.
	TerminateCtx(ctx context.Context) error
	// Ready returns a chan that is closed when the service is either started or
	// has transitioned to an Error state.
	Ready() <-chan struct{}
	// Done returns a chan that is closed when the service is either stopped or
	// has transitioned to an Error state.
	Done() <-chan struct{}
	// State returns the current state of the service.
	State() State
	// Observes registers a chan on which the service will post lifecycle events
	// such as state changes and errors. No action is taken if ch is nil.
	Observe(ch chan<- Event)
	// Unobserve removes the provided chan from the list of observers. No action
	// is taken if ch is nil or not in the list of observers.
	Unobserve(ch chan<- Event)
}

Service represents a process that can be started, shut down or terminated. It exposes a set of methods to control the state of the service.

type ServiceOptions

type ServiceOptions struct {
	// ReadinessProbe allows to specify how the service transitions from a
	// Starting to a Started state. If provided, the service will wait for the
	// chan returned by this function to either be closed, or to return
	// an error. In the latter case, the service will transition to an Error
	// state, unless the error is ignored by the Error hook.
	ReadinessProbe func() <-chan error
	// ShutdownTimeout defines a maximum amount of time for which the service
	// can remain in ShuttingDown state. When the specified amount of time
	// is elapsed, the service is terminated (default: 15 seconds).
	ShutdownTimeout time.Duration
	// Signals defines the signals to listen to. When one of these signals is
	// received, the action defined by SignalAction will be taken (default:
	// syscall.SIGINT, syscall.SIGTERM).
	Signals []os.Signal
	// Defines the action to be taken when a signal is received (default:
	// Shutdown)
	SignalAction Action
	// Sets the Logger to use to log worker events. If nil, the logging messages
	// are discarded.
	Logger Logger
}

ServiceOptions contains options for the service.

type State

type State uint8

State represents a state in the lifecycle state machine. The state machine provided by this package is the following:

     +--------------+
     | Initial      |
     +-+------------+
       |
     +-v------------+
+----+ Starting     +----+
|    +-+------------+    |
|      |                 |
|    +-v------------+    |
+----+ Started      +----+
|    +-+------------+    |
|      |                 |
|    +-v------------+    |
+----+ ShuttingDown +----+
|    +-+------------+    |
|      |                 |
|    +-v------------+  +-v------------+
+----+ Stopped      <--+ Terminating  |
|    +--------------+  +-+------------+
|                        |
|    +--------------+    |
+----> Error        <----+
     +--------------+
const (
	// Initial state of a service
	Initial State = iota
	// Starting represents a system that is in the process of starting.
	Starting
	// Started represents a running service.
	Started
	// ShuttingDown represents a process being shut down gracefully.
	ShuttingDown
	// Terminating represents a process being forcefully terminated.
	Terminating
	// Stopped represents a service shut down without errors.
	Stopped
	// Error represents a service having reached an error.
	Error
)

func (State) String

func (s State) String() string

type Worker

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

Worker is a Service that can be started, stopped and terminated based on a set of provided hooks.

func NewWorker

func NewWorker(hooks *Hooks) *Worker

NewWorker creates a Worker with the provided hooks. It returns nil if either of the hook structure, the start hook or the shutdown hook is nil.

func NewWorkerWithOptions

func NewWorkerWithOptions(hooks *Hooks, opts *ServiceOptions) *Worker

NewWorkerWithOptions creates a Worker with the provided hooks and options It returns nil if either of the hook structure, the start hook or the shutdown hook is nil.

func (*Worker) Done

func (c *Worker) Done() <-chan struct{}

Done returns a chan that is closed when the service is either stopped or has transitioned to an Error state.

func (*Worker) Name

func (c *Worker) Name() string

Name provides a user-friendly name for the service, that is used in the logs.

func (*Worker) Observe

func (c *Worker) Observe(ch chan<- Event)

Observe registers a chan on which the service will post lifecycle events such as state changes and errors. No action is taken if ch is nil.

func (*Worker) Ready

func (c *Worker) Ready() <-chan struct{}

Ready returns a chan that is closed when the service is either started or has transitioned to an Error state.

func (*Worker) Shutdown

func (c *Worker) Shutdown() error

Shutdown shuts the service down gracefully. This function returns a non-nil error if the Shutdown hook returns an error, unless the error is ignored by the Error hook.

func (*Worker) ShutdownCtx

func (c *Worker) ShutdownCtx(ctx context.Context) error

ShutdownCtx shuts the service down gracefully providing context. This function returns a non-nil error if the Shutdown hook returns an error, unless the error is ignored by the Error hook.

func (*Worker) Start

func (c *Worker) Start() error

Start the service. This function blocks until the service is stopped. This function returns a non-nil error if either the start hook or the readiness probe return an error, unless the error is ignored by the Error hook.

func (*Worker) StartBackground

func (c *Worker) StartBackground() error

StartBackground starts the service in the background. This function does not block. It should typically be used in combination with Done. This function returns a non-nil error if either the start hook or the readiness probe return an error, unless the error is ignored by the Error hook.

func (*Worker) StartBackgroundCtx

func (c *Worker) StartBackgroundCtx(ctx context.Context) error

StartBackgroundCtx starts the service in the background providing background. This function does not block. It should typically be used in combination with Done. This function returns a non-nil error if either the start hook or the readiness probe return an error, unless the error is ignored by the Error hook.

func (*Worker) StartCtx

func (c *Worker) StartCtx(ctx context.Context) error

StartCtx starts the service providing context. This function blocks until the service is stopped. This function returns a non-nil error if either the start hook or the readiness probe return an error, unless the error is ignored by the Error hook.

func (*Worker) State

func (c *Worker) State() State

State returns the current state of the service.

func (*Worker) Terminate

func (c *Worker) Terminate() error

Terminate forcefully terminates the service. This function returns a non-nil error if the Shutdown hook returns an error, unless the error is ignored by the Error hook.

func (*Worker) TerminateCtx

func (c *Worker) TerminateCtx(ctx context.Context) error

TerminateCtx forcefully terminates the service providing context. This function returns a non-nil error if the Shutdown hook returns an error, unless the error is ignored by the Error hook.

func (*Worker) Unobserve

func (c *Worker) Unobserve(ch chan<- Event)

Unobserve removes the provided chan from the list of observers. No action is taken if ch is nil or not in the list of observers.

Directories

Path Synopsis
examples
http command

Jump to

Keyboard shortcuts

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