Skip to content

mcwalrus/exit-manager

Repository files navigation

Exit Manager

Go Version Go Report Card codecov GoDoc License: MIT

Exit Manager is a module for graceful shutdown coordination to ensure critical operations complete and cleanup runs before your application exits. See full documentation.

Features

  • Signal handling: Listens for SIGINT (Ctrl+C) and SIGTERM
  • Notifications: Notify() channel closes when shutdown starts
  • Shutdown locks: Prevents exit until critical operations finish
  • Cleanup functions: Runs cleanup in reverse registration order
  • Context cancellation: Cancels contexts when shutdown begins

Installation

go get github.com/mcwalrus/exit-manager

Quick Start

1. Stop Goroutines on Shutdown

Listen for shutdown in your goroutines:

package main

import (
    "log"
    "time"
    exitmanager "github.com/mcwalrus/exit-manager"
)

func main() {
    em := exitmanager.Global()
    go worker(em)
    
    <-em.Notify()
    log.Println("Shutting down...")
    select {}
}

func worker(em *exitmanager.ExitManager) {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()
    for {
        select {
        case <-em.Notify():
            log.Println("Worker stopping")
            return
        case <-ticker.C:
            log.Println("Working...")
        }
    }
}

2. Wait for Critical Operations

Use locks to ensure operations complete before shutdown:

package main

import (
    "log"
    "time"
    exitmanager "github.com/mcwalrus/exit-manager"
)

func main() {
    em := exitmanager.Global()
    go processData(em)
    
    log.Println("Running... Press Ctrl+C")
    <-em.Notify()

    log.Println("Waiting for operations to complete...")
    select {}
}

// performs critical operation
func processData(em *exitmanager.ExitManager) {
    // exit-manager will wait for process to complete
    _ = em.WithShutdownLock(func() error {
        time.Sleep(10 * time.Second)
        log.Println("Data processed")
    })
}

3. Clean Up Resources

Register cleanup functions that run automatically:

package main

import (
    "log"
    exitmanager "github.com/mcwalrus/exit-manager"
)

func main() {
    em := exitmanager.Global()
    
    em.RegisterCleanup(func() {
        log.Println("Closing database...")
    })
    em.RegisterCleanup(func() {
        log.Println("Closing cache...")
    })
    
    log.Println("Running... Press Ctrl+C")
    <-em.Notify()
    select {}
}

4. Context Cancellations

Use contexts that cancel on shutdown:

package main

import (
    "context"
    "log"
    "time"
    exitmanager "github.com/mcwalrus/exit-manager"
)

func main() {
    em := exitmanager.Global()
    ctx, cancel := em.NotifyContext(context.Background())
    defer cancel()
    
    go longTask(ctx)
    
    <-em.Notify()
    select {}
}

func longTask(ctx context.Context) {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()
    for {
        select {
        case <-ctx.Done():
            log.Println("Task cancelled")
            return
        case <-ticker.C:
            log.Println("Processing...")
        }
    }
}

5. Timeout Configuration

Set a timeout to prevent shutdown from hanging:

package main

import (
    "log"
    "time"
    exitmanager "github.com/mcwalrus/exit-manager"
)

func main() {
    em := exitmanager.Global()
    em.SetTimeout(exitmanager.TimeoutModeGraceful, 10*time.Second)
    
    em.RegisterCleanup(func() {
        log.Println("Cleaning up...")
        time.Sleep(120 * time.Second) // timeout occurs first ...
    })
    
    log.Println("Running... Press Ctrl+C")
    <-em.Notify()
    select {}
}

6. Multiple Signals Handling

Configure how additional shutdown signals are handled:

package main

import (
    "log"
    "time"
    exitmanager "github.com/mcwalrus/exit-manager"
)

func main() {
    em := exitmanager.Global()
    em.SetMultipleSignals(exitmanager.MultipleSignalsModeForcefulExit)

    em.RegisterCleanup(func() {
        log.Println("Cleaning up...")
        time.Sleep(30 * time.Second)
    })
    
    log.Println("Running... Press Ctrl+C twice for immediate exit")
    <-em.Notify()
    select {}
}

Try the CLI Demo

See graceful shutdown in action:

cd cmd/exit-manager && go run .

Testing

go test -race ./...

Contributing

Report issues and feature requests at the GitHub repository.

License

This module is available under the MIT License.

About

Go module to manage SIGINT and SIGTERM requests gracefully for long-running services

Resources

License

Stars

Watchers

Forks

Packages

No packages published