Phase helps Go applications perform graceful, ordered shutdown — giving you explicit control over how subsystems wind down.
Use Phase to ensure resources are released in the right order: servers stop accepting connections before pipelines drain, and pipelines complete before writing to the database ends.
Imagine your app has this data flow:
[ Web API Request ] -> [ Transform Data ] -> [ Persist to DB ]
During shutdown, you want:
- The web server to stop accepting new requests
- Outstanding work in the transformation queue to finish
- Final transformed data to be persisted to the database
- The program to exit cleanly
But standard context.Context
cancels everything immediately downstream, making it hard to enforce this order. You’d need to manually coordinate dependent goroutines — a recipe for bugs and complexity.
Phase solves this by chaining context lifecycles with parent-child awareness. Shutdown proceeds in reverse order of creation, giving you fine-grained control with a simple API.
Go’s context.Context
is often used to manage cancellation. However, it wasn’t designed for ordered shutdown of interdependent goroutines.
Phase extends this concept by introducing a Phaser
— a context with awareness of its children. It gives you a simple interface to create, cancel, wait, and close contexts in a reliable, deterministic order.
- ✅ Works anywhere a
context.Context
is accepted - 📚 Automatically tracks child phases
- ⛓ Graceful teardown in reverse order of creation
- 🧹 Guarantees that cleanup completes before parent exits
Install:
go get github.com/aelse/phase
Create a root Phaser from any context:
phaser := phase.FromContext(context.Background())
Spawn child phases using Next():
child := phaser.Next()
Cancel and wait for shutdown in reverse order:
phase.Cancel(child)
phase.Wait(child)
phase.Close(child)
package main
import (
"context"
"fmt"
"time"
"github.com/aelse/phase"
)
func main() {
// Create root phaser
ph := phase.Next(context.Background())
defer phase.Close(ph)
// Each subsystem is a child of the previous
web := phase.Next(ph)
go component(web, "Web Server")
pipeline := phase.Next(web)
go component(pipeline, "Data Pipeline")
db := phase.Next(pipeline)
go component(db, "Database")
fmt.Println("Shutting down in 5 seconds...")
time.Sleep(5 * time.Second)
// Start shutdown from root
phase.Cancel(ph)
// Wait for all phases to complete
phase.Wait(ph)
fmt.Println("All systems shut down.")
}
func component(p phase.Phaser, name string) {
defer phase.Close(p)
fmt.Printf("[%s] started\n", name)
<-p.Done()
fmt.Printf("[%s] shutting down (wait for children)\n", name)
phase.Wait(p)
fmt.Printf("[%s] shutting down (cleanup)\n", name)
time.Sleep(time.Second)
fmt.Printf("[%s] shutting down\n", name)
}
🔎 See the godoc examples for more complete use cases.
Projects with similar goals:
- ash2k/stager
- skovtunenko/graterm
- icholy/killable
- tomb
This project is licensed under the MIT License. See LICENSE for details.