github.com/go-srvc/srvc
go get github.com/go-srvc/srvc@v0.3.0
README
Simple, Safe, and Modular Service Runner
srvc library provides a simple but powerful interface with zero external dependencies for running service modules.
Use Case
Normally Go services are composed of multiple "modules" which each run in their own goroutine such as http server, signal listener, kafka consumer, ticker, etc. These modules should remain alive throughout the lifecycle of the whole service, and if one goes down, graceful exit should be executed to avoid "zombie" services. srvc takes care of all this via a simple module interface.
List of ready made modules can be found under github.com/go-srvc/mods
Usage
Main package
package main
import (
"fmt"
"net/http"
"github.com/go-srvc/mods/httpmod"
"github.com/go-srvc/mods/logmod"
"github.com/go-srvc/mods/metermod"
"github.com/go-srvc/mods/sigmod"
"github.com/go-srvc/mods/sqlxmod"
"github.com/go-srvc/mods/tracemod"
"github.com/go-srvc/srvc"
)
func main() {
db := sqlxmod.New()
srvc.RunAndExit(
logmod.New(),
sigmod.New(),
tracemod.New(),
metermod.New(),
db,
httpmod.New(
httpmod.WithAddr(":8080"),
httpmod.WithHandler(handler(db)),
),
)
}
func handler(db *sqlxmod.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := db.DB().PingContext(r.Context()); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprint(w, "OK")
})
}
Implementing custom modules
package main
import "github.com/go-srvc/srvc"
func main() {
srvc.RunAndExit(&MyMod{})
}
type MyMod struct {
done chan struct{}
}
func (m *MyMod) Init() error {
m.done = make(chan struct{})
return nil
}
// Run should block until the module is stopped.
// If you don't have a blocking operation, you can use done channel to block.
func (m *MyMod) Run() error {
<-m.done
return nil
}
func (m *MyMod) Stop() error {
defer close(m.done)
return nil
}
func (m *MyMod) ID() string { return "MyMod" }
Lifecycle
Run executes modules through a deterministic lifecycle:
- Init is called sequentially in the order modules are passed. If any
Initreturns an error, the loop stops andStopis called on already-initialized modules in reverse order. Uninitialized modules never getInitorStop. - Run is started for each successfully initialized module in its own goroutine. Start order is not guaranteed.
- When the first
Runreturns (with or without error), the service moves to shutdown. - Stop is called sequentially in reverse order on every initialized module. Each module's
Stopmust cause itsRunto return. Runblocks until everyRungoroutine has returned, then returns the joined errors fromInit,Run, andStop(ornil).
Panic recovery
Panics inside Init, Run, or Stop are recovered. The stack trace is logged, and the panic is converted into an error wrapping srvc.ErrModulePanic so other modules can still shut down gracefully.
Exit behaviour
RunAndExit calls os.Exit(1) if Run returns any error, and returns normally on success.
Contracts modules must uphold
Stopmust makeRunreturn. IfRunignoresStop,srvc.Runwill block in its final wait. There is no built-in shutdown timeout, so a stuck module hangs the service.IDshould return a stable, unique identifier used for log attribution.
Acknowledgements
This library is something I have found myself writing over and over again in every project I been part of. One of the iterations can be found under https://github.com/elisasre/go-common.
Overview
Package srvc provides simple but powerful Run functionality on top of Module abstraction. Ready made modules can be found under: github.com/go-srvc/mods.
Constants
const ErrModulePanic = ErrStr("module recovered from panic")Variables
var JoinErrors = errors.JoinJoinErrors is used by srvc to combine multiple errors into one. Override it to plug in custom multi-error formatting.
Functions
func Run
func Run(modules ...Module) errorRun will run all given modules using following control flow:
- Exec Init() for each module in order. If any Init() returns error the Init loop is stopped and Stop() will be called for already initialized modules in reverse order.
- Exec Run() for each module in own goroutine so order isn't guaranteed.
- Wait for any Run() function to return nil or an error and move to Stop loop.
- Exec Stop() for modules in reverse order.
- Wait for all Run() goroutines to return.
- Return all errors or nil
Panics inside modules are recovered so other modules can shut down gracefully. Recovered panics are returned as errors wrapping ErrModulePanic.
func RunAndExit
func RunAndExit(modules ...Module)RunAndExit is convenience wrapper for Run that calls os.Exit with code 1 in case of an error. The common use case is to srvc.RunAndExit from main function and let the srvc handle the rest.
package main
import "github.com/go-srvc/srvc"
func main() {
srvc.RunAndExit(
// Add your modules here
)
}
Types
type ErrGroup
type ErrGroup struct {
// contains filtered or unexported fields
}ErrGroup is a goroutine group that waits for all goroutines to finish and collects errors.
func (*ErrGroup) Go
func (eg *ErrGroup) Go(f func() error)Go runs the given function in a goroutine.
func (*ErrGroup) Wait
func (eg *ErrGroup) Wait() errorWait waits for all goroutines to finish and returns all errors that occurred.
type ErrStr
type ErrStr stringErrStr adds Error method to string type.
func (ErrStr) Error
func (e ErrStr) Error() stringtype Module
type Module interface {
// ID should return identifier for logging purposes.
ID() string
// Init allows synchronous initialization of module.
Init() error
// Run should start the module and block until stop is called or error occurs.
Run() error
// Stop is called for synchronous cleanup and must cause Run to return.
// If Init ran, Stop is guaranteed to run as part of cleanup.
Stop() error
}Examples
ExampleRun
package main
import (
"fmt"
"github.com/go-srvc/srvc"
)
type printMod struct{}
func (m *printMod) ID() string { return "printMod" }
func (m *printMod) Init() error { return nil }
func (m *printMod) Run() error { fmt.Println("hello"); return nil }
func (m *printMod) Stop() error { return nil }
func main() {
_ = srvc.Run(&printMod{})
}