bootseq

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Dec 14, 2019 License: MIT Imports: 8 Imported by: 0

README

bootseq GoDoc

Package bootseq provides a general-purpose boot sequence manager with separate startup/shutdown phases, cancellation and a simple abstraction that allows easy control over execution order and concurrency.

Installation

This package supports Go Modules. Simply run:

go get github.com/mkock/bootseq

to get the latest tagged version.

Quickstart

seq := bootseq.New("My Boot Sequence")
seq.Add("some-service", someServiceUp, someServiceDown)
seq.Add("other-service", otherServiceUp, otherServiceDown)
seq.Add("third-service", thirdServiceUp, thirdServiceDown)
seq.Sequence("(some-service : other-service) > third-service")
up := seq.Up(context.Background())
up.Wait()

// Your application is now ready!

down := up.Down(context.Background())
down.Wait()

Introduction

A "boot sequence" is one or more steps to execute. Each step is a name and two functions, one for booting up (the up phase) and one for shutting down (the down phase).

A sequence is a predefined order of steps, in the form of step names that can be grouped by parentheses and separated by two different characters, > for serial execution, and : for parallel execution. A concrete sequence of a specific set of steps is referred to as a formula.

The formula should be described as the boot sequence would look for the up phase. The down phase is the same formula, but executed in reverse.

The practical considerations of executing the two up/down phases is handled by an agent, with each agent being in charge of exactly one phase.

Usage

Start by analyzing your boot sequence. Some services must run in a certain order (ie. you must establish a database connection before you can preload data for an in-memory cache), and some services can run concurrently when they don't depend on each other.

Next, write two functions per service: a bootup function and a shutdown function. If you don't need both, you can use bootseq.Noop as a replacement, it simply does nothing.

Once your functions are ready, you just need to register them under a meaningful name and then write a formula of the boot sequence. This is a simple string that describes the flow with a couple of special characters for instructing bootseq on which services to execute in-order, and which to execute concurrently.

Syntax

In order to define the formula, a little background knowledge of the syntax is in order.

  • Any word must match one of your registered service names. You may use underscore and dash as well as uppercase letters in your names, but no spacing or any other character. It's recommended to stick with lowercase letters only and to use short names, ie. "mysql" rather than "database_manager".
  • Separate words by the character > for sequences, ie. where services must be executed in chronological order, and : for when services can be executed concurrently.
  • Use parenthesis to group services whenever there are changes to the execution order. The parser is not sophisticated and may need some help figuring out the service groupings.

Make sure to register all services before defining your formula. Errors will be raised when a word is encountered that doesn't match a service name.

Examples

// Run services "mysql", "aerospike" and "cache_stuff" in chronological order.
seq.Sequence("mysql > aerospike > cache_stuff")

// Run service "load_config" first, then "mysql" and "aerospike" concurrently,
// followed by "cache_stuff" after both "mysql" and "aerospike" are finished. 
seq.Sequence("load_config > (mysql : aerospike) > cache_stuff")

// Run service "logging", followed by "error_handling" and then three other
// services that can run concurrently. 
seq.Sequence("logging > error_handling > (mysql : aerospike : kafka)")

Details

Progress reports

Once a boot sequence is in progress, you may call either Agent.Wait() or Agent.Progress() on the agent. It's a panic if you call both.

Agent.Wait() will block while listening to a special channel on which progress reports are sent at the end of each step's execution. It returns when the sequence has completed, or if an error was raised during execution.

Agent.Progress(), on the other hand, will return the very same progress channel to you. You can then range over each element to receive progress updates as they happen. Although these progress reports don't tell you how far you are in the execution sequence, you can easily devise your own progress indicator by getting the total number of steps from Instance.CountSteps() combined with a counter that is incremented with one for every progress report received.

Progress reports are simple structs containing the name of the executed service and an error (which is nil for successful execution):

type Progress struct {
	Service string
	Err     error
}

Due to the fact that execution steps may be cancelled or time out due to their associated context, the reported error can be of type context.Canceled or context.DeadlineExceeded. It can also be of any type returned by your service functions.

Cancellation

Any boot sequence can be cancelled by calling Agent.Up() with a context that can be cancelled. A call to Context.Cancel() is checked before each step, so a step that is in progress, will finish before stopping. For steps that contain multiple concurrent steps, the agent will wait for each one to finish before stopping.

Builtin Limitations

  • Any manager cannot contain more than 65535 services
  • Any sequence cannot contain more than 256 steps

A panic is raised if any of these limitations are breached.

Feature Wish List

  • Optional injection of logger during instantiation
  • Proper shutdown on panics and cancellations

Contributing

Contributions are welcome in the form of well-explained PR's along with some beautiful unit tests ;-)

License

This software package is released under the MIT License.

Documentation

Overview

Package bootseq provides a general-purpose boot sequence manager with separate startup/shutdown phases, cancellation and a simple abstraction that allows easy control over execution order and concurrency.

Quick Start

seq := bootseq.New("My Boot Sequence")
seq.Add("some-service", someServiceUp, someServiceDown)
seq.Add("other-service", otherServiceUp, otherServiceDown)
seq.Add("third-service", thirdServiceUp, thirdServiceDown)

// Execute "some-service" and "other-service" concurrently, followed by "third-service".
seq.Sequence("(some-service : other-service) > third-service")
up := seq.Up(context.Background())
up.Wait()

// Your application is now ready!

down := up.Down(context.Background())
down.Wait()

Please refer to the enclosed README for more details.

Example (Basic)
package main

import (
	"context"
	"fmt"
	"github.com/mkock/bootseq"
	"strings"
)

func main() {
	// Let's use a boot sequence to construct a sentence!
	// For the shutdown sequence, we'll "deconstruct" it by removing each word.
	var words []string

	add := func(word string) func() error {
		return func() error {
			words = append(words, word)
			return nil
		}
	}

	rm := func() error {
		words = words[:len(words)-1]
		return nil
	}

	seq := bootseq.New("Basic Example")
	seq.Add("welcome", add("Welcome"), rm)
	seq.Add("to", add("to"), rm)
	seq.Add("my", add("my"), rm)
	seq.Add("world", add("world!"), rm)
	i, err := seq.Sequence("welcome > to > my > world")
	if err != nil {
		panic(err)
	}

	// Bootup sequence.
	up := i.Up(context.Background())
	up.Wait()

	fmt.Println(strings.Join(words, " "))

	// Shutdown sequence.
	down := up.Down(context.Background())
	down.Wait()

	fmt.Println(strings.Join(words, " "))

}
Output:

Welcome to my world!
Example (Progress)
package main

import (
	"github.com/mkock/bootseq"
)

func main() {
	bootseq.New("Boot it!")

	// ...

}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Errop

func Errop() error

Errop (error operation) is a convenience function you can use in place of a step function for when you want a function that returns an error.

func Noop

func Noop() error

Noop (no operation) is a convenience function you can use in place of a step function for when you want a function that does nothing.

func Panicop

func Panicop() error

Panicop (panic operation) is a convenience function you can use in place of a step function for when you want a function that panics.

func Sleepop

func Sleepop() error

Sleepop (sleep operation) is a convenience function you can use in place of a step function for when you want a function that sleeps for a short while.

Types

type Agent

type Agent struct {
	sync.Mutex // Controls access to Agent.callee.
	// contains filtered or unexported fields
}

Agent represents the execution of a sequence of steps. For any sequence, there will be two agents in play: one for the bootup sequence, and another for the shutdown sequence. The only difference between these two is the order in which the sequence is executed. Each agent keeps track of its progress and handles execution of sequence steps.

func (*Agent) Down

func (a *Agent) Down(ctx context.Context) *Agent

Down starts the shutdown sequence. It returns a new agent for controlling and monitoring execution of the sequence.

func (*Agent) Progress

func (a *Agent) Progress() chan Progress

Progress returns a channel that will receive a Progress struct every time a step in the boot sequence has completed. In case of an error, execution will stop and no further progress reports will be sent. Consequently, there will either be a progress report for each step in the sequence, or if execution stops short, the last progress report sent will contain an error.

func (*Agent) Wait

func (a *Agent) Wait() error

Wait will block until execution of the boot sequence has completed. It returns an error if any steps in the sequence failed.

type ErrParsingFormula

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

ErrParsingFormula represents a parse problem with the formula to the Sequence() method.

func (ErrParsingFormula) Error

func (e ErrParsingFormula) Error() string

Error satisfies the error interface by returning an error message with parse error details.

type Func

type Func func() error

Func is the type used for any function that can be executed as a service in a boot sequence. Any function that you wish to register and execute as a service must satisfy this type.

type Instance

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

Instance contains the actual sequence of steps that need to be performed during execution of the boot sequence. It also keeps track of progress along the way, and provides the Up() method for starting the boot sequence.

func (Instance) CountSteps

func (i Instance) CountSteps() uint8

CountSteps returns the number of steps currently added to the Instance. It counts steps recursively, covering all sub-steps. The count is for a single sequence (up/down), so you'll need to multiply this number by two to cover both sequences.

func (Instance) Up

func (i Instance) Up(ctx context.Context) *Agent

Up executes the startup phase, returning an agent for keeping track of, and controlling the execution of the sequence.

type Manager

type Manager struct {
	Name string
	// contains filtered or unexported fields
}

Manager represents a single boot sequence with its own name. Actual up/down functions are stored (and referenced) by name in the map services.

func New

func New(name string) Manager

New returns a new and uninitialised boot sequence manager.

func (Manager) Add

func (m Manager) Add(name string, up, down Func)

Add adds a single named service to the boot sequence, with the given "up" and "down" functions. If a service with the given name already exists, the provided up- and down functions replace those already registered.

func (Manager) Sequence

func (m Manager) Sequence(form string) (Instance, error)

Sequence takes a formula (see package-level comment) and returns an Instance that acts as the main struct for calling Up() and keeping track of progress.

func (Manager) ServiceCount

func (m Manager) ServiceCount() uint16

ServiceCount returns the number of services currently registered with the Manager.

func (Manager) ServiceNames

func (m Manager) ServiceNames() []string

ServiceNames returns the name of each registered service, in no particular order.

type Progress

type Progress struct {
	Service string
	Err     error
}

The Progress is communicated on channels returned by methods Up() and Down() and provides feedback on the current progress of the boot sequence. This includes the name of the service that was last executed, along with an optional error if the step failed. err will be nil on success.

Jump to

Keyboard shortcuts

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