Skip to content

aelse/phase

Repository files navigation

Tests Lint codecov License GoDoc

Phase

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.


🚦 Why Phase?

Imagine your app has this data flow:

[ Web API Request ] -> [ Transform Data ] -> [ Persist to DB ]

During shutdown, you want:

  1. The web server to stop accepting new requests
  2. Outstanding work in the transformation queue to finish
  3. Final transformed data to be persisted to the database
  4. 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.


🧠 Background

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.


✨ Features

  • ✅ 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

🚀 Getting Started

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)

🔧 Example

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.

🔁 Alternatives

Projects with similar goals:

  • ash2k/stager
  • skovtunenko/graterm
  • icholy/killable
  • tomb

🪪 License

This project is licensed under the MIT License. See LICENSE for details.

About

Phase provides features to enable systems to perform graceful ordered shutdown.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages