https://github.com/moeryomenko/squad
Squad is package contains a shared shutdown primitive with helpful options.
https://github.com/moeryomenko/squad
errgroup golang graceful-shutdown
Last synced: 5 months ago
JSON representation
Squad is package contains a shared shutdown primitive with helpful options.
- Host: GitHub
- URL: https://github.com/moeryomenko/squad
- Owner: moeryomenko
- License: apache-2.0
- Created: 2021-06-02T13:59:56.000Z (about 5 years ago)
- Default Branch: main
- Last Pushed: 2025-11-26T07:23:53.000Z (7 months ago)
- Last Synced: 2025-11-29T06:48:11.189Z (7 months ago)
- Topics: errgroup, golang, graceful-shutdown
- Language: Go
- Homepage: https://pkg.go.dev/github.com/moeryomenko/squad
- Size: 191 KB
- Stars: 5
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE-APACHE
Awesome Lists containing this project
README
# ๐ Squad
[](https://pkg.go.dev/github.com/moeryomenko/squad)
[](https://goreportcard.com/report/github.com/moeryomenko/squad)
[](./LICENSE-MIT)
[](./LICENSE-APACHE)
**A shared shutdown primitive for graceful application lifecycle management in Go**
Squad helps you orchestrate goroutines, manage graceful shutdowns, and handle application lifecycle events with minimal boilerplate.
[Features](#-features) โข [Installation](#-installation) โข [Quick Start](#-quick-start) โข [Documentation](#-documentation) โข [Examples](#-examples)
---
## ๐ Table of Contents
- [About](#-about)
- [Features](#-features)
- [Installation](#-installation)
- [Quick Start](#-quick-start)
- [Usage](#-usage)
- [Basic Service](#basic-service)
- [HTTP Server](#http-server)
- [Consumer Workers](#consumer-workers)
- [Graceful Shutdown](#graceful-shutdown)
- [Bootstrap & Cleanup](#bootstrap--cleanup)
- [API Reference](#-api-reference)
- [Examples](#-examples)
- [Project Structure](#-project-structure)
- [Development](#-development)
- [Testing](#-testing)
- [FAQ](#-faq)
- [Contributing](#-contributing)
- [License](#-license)
- [Author](#-author)
---
## ๐ฏ About
**Squad** is a lightweight Go package that provides a shared shutdown primitive for managing application lifecycle. It allows you to:
- **Coordinate multiple goroutines** that must start and stop together
- **Handle graceful shutdowns** with configurable timeouts and grace periods
- **Manage HTTP servers** with proper shutdown sequences
- **Run consumer workers** (e.g., message queues, event streams) with graceful stop
- **Execute bootstrap and cleanup functions** for subsystems
- **Automatically handle OS signals** (SIGINT, SIGTERM, SIGHUP, SIGQUIT)
Squad is particularly useful for:
- Microservices and API servers
- Background workers and job processors
- Event-driven applications with multiple consumers
- Any Go application requiring coordinated startup/shutdown
The package is designed to be Kubernetes-aware, with default grace periods aligned with Pod termination lifecycle.
---
## โจ Features
- โก **Zero dependencies** - Uses only standard library and `github.com/moeryomenko/synx`
- ๐ **Coordinated lifecycle** - If one goroutine exits, others are signaled to stop
- ๐ก๏ธ **Graceful shutdowns** - Configurable grace periods and shutdown timeouts
- ๐ **HTTP server support** - Built-in wrapper for `http.Server` with proper shutdown
- ๐จ **Consumer pattern** - Graceful stop for message/event consumers without interrupting active handlers
- ๐ฏ **Signal handling** - Automatic SIGINT/SIGTERM/SIGHUP/SIGQUIT handling
- ๐ **Bootstrap/cleanup hooks** - Run initialization and cleanup functions
- โฑ๏ธ **Kubernetes-friendly** - Default 30s grace period matches k8s pod termination
- ๐งช **Well tested** - Comprehensive test coverage
- ๐ฆ **Simple API** - Minimal boilerplate, intuitive usage
---
## ๐ฆ Installation
```bash
go get github.com/moeryomenko/squad
```
**Requirements:**
- Go 1.25 or higher
---
## ๐ Quick Start
```go
package main
import (
"context"
"log"
"github.com/moeryomenko/squad"
)
func main() {
// Create a new squad with signal handler
s, err := squad.New(squad.WithSignalHandler())
if err != nil {
log.Fatal(err)
}
// Run your application logic
s.Run(func(ctx context.Context) error {
// Your code here
<-ctx.Done()
return nil
})
// Wait for shutdown
if err := s.Wait(); err != nil {
log.Printf("shutdown error: %v", err)
}
}
```
---
## ๐ Usage
### Basic Service
Create a simple service with signal handling:
```go
s, err := squad.New(squad.WithSignalHandler())
if err != nil {
log.Fatal(err)
}
s.Run(func(ctx context.Context) error {
// Your background work
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil
case <-ticker.C:
log.Println("tick")
}
}
})
s.Wait()
```
### HTTP Server
Launch an HTTP server with graceful shutdown:
```go
s, err := squad.New(squad.WithSignalHandler())
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
s.RunServer(&http.Server{Addr: ":8080"})
s.Wait()
```
### Consumer Workers
Run consumer workers that stop gracefully without interrupting active handlers:
```go
s, err := squad.New(squad.WithSignalHandler())
if err != nil {
log.Fatal(err)
}
s.RunConsumer(func(consumeCtx, handleCtx context.Context) error {
// consumeCtx: cancelled when shutdown starts
// handleCtx: never cancelled, allows active handlers to complete
for {
select {
case <-consumeCtx.Done():
return nil
default:
msg := receiveMessage() // Your message source
go processMessage(handleCtx, msg)
}
}
})
s.Wait()
```
### Graceful Shutdown
Run tasks with cleanup functions:
```go
s, err := squad.New(squad.WithSignalHandler(
squad.WithGracefulPeriod(30 * time.Second),
squad.WithShutdownTimeout(5 * time.Second),
))
if err != nil {
log.Fatal(err)
}
s.RunGracefully(
// Background function
func(ctx context.Context) error {
// Your work
<-ctx.Done()
return nil
},
// Cleanup function (runs on shutdown)
func(ctx context.Context) error {
log.Println("cleaning up...")
return closeResources(ctx)
},
)
s.Wait()
```
### Bootstrap & Cleanup
Initialize and cleanup subsystems:
```go
s, err := squad.New(
squad.WithSignalHandler(),
squad.WithBootstrap(
func(ctx context.Context) error {
return initDatabase(ctx)
},
func(ctx context.Context) error {
return connectToCache(ctx)
},
),
squad.WithCloses(
func(ctx context.Context) error {
return closeDatabase(ctx)
},
),
squad.WithSubsystem(
func(ctx context.Context) error { return openMessageQueue(ctx) },
func(ctx context.Context) error { return closeMessageQueue(ctx) },
),
)
if err != nil {
log.Fatal("initialization failed:", err)
}
// Your application code...
s.Wait()
```
---
## ๐ API Reference
### Core Types
- **`Squad`** - Main struct for coordinating goroutines
- **`ConsumerLoop`** - Function signature for consumer workers
### Constructor
- **`New(opts ...Option) (*Squad, error)`** - Create a new Squad instance
### Options
- **`WithSignalHandler(...ShutdownOpt)`** - Add OS signal handling
- **`WithGracefulPeriod(duration)`** - Set graceful shutdown period (default: 30s)
- **`WithShutdownTimeout(duration)`** - Set shutdown timeout (default: 2s)
- **`WithShutdownInGracePeriod(duration)`** - Set both graceful and shutdown timeout
- **`WithBootstrap(...func)`** - Add initialization functions
- **`WithCloses(...func)`** - Add cleanup functions
- **`WithSubsystem(init, close)`** - Add init+cleanup pair for a subsystem
### Methods
- **`Run(fn)`** - Run a function in the squad
- **`RunGracefully(backgroundFn, onDown)`** - Run with cleanup function
- **`RunServer(*http.Server)`** - Run HTTP server with graceful shutdown
- **`RunConsumer(ConsumerLoop)`** - Run consumer worker
- **`Wait() error`** - Block until all squad members exit
---
## ๐ก Examples
See the [`example/`](./example) directory for complete examples:
- **[simple.go](./example/simple.go)** - HTTP server with signal handling and graceful shutdown
Run the example:
```bash
go run example/simple.go
```
---
## ๐ Project Structure
```
squad/
โโโ squad.go # Core Squad implementation
โโโ consumers.go # Consumer worker helpers
โโโ options.go # Configuration options
โโโ squad_test.go # Unit tests
โโโ example/ # Example applications
โ โโโ simple.go
โโโ tools/ # Development tools
โโโ go.mod # Go module definition
โโโ Makefile # Build automation
โโโ LICENSE-APACHE # Apache 2.0 license
โโโ LICENSE-MIT # MIT license
โโโ README.md # This file
```
---
## ๐ Development
### Prerequisites
- Go 1.25+
- golangci-lint (for linting)
- go-mod-upgrade (for dependency management)
### Commands
```bash
# Run tests
make test
# Run tests with race detector
RACE_DETECTOR=1 make test
# View coverage report
make cover
# Run linter
make lint
# Update dependencies
make mod
# View all commands
make help
```
---
## ๐งช Testing
Squad includes comprehensive unit tests covering:
- Basic lifecycle management
- Bootstrap initialization
- Background task failures
- Shutdown failures and timeouts
- Error propagation
- Concurrent operations
Run tests:
```bash
go test ./...
# With race detector
go test -race ./...
# With coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
```
---
## โ FAQ
### Q: What happens if a goroutine panics?
Squad uses `synx.CtxGroup` which recovers from panics and converts them to errors. The error will be returned from `Wait()`.
### Q: What's the difference between graceful period and shutdown timeout?
- **Graceful period**: Total time allowed for the entire shutdown process
- **Shutdown timeout**: Time reserved for executing cleanup functions
The signal handler waits `gracefulPeriod - shutdownTimeout` before canceling the squad context.
### Q: Can I use Squad without signal handling?
Yes! Simply don't use `WithSignalHandler()`. You'll need to manage context cancellation yourself.
### Q: How does RunConsumer differ from Run?
`RunConsumer` provides two contexts:
- `consumeContext`: Cancelled on shutdown (stop accepting new work)
- `handleContext`: Never cancelled (allow in-flight work to complete)
This enables graceful consumer shutdown without interrupting active message handlers.
### Q: Is Squad safe for concurrent use?
Yes, Squad is designed to be used concurrently. Internal state is protected by mutexes.
### Q: What's the default grace period?
30 seconds, matching Kubernetes pod termination grace period. This ensures compatibility with k8s deployments.
---
## ๐ค Contributing
Contributions are welcome! Please feel free to submit issues, fork the repository, and send pull requests.
### Guidelines
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Add tests for new functionality
5. Run tests and linter (`make test lint`)
6. Commit your changes
7. Push to the branch (`git push origin feature/amazing-feature`)
8. Open a Pull Request
---
## ๐ License
This project is dual-licensed under your choice of:
- **MIT License** - See [LICENSE-MIT](./LICENSE-MIT)
- **Apache License 2.0** - See [LICENSE-APACHE](./LICENSE-APACHE)
You may use this project under the terms of either license.
---
## ๐ค Author
**Maxim Eryomenko**
- GitHub: [@moeryomenko](https://github.com/moeryomenko)
- Email: moeryomenko@gmail.com
---
**If you find Squad useful, please consider giving it a โญ๏ธ!**
Made with โค๏ธ by [Maxim Eryomenko](https://github.com/moeryomenko)