Skip to content

struct0x/exitplan

Repository files navigation

Exitplan

Go Reference Coverage

A Go library for managing the lifecycle of an application with graceful shutdown capabilities.

Overview

The Exitplan library provides a simple mechanism for managing the lifetime of an application. It helps you handle application startup, running, and shutdown phases with proper resource cleanup.

Key features include:

  • Distinct application lifecycle phases (starting, running, teardown)
  • Context-based lifecycle management
  • Graceful shutdown with customizable timeout
  • Flexible callback registration for cleanup operations
  • Signal handling for clean application termination
  • Synchronous and asynchronous shutdown callbacks
  • Error handling during shutdown

Installation

go get github.com/struct0x/exitplan

Lifecycle Phases

Exitplan splits application lifetime into three phases, each with its own context:

  • Starting: before Run() begins. Use StartingContext() for initialization.
    It is canceled immediately when Run() starts.

  • Running: active between Run() and Exit(). Use Context() for workers and other long-running tasks.
    It is canceled as soon as shutdown begins.

  • Teardown: after Exit() is called. Use TeardownContext() in shutdown callbacks.
    It is canceled when the global teardown timeout elapses.

Callback ordering

Shutdown callbacks registered with OnExit* are executed in LIFO order (last registered, first executed).
This mirrors resource lifecycles: if you start DB then HTTP, shutdown runs HTTP then DB.
Callbacks marked with Async are awaited up to the teardown timeout.

Usage

Basic Example

package main

import (
	"fmt"
	"syscall"
	"time"

	"github.com/struct0x/exitplan"
)

func main() {
	// Create a new Exitplan instance with signal handling for graceful shutdown
	ex := exitplan.New(
		exitplan.WithSignal(syscall.SIGINT, syscall.SIGTERM),
		exitplan.WithTeardownTimeout(5*time.Second),
	)

	// Register cleanup functions
	ex.OnExit(func() {
		fmt.Println("Cleaning up resources...")
		time.Sleep(1 * time.Second)
		fmt.Println("Cleanup complete")
	})

	// Start your application
	fmt.Println("Application starting...")

	// Run the application (blocks until Exit() is called)
	exitCause := ex.Run()
	fmt.Printf("Application exited: %v\n", exitCause)
}

Advanced Example with Context

package main

import (
	"context"
	"fmt"
	"syscall"
	"time"

	"github.com/struct0x/exitplan"
)

func main() {
	// Create a new Exitplan instance with options
	ex := exitplan.New(
		exitplan.WithSignal(syscall.SIGINT, syscall.SIGTERM),
		exitplan.WithTeardownTimeout(10*time.Second),
		exitplan.WithExitError(func(err error) {
			fmt.Printf("Error during shutdown: %v\n", err)
		}),
	)

	// Use the starting context for initialization
	startingCtx := ex.StartingContext()
	_ = startingCtx
	// Initialize resources with the starting context

	// For example, pinging a database connection to ensure it is ready, yet it should not freeze the application
	// err := db.Ping(startingCtx)

	// Register cleanup with context awareness
	ex.OnExitWithContext(func(ctx context.Context) {
		fmt.Println("Starting cleanup...")

		select {
		case <-time.After(2 * time.Second):
			fmt.Println("Cleanup completed successfully")
		case <-ctx.Done():
			fmt.Println("Cleanup was interrupted by timeout")
		}
	})

	// Register cleanup that might return an error
	ex.OnExitWithContextError(func(ctx context.Context) error {
		fmt.Println("Closing database connection...")
		time.Sleep(1 * time.Second)
		return nil
	})

	// Register an async cleanup task
	ex.OnExit(func() {
		fmt.Println("Performing async cleanup...")
		time.Sleep(3 * time.Second)
		fmt.Println("Async cleanup complete")
	}, exitplan.Async)

	// Start your application
	fmt.Println("Application starting...")

	// Get the running context to use in your application
	runningCtx := ex.Context()

	// Start a worker that respects the application lifecycle
	workerDone := make(chan struct{})
	go func() {
		for {
			select {
			case <-runningCtx.Done():
				fmt.Println("Worker shutting down...")
				time.Sleep(100 * time.Millisecond) // Simulate some work
				close(workerDone)
				return
			case <-time.After(1 * time.Second):
				fmt.Println("Worker doing work...")
			}
		}
	}()
	ex.OnExitWithContext(func(ctx context.Context) {
		select {
		case <-workerDone:
			fmt.Println("Worker shutdown complete")
		case <-ctx.Done():
			fmt.Println("Worker shutdown interrupted")
		}
	})

	// Run the application (blocks until Exit() is called)
	exitCause := ex.Run()
	fmt.Printf("Application exited: %v\n", exitCause)
}

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

About

A Go library for managing the lifecycle of an application with graceful shutdown capabilities.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages