- Notifications
You must be signed in to change notification settings - Fork0
Graceful Shutdown Manager for Go
License
marnixbouhuis/coda
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Coda is a Go library that helps manage graceful shutdowns of concurrent applications. It provides a structured way to organize goroutines into groups with dependencies and ensures they shut down in the correct order.
- Group-based goroutine management
- Dependency-based shutdown ordering
- Configurable timeouts and behavior
- Flexible logging options
- Error handling and propagation
go get github.com/marnixbouhuis/coda
shutdown:=coda.NewShutdown()// Create groupsdbGroup:=coda.Must(shutdown.NewGroup("database",nil))workerGroup:=coda.Must(shutdown.NewGroup("worker", []*coda.Group{dbGroup}))// Add goroutines to groupsdbGroup.Go(func(ctx context.Context,readyfunc())error {// Do startup work hereready()// Wait for shutdown signal<-ctx.Done()// Do tear down logic herereturnnil})// Wait for shutdownerr:=shutdown.Wait()
The shutdown manager is the main coordinator that manages groups and their shutdown sequence. Create one usingNewShutdown()
.
Options:
WithShutdownLogger(logger Logger)
- Set a custom logger (default: NoopLogger)
Groups are collections of goroutines that should be shut down together. Groups can depend on other groups, creating a shutdown hierarchy.
Create groups usingshutdown.NewGroup(name string, dependencies []*Group, opts ...GroupOption)
.
Group options:
WithGroupShutdownTimeout(duration)
- Maximum time to wait for group shutdown (default: no timeout)
Add goroutines to groups using theGo()
method. Each goroutine receives a context and a ready function.
group.Go(func(ctx context.Context,readyfunc())error {// Do startup here// Signal readinessready()// Wait for shutdown<-ctx.Done()// Do shutdown herereturnnil},opts...)
Goroutine options:
WithReadyTimeout(duration)
- Maximum time to wait for ready signal (default: no timeout)WithCrashOnReadyTimeoutHit(bool)
- Stop everything if ready timeout is hit (default: true)WithCrashOnError(bool)
- Stop everything if goroutine returns error (default: true)WithCrashOnEarlyStop(bool)
- Stop everything if goroutine exits before shutdown (default: true)WithBlock(bool)
- Block until goroutine signals ready (default: false)
funcExample() {sd:=coda.NewShutdown(coda.WithShutdownLogger(coda.NewStdLogger(log.Default())),)// Create groups with dependenciesdbGroup:=coda.Must(sd.NewGroup("database",nil,coda.WithGroupShutdownTimeout(5*time.Second),))workerGroup:=coda.Must(sd.NewGroup("workers", []*coda.Group{dbGroup},coda.WithGroupShutdownTimeout(10*time.Second),))// Database connectiondbGroup.Go(func(ctx context.Context,readyfunc())error {db,err:=sql.Open("postgres","connection-string")iferr!=nil {returnerr}deferdb.Close()ready()<-ctx.Done()returnnil},coda.WithBlock(true))// Start multiple workersforworkerID:=range3 {workerGroup.Go(func(ctx context.Context,readyfunc())error {log.Printf("Worker %d starting",workerID)ready()for {select {case<-ctx.Done():log.Printf("Worker %d shutting down",workerID)returnnilcase<-time.After(time.Second):// Do some worklog.Printf("Worker %d processing",workerID)}}},coda.WithBlock(true))}serverGroup:=coda.Must(sd.NewGroup("server", []*coda.Group{workerGroup,dbGroup}))serverGroup.Go(func(ctx context.Context,readyfunc())error {ready()mux:=http.NewServeMux()srv:=&http.Server{Addr:":8080",Handler:mux,ReadTimeout:5*time.Second,WriteTimeout:10*time.Second,}mux.HandleFunc("/demo",func(w http.ResponseWriter,_*http.Request) {w.WriteHeader(http.StatusOK)})gofunc() {<-ctx.Done()shutdownCtx,cancel:=context.WithTimeout(context.WithoutCancel(ctx),time.Second*30)defercancel()iferr:=srv.Shutdown(shutdownCtx);err!=nil {log.Printf("Failed to stop HTTP server gracefully: %v",err)sd.StopWithError(err)}}()iferr:=srv.ListenAndServe();err!=nil&&!errors.Is(err,http.ErrServerClosed) {returnerr}returnnil})gofunc() {ch:=make(chan os.Signal,1)signal.Notify(ch,syscall.SIGINT,syscall.SIGTERM)<-chsd.Stop()}()iferr:=sd.Wait();err!=nil {log.Printf("Shutdown error: %v",err)os.Exit(1)}}
Errors can occur in several ways:
- Goroutine returns an error
- Ready timeout is hit
- Goroutine stops early
- Shutdown timeout is hit
Configure how these errors are handled using the appropriate options. Errors can either crash the entire shutdown process or be logged and ignored.
Coda supports custom logging through theLogger
interface:
typeLoggerinterface {Info(strstring)Error(strstring)}
Built-in loggers:
NewNoopLogger()
- Discards all logs (default)NewStdLogger(logger *log.Logger)
- Adapts standard library logger
Loggers available as external module:
This project is licensed under the MIT License - see theLICENSE.md file for details.
About
Graceful Shutdown Manager for Go