11# Exitplan
2+
23[ ![ Go Reference] ( https://pkg.go.dev/badge/github.com/struct0x/exitplan.svg )] ( https://pkg.go.dev/github.com/struct0x/exitplan )
34![ Coverage] ( https://img.shields.io/badge/Coverage-83.2%25-brightgreen )
45
56A Go library for managing the lifecycle of an application with graceful shutdown capabilities.
67
78## Overview
89
9- The Exitplan library provides a simple mechanism for managing the lifetime of an application.
10- It helps you handle application startup, running, and shutdown phases with proper resource cleanup.
10+ The Exitplan library provides a simple mechanism for managing the lifetime of an application.
11+ It helps you handle application running, and shutdown phases with proper resource cleanup.
1112
1213Key features include:
1314
14- - Distinct application lifecycle phases (starting, running, teardown)
15+ - Distinct application lifecycle phases (running, teardown)
1516- Context-based lifecycle management
1617- Graceful shutdown with customizable timeout
1718- Flexible callback registration for cleanup operations
@@ -27,20 +28,49 @@ go get github.com/struct0x/exitplan
2728
2829## Lifecycle Phases
2930
30- Exitplan splits application lifetime into three phases, each with its own context:
31-
32- - ** Starting** : before ` Run() ` begins. Use ` StartingContext() ` for initialization.
33- It is canceled immediately when ` Run() ` starts.
31+ Exitplan manages two lifecycle phases:
3432
35- - ** Running** : active between ` Run() ` and ` Exit() ` . Use ` Context() ` for workers and other long-running tasks.
36- It is canceled as soon as shutdown begins.
33+ - ** Running** : active between ` Run() ` and ` Exit() ` . Use
34+ ` Context() ` for workers and other long-running tasks.
35+ It is canceled as soon as shutdown begins (via ` Exit() ` , signal, or startup timeout).
3736
3837- ** Teardown** : after ` Exit() ` is called. Use ` TeardownContext() ` in shutdown callbacks.
3938 It is canceled when the global teardown timeout elapses.
4039
40+ Use
41+ ` Started() ` to receive a signal when the application enters the running phase.
42+ This is useful for readiness probes or coordinating dependent services.
43+
44+ ### Startup Timeout
45+
46+ Use ` WithStartupTimeout() ` to detect stuck initialization:
47+
48+ ``` go
49+ package main
50+
51+ import (
52+ " time"
53+
54+ " github.com/struct0x/exitplan"
55+ )
56+
57+ func main () {
58+ _ = exitplan.New (
59+ exitplan.WithStartupTimeout (10 * time.Second ),
60+ )
61+
62+ // If Run() isn't called within 10 seconds,
63+ // Context() is canceled and teardown begins
64+ }
65+
66+ ```
67+
68+ This is useful when initialization depends on external services that might hang.
69+
4170### Callback ordering
4271
43- Shutdown callbacks registered with ` OnExit* ` are executed in ** LIFO order** (last registered, first executed).
72+ Shutdown callbacks registered with ` OnExit* ` are executed in ** LIFO order
73+ ** (last registered, first executed).
4474This mirrors resource lifecycles: if you start DB then HTTP, shutdown runs HTTP then DB.
4575Callbacks marked with ` Async ` are awaited up to the teardown timeout.
4676
@@ -107,13 +137,18 @@ func main() {
107137 }),
108138 )
109139
110- // Use the starting context for initialization
111- startingCtx := ex.StartingContext ()
112- _ = startingCtx
113- // Initialize resources with the starting context
140+ // Signal readiness when Run() starts
141+ go func () {
142+ <- ex.Started ()
143+ fmt.Println (" Application is now running and ready" )
144+ // e.g., signal readiness probe, notify dependent services
145+ }()
114146
115- // For example, pinging a database connection to ensure it is ready, yet it should not freeze the application
116- // err := db.Ping(startingCtx)
147+ // Initialize resources before Run()
148+ // Use context.WithTimeout() if you need bounded initialization
149+ // ctx, cancel := context.WithTimeout(ex.Context(), 5*time.Second)
150+ // defer cancel()
151+ // err := db.Ping(ctx)
117152
118153 // Register cleanup with context awareness
119154 ex.OnExitWithContext (func (ctx context.Context ) {
@@ -145,16 +180,16 @@ func main() {
145180 fmt.Println (" Application starting..." )
146181
147182 // Get the running context to use in your application
148- runningCtx := ex.Context ()
183+ ctx := ex.Context ()
149184
150185 // Start a worker that respects the application lifecycle
151186 workerDone := make (chan struct {})
152187 go func () {
153188 for {
154189 select {
155- case <- runningCtx .Done ():
190+ case <- ctx .Done ():
156191 fmt.Println (" Worker shutting down..." )
157- time.Sleep (100 * time.Millisecond ) // Simulate some work
192+ time.Sleep (100 * time.Millisecond ) // Simulate some teardown work
158193 close (workerDone)
159194 return
160195 case <- time.After (1 * time.Second ):
0 commit comments