# README
bootseq
Package bootseq provides a light-weight, general-purpose boot sequence manager with separate startup/shutdown phases, real-time progress reports, cancellation and a simple mechanism that allows for easy control over execution order and concurrency.
Compatibility: Go 1.7+
Installation
This package supports Go Modules. Simply run:
go get github.com/mkock/bootseq/v2
to get the latest tagged version.
Quickstart
sequence := bootseq.New("My Boot Sequence")
sequence.Register("some-service", someServiceUp, someServiceDown)
sequence.Register("other-service", otherServiceUp, otherServiceDown)
sequence.Register("third-service", thirdServiceUp, thirdServiceDown).After("other-service")
agent, err := sequence.Agent()
up := agent.Up(context.Background())
up.Wait()
// Your application is now ready!
down := agent.Down(context.Background())
down.Wait()
Preamble
This README describes v2.
Although v1 is still available, it has been deprecated due to its quirky syntax for formulating execution order which, to be honest, is much better handled via simple method calls. V2 greatly improves upon this.
Introduction
A "boot sequence" is one or more Services to execute. Each service is a name and two functions, one for booting up (the startup phase) and one for shutting down (the shutdown phase). These are registered with the Manager.
The practicalities of executing the two up/down phases is handled by an Agent, which can be instantiated directly from the Manager once Service registration has completed.
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 startup 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 define their order of
execution by calling Service.After("name-of-dependent-service")
.
If your application has multiple packages that need to do some processing during the startup and shutdown phases of
the boot sequence, you can register them in the init
functions of the respective packages.
Details
Progress reports
Once a boot sequence is in progress, you may call either Agent.Wait()
or
Agent.Progress()
on the agent. You'll get an error if you call both.
Agent.Wait()
will block while the boot sequence is running. It returns when the sequence has completed, or if
an error was raised during execution.
Agent.Progress()
, on the other hand, will return a channel of Progress
structs. You can then iterate over
each Progress
struct to receive progress updates as they occur. Although these progress reports don't tell you how
far you are in the boot sequence, you can easily devise your own progress indicator by getting the total number of
services from Manager.ServiceCount()
combined with a counter that is incremented by one for every Progress
report received. Progress reports also serve as error handlers.
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.
Note: you will receive exactly one Progress report per registered Service, plus one extra that indicates the end of the boot sequence. The last Progress report will always have an empty Service name.
Cancellation and Errors
Any boot sequence can be cancelled by calling Agent.Up()
with a context that can be cancelled.
A call to Context.Done()
is checked before each non-concurrent Service execution, so a Service that is
in progress will finish before stopping. For sequences where multiple Services are running concurrently, the Agent
will wait for all of them to finish before stopping.
If, for example, Service A, B and C run sequentially - one by one - and there is an error in B, that means that A would still have completed, but as the Agent is not waiting for any other concurrent Services, execution can halt immediately and C won't run.
In another example, Service A, B and C run concurrently, and Service D runs after C. If B fails, A and C will continue to run to completion, but execution stops afterwards, and D won't run.
Builtin Limitations
- A Manager cannot contain more than 65535 Services
- An Agent cannot contain more than 65535 priorities
A panic is raised if any of these limitations are breached.
Feature Wish List
- Optional injection of logger after instantiation
- Proper shutdown on panics and timeouts
- Method for unregistering services
FAQ
What happens if I register Service B before Service A, when B depends on A?
No problem. Service dependencies aren't resolved until Agent instantiation. You are free to register Services via the init
functions of your various packages - in fact, this is the intended use case.
Can more Services be added to the Manager after Agent instantiation?
While it's possible to continue adding more Services to the Manager after Agent instantiation, they will not be part of the Agent's boot sequence. You'll have to re-instantiate it.
What if registered Services have cyclic dependencies?
These will be detected during Agent instantiation, and you'll receive an error if this happens.
What are the dependencies for this package?
None. Just stdlib and golang.org/x/sync.
Contributing
Contributions are welcome in the form of well-explained PR's along with some beautiful unit tests ;-)
About v1
The v1 version of this package is deprecated and unsupported. In case you need to use it anyway, the documentation for v1 is here.
License
This software package is released under the MIT License.