run

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Feb 16, 2024 License: Apache-2.0 Imports: 12 Imported by: 23

README

run

This package contains a universal mechanism to manage goroutine lifecycles. It implements an actor-runner with deterministic teardown. It uses the https://github.com/oklog/run/ package as its basis and enhances it with configuration registration and validation as well as pre-run phase logic.

See godoc for information how to use
run.Group

Documentation

Overview

Package run implements an actor-runner with deterministic teardown. It uses the concepts found in the https://github.com/oklog/run/ package as its basis and enhances it with configuration registration and validation as well as pre-run phase logic.

Example
package main

import (
	"errors"
	"fmt"
	"os"

	"github.com/spf13/pflag"
	"github.com/tetratelabs/run"
	"github.com/tetratelabs/run/pkg/signal"
)

func main() {
	var (
		g run.Group
		p PersonService
		s signal.Handler
	)

	// add our PersonService
	g.Register(&p)

	// add a SignalHandler service
	g.Register(&s)

	// Start our services and block until error or exit request.
	// If sending a SIGINT to the process, a graceful shutdown of the
	// application will occur.
	if err := g.Run(); err != nil {
		fmt.Printf("Unexpected exit: %v\n", err)
		os.Exit(-1)
	}
}

// PersonService implements run.Config, run.PreRunner and run.GroupService to
// show a fully managed service lifecycle.
type PersonService struct {
	name string
	age  int

	closer chan error
}

func (p PersonService) Name() string {
	return "person"
}

// FlagSet implements run.Config and thus its configuration and flag handling is
// automatically registered when adding the service to Group.
func (p *PersonService) FlagSet() *pflag.FlagSet {
	flags := pflag.NewFlagSet("PersonService's flags", pflag.ContinueOnError)

	flags.StringVarP(&p.name, "name", "-n", "john doe", "name of person")
	flags.IntVarP(&p.age, "age", "a", 42, "age of person")

	return flags
}

// Validate implements run.Config and thus its configuration and flag handling
// is automatically registered when adding the service to Group.
func (p PersonService) Validate() error {
	var errs []error
	if p.name == "" {
		errs = append(errs, errors.New("invalid name provided"))
	}
	if p.age < 18 {
		errs = append(errs, errors.New("invalid age provided, we don't serve minors"))
	}
	if p.age > 110 {
		errs = append(errs, errors.New("faking it? or life expectancy assumptions surpassed by future healthcare system"))
	}
	return errors.Join(errs...)
}

// PreRun implements run.PreRunner and thus this method is run at the pre-run
// stage of Group before starting any of the services.
func (p *PersonService) PreRun() error {
	p.closer = make(chan error)
	return nil
}

// Serve implements run.GroupService and is executed at the service run phase of
// Group in order of registration. All Serve methods must block until requested
// to Stop or needing to fatally error.
func (p PersonService) Serve() error {
	<-p.closer
	return nil
}

// GracefulStop implements run.GroupService and is executed at the shutdown
// phase of Group.
func (p PersonService) GracefulStop() {
	close(p.closer)
}
Output:

Index

Examples

Constants

View Source
const BinaryName = "{{.Name}}"

BinaryName holds the template variable that will be replaced by the Group name in HelpText strings.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config interface {
	// Unit is embedded for Group registration and identification
	Unit
	// FlagSet returns an object's FlagSet
	FlagSet() *FlagSet
	// Validate checks an object's stored values
	Validate() error
}

Config interface should be implemented by Group Unit objects that manage their own configuration through the use of flags. If a Unit's Validate returns an error it will stop the Group immediately.

type Error

type Error string

Error allows for creating constant errors instead of sentinel ones.

const ErrBailEarlyRequest Error = "exit request from flag handler"

ErrBailEarlyRequest is returned when a call to RunConfig was successful but signals that the application should exit in success immediately. It is typically returned on --version and --help requests that have been served. It can and should be used for custom config phase situations where the job of the application is done.

const ErrRequestedShutdown Error = "shutdown requested"

ErrRequestedShutdown can be used by Service implementations to gracefully request a shutdown of the application. Group will then exit without errors.

func (Error) Error

func (e Error) Error() string

Error implements error.

type FlagSet

type FlagSet struct {
	*pflag.FlagSet
	Name string
}

FlagSet holds a pflag.FlagSet as well as an exported Name variable for allowing improved help usage information.

func NewFlagSet

func NewFlagSet(name string) *FlagSet

NewFlagSet returns a new FlagSet for usage in Config objects.

type Group

type Group struct {
	// Name of the Group managed service. If omitted, the binary name will be
	// used as found at runtime.
	Name string
	// HelpText is optional and allows to provide some additional help context
	// when --help is requested.
	HelpText string
	Logger   telemetry.Logger
	// contains filtered or unexported fields
}

Group builds on concepts from https://github.com/oklog/run to provide a deterministic way to manage service lifecycles. It allows for easy composition of elegant monoliths as well as adding signal handlers, metrics services, etc.

func (*Group) Deregister

func (g *Group) Deregister(units ...Unit) []bool

Deregister will inspect the provided objects implementing the Unit interface to see if it needs to de-register the objects for any of the Group bootstrap phases. The returned array of booleans is of the same size as the amount of provided Units, signalling for each provided Unit if it successfully de-registered with Group for at least one of the bootstrap phases or if it was ignored. It is generally safe to use Deregister at any bootstrap phase except at Serve time (when it will have no effect). WARNING: Dependencies between Units can cause a crash as a dependent Unit might expect the other Unit to gone through all the needed bootstrapping phases.

func (Group) ListUnits

func (g Group) ListUnits() string

ListUnits returns a list of all Group phases and the Units registered to each of them.

func (*Group) Register

func (g *Group) Register(units ...Unit) []bool

Register will inspect the provided objects implementing the Unit interface to see if it needs to register the objects for any of the Group bootstrap phases. If a Unit doesn't satisfy any of the bootstrap phases it is ignored by Group. The returned array of booleans is of the same size as the amount of provided Units, signalling for each provided Unit if it successfully registered with Group for at least one of the bootstrap phases or if it was ignored.

Important: It is a design flaw for a Unit implementation to adhere to both the Service and ServiceContext interfaces. Passing along such a Unit will cause Register to throw a panic!

func (*Group) Run

func (g *Group) Run(args ...string) (err error)

Run will execute all phases of all registered Units and block until an error occurs. If RunConfig has been called prior to Run, the Group's Config phase will be skipped and Run continues with the PreRunner and Service phases.

The following phases are executed in the following sequence:

Initialization phase (serially, in order of Unit registration)
  - Initialize()     Initialize Unit's supporting this interface.

Config phase (serially, in order of Unit registration)
  - FlagSet()        Get & register all FlagSets from Config Units.
  - Flag Parsing     Using the provided args (os.Args if empty).
  - Validate()       Validate Config Units. Exit on first error.

PreRunner phase (serially, in order of Unit registration)
  - PreRun()         Execute PreRunner Units. Exit on first error.

Service and ServiceContext phase (concurrently)
  - Serve()          Execute all Service Units in separate Go routines.
    ServeContext()   Execute all ServiceContext Units.
  - Wait             Block until one of the Serve() or ServeContext()
                     methods returns.
  - GracefulStop()   Call interrupt handlers of all Service Units and
                     cancel the context.Context provided to all the
                     ServiceContext units registered.

Run will return with the originating error on:
- first Config.Validate()  returning an error
- first PreRunner.PreRun() returning an error
- first Service.Serve() or ServiceContext.ServeContext() returning

Note: it is perfectly acceptable to use Group without Service and ServiceContext units. In this case Run will just return immediately after having handled the Config and PreRunner phases of the registered Units. This is particularly convenient if using the common pkg middlewares in a CLI, script, or other ephemeral environment.

func (*Group) RunConfig

func (g *Group) RunConfig(args ...string) (err error)

RunConfig runs the Config phase of all registered Config aware Units. Only use this function if needing to add additional wiring between config and (pre)run phases and a separate PreRunner phase is not an option. In most cases it is best to use the Run method directly as it will run the Config phase prior to executing the PreRunner and Service phases. If an error is returned the application must shut down as it is considered fatal. In case the error is an ErrBailEarlyRequest the application should clean up and exit without an error code as an ErrBailEarlyRequest is not an actual error but a request for Help, Version or other task that has been finished and there is no more work left to handle.

type Initializer

type Initializer interface {
	// Unit is embedded for Group registration and identification
	Unit
	Initialize()
}

Initializer is an extension interface that Units can implement if they need to have certain properties initialized after creation but before any of the other lifecycle phases such as Config, PreRunner and/or Serve are run. Note, since an Initializer is a public function, make sure it is safe to be called multiple times.

type Lifecycle

type Lifecycle interface {
	Unit

	// Context returns a context that gets cancelled when application
	// is stopped.
	Context() context.Context
}

Lifecycle tracks application lifecycle. And allows anyone to attach to it by exposing a `context.Context` that will end at the shutdown phase.

func NewLifecycle

func NewLifecycle() Lifecycle

NewLifecycle returns a new application lifecycle tracker.

type Namer

type Namer interface {
	GroupName(string)
}

Namer is an extension interface that Units can implement if they need to know or want to use the Group.Name. Since Group's name can be updated at runtime by the -n flag, Group first parses its own FlagSet the know if its Name needs to be updated and then runs the Name method on all Units implementing the Namer interface before handling the Units that implement Config. This allows these units to have the Name method be used to adjust the default values for flags or any other logic that uses the Group name to make decisions.

type PreRunner

type PreRunner interface {
	// Unit is embedded for Group registration and identification
	Unit
	PreRun() error
}

PreRunner interface should be implemented by Group Unit objects that need a pre run stage before starting the Group Services. If a Unit's PreRun returns an error it will stop the Group immediately.

func NewPreRunner

func NewPreRunner(name string, fn func() error) PreRunner

NewPreRunner takes a name and a standalone pre runner compatible function and turns them into a Group compatible PreRunner, ready for registration.

type Service

type Service interface {
	// Unit is embedded for Group registration and identification
	Unit
	// Serve starts the GroupService and blocks.
	Serve() error
	// GracefulStop shuts down and cleans up the GroupService.
	GracefulStop()
}

Service interface should be implemented by Group Unit objects that need to run a blocking service until an error occurs or a shutdown request is made. The Serve method must be blocking and return an error on unexpected shutdown. Recoverable errors need to be handled inside the service itself. GracefulStop must gracefully stop the service and make the Serve call return.

Since Service is managed by Group, it is considered a design flaw to call any of the Service methods directly in application code.

An alternative to implementing Service can be found in the ServiceContext interface which allows the Group Unit to listen for the cancellation signal from the Group provided context.Context.

Important: Service and ServiceContext are mutually exclusive and should never be implemented in the same Unit.

type ServiceContext added in v0.2.0

type ServiceContext interface {
	// Unit is embedded for Group registration and identification
	Unit
	// ServeContext starts the GroupService and blocks until the provided
	// context is cancelled.
	ServeContext(ctx context.Context) error
}

ServiceContext interface should be implemented by Group Unit objects that need to run a blocking service until an error occurs or the by Group provided context.Context sends a cancellation signal.

An alternative to implementing ServiceContext can be found in the Service interface which has specific Serve and GracefulStop methods.

Important: Service and ServiceContext are mutually exclusive and should never be implemented in the same Unit.

type Unit

type Unit interface {
	Name() string
}

Unit is the default interface an object needs to implement for it to be able to register with a Group. Name should return a short but good identifier of the Unit.

Directories

Path Synopsis
pkg
log
signal
Package signal implements a run.GroupService handling incoming unix signals.
Package signal implements a run.GroupService handling incoming unix signals.
test
Package test adds helper utilities for testing run.Group enabled services.
Package test adds helper utilities for testing run.Group enabled services.
version
Package version can be used to implement embedding versioning details from git branches and tags into the binary importing this package.
Package version can be used to implement embedding versioning details from git branches and tags into the binary importing this package.

Jump to

Keyboard shortcuts

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