Documentation
¶
Overview ¶
Package startStop provides a robust, thread-safe mechanism for managing the lifecycle of services or long-running tasks that require explicit start and stop operations.
It is designed to handle common patterns in service management:
- Asynchronous execution of start functions.
- Graceful shutdown via stop functions.
- Automatic uptime tracking.
- Centralized error collection and reporting.
- Thread-safe state management (Running vs. Stopped).
- Context-aware operations with cancellation support.
Architecture & Data Flow ¶
The following diagram illustrates the internal architecture, state transitions, and synchronization points during the service lifecycle.
[ External Caller ]
|
| (1) Start(ctx)
v
+-------------------+
| Start() Logic | <----------------+
+-------------------+ |
| | (Restart calls Stop then Start)
| (2) Mutex Lock |
| (3) Stop If Running --------+
| (4) Reset Error Pool
| (5) Create 'done' Chan
| (6) Create New Context
| (7) Launch Goroutine (Async)
| (8) Mutex Unlock
v
+-----------------------------+
| getFctStart() Goroutine |
+-----------------------------+
| (A) Set Running = true |
| (B) Set Start Time |
| (C) Execute User Start Func | ----> [ Blocking Work ]
| (D) Wait for Completion/Ctx | <---- [ Context Cancelled ]
| (E) Capture Error |
| (F) Set Running = false |
| (G) Reset Start Time |
| (H) Close 'done' Chan | ----> (Signals Stop() to return)
+-----------------------------+
|
| (1) Stop(ctx)
v
+-------------------+
| Stop() Logic |
+-------------------+
|
| (2) Fast Path Check (IsRunning?)
| (3) Mutex Lock
| (4) Signal Ctx Cancel
| (5) Launch getFctStop() Goroutine (Async)
| (6) Mutex Unlock
| (7) Wait for 'done' Chan (Resynchronization)
| (with Timeout/Safety Wait)
v
[ External Caller Resumes ]
State Transitions ¶
- STOPPED --(Start)--> STARTING --(Async)--> RUNNING
- RUNNING --(Stop)--> STOPPING --(Async)--> STOPPED
- RUNNING --(Restart)--> STOPPING --(Async)--> STOPPED --(Start)--> RUNNING
Structure ¶
The core of the package is the StartStop interface, which combines two main aspects:
- Lifecycle Management: Inherited from libsrv.Runner, providing Start(), Stop(), Restart(), IsRunning(), and Uptime() methods.
- Error Management: Inherited from liberr.Errors, providing access to the history of errors encountered during the service's lifetime.
Working Principle ¶
When Start() is called:
- It checks if the service is already running. If so, it attempts to stop it first.
- It acquires a mutex to prevent concurrent Start/Stop operations.
- It clears any previous errors from the internal error pool.
- It creates a new "done" channel, which will be closed when the service's goroutine exits.
- It creates a new cancellable context for the service's lifetime.
- It launches the user-provided 'start' function in a new goroutine.
- It releases the mutex.
When the 'start' function returns (either due to completion or error):
- A deferred function captures any returned error and adds it to the internal error pool.
- The service state is updated to "stopped" (atomic flag and start time reset).
- The internal context's cancel function is called.
- The internal wait channel ('done') is closed to signal termination to any waiting Stop() call.
When Stop() is called:
- It performs a fast check: if the service is not running, it returns immediately.
- It acquires a mutex to serialize stop operations.
- It calls the internal context's cancel function to signal the 'start' function to terminate.
- It executes the user-provided 'stop' function in a background goroutine with a timeout.
- It releases the mutex.
- It waits for the 'start' goroutine to signal its full termination via the 'w' channel, subject to secondary timeouts for safety.
Quick Start ¶
To use this package, you need two functions: one that blocks while the service is running (the start function), and one that initiates a shutdown (the stop function).
package main
import (
"context"
"fmt"
"net/http"
"time"
"github.com/nabbar/golib/runner/startStop"
)
func main() {
srv := &http.Server{Addr: ":8080"}
runner := startStop.New(
func(ctx context.Context) error {
// The start function usually blocks (e.g., ListenAndServe)
fmt.Println("Service starting...")
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
return err
}
fmt.Println("Service stopped.")
return nil
},
func(ctx context.Context) error {
// The stop function initiates shutdown
fmt.Println("Service shutting down...")
return srv.Shutdown(ctx)
},
)
// Start the service in a goroutine
go func() {
if err := runner.Start(context.Background()); err != nil {
fmt.Printf("Service Start error: %v\n", err)
}
}()
time.Sleep(2 * time.Second)
fmt.Printf("Is running: %v, Uptime: %v\n", runner.IsRunning(), runner.Uptime())
// Stop the service
if err := runner.Stop(context.Background()); err != nil {
fmt.Printf("Service Stop error: %v\n", err)
}
// Wait a bit for shutdown to complete
time.Sleep(1 * time.Second)
fmt.Printf("After stop: Is running: %v, Uptime: %v\n", runner.IsRunning(), runner.Uptime())
if err := runner.ErrorsLast(); err != nil {
fmt.Printf("Last error: %v\n", err)
}
}
Use Cases ¶
- HTTP Servers: Managing the ListenAndServe / Shutdown cycle for web applications.
- Workers: Background goroutines processing queues that need clean termination upon application shutdown.
- Database Connections: Managing the lifecycle of persistent database connections or connection pools with health checks.
- System Daemons: Any long-running component requiring a standard "Service" interface in Go, especially when integrating with system init systems or orchestration tools.
Package startStop provides a robust, thread-safe runner for managing the lifecycle of long-running services or tasks that require explicit start and stop operations. It offers a standardized way to control service execution, handle graceful shutdowns, track operational status, and collect errors in a concurrent-safe manner.
The core functionality revolves around executing user-defined start and stop functions asynchronously. It meticulously manages context cancellation to signal termination to the running service, tracks execution state (running/stopped), monitors uptime, and aggregates any errors encountered during the service's lifetime.
All operations exposed by this package are designed to be thread-safe, meaning they can be safely called concurrently from multiple goroutines without introducing race conditions or requiring external synchronization mechanisms.
Example usage demonstrates how to instantiate and use the runner for a typical service:
runner := startStop.New(
func(ctx context.Context) error {
// This function represents the main logic of your service.
// It should typically block until the service is meant to be terminated.
// The provided context (ctx) will be cancelled when Stop() is called,
// allowing for graceful shutdown procedures within this function.
return server.ListenAndServe() // Example: starting an HTTP server
},
func(ctx context.Context) error {
// This function is responsible for gracefully stopping your service.
// It is invoked when runner.Stop() or runner.Restart() is called.
// The context provided here can be used for shutdown timeouts.
return server.Shutdown(ctx) // Example: shutting down an HTTP server
},
)
// To start the service, typically in a separate goroutine for non-blocking execution:
go func() {
if err := runner.Start(context.Background()); err != nil {
log.Printf("Service failed to start or encountered an error: %v", err)
}
}()
// Later, to stop the service:
// err := runner.Stop(context.Background())
// if err != nil {
// log.Printf("Service failed to stop gracefully: %v", err)
// }
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInvalid = errors.New("invalid instance")
ErrInvalid is returned when a operation is attempted on an uninitialized or nil runner instance. It ensures that the package fails gracefully when API contract is violated.
Functions ¶
This section is empty.
Types ¶
type StartStop ¶
type StartStop interface {
// Runner embeds the base server interface, providing fundamental lifecycle operations.
// These methods allow for starting, stopping, restarting, and querying the operational
// status and uptime of the managed service.
libsrv.Runner
// Errors embeds error tracking operations, allowing the caller to inspect
// any errors that occurred during the service's execution or lifecycle transitions.
liberr.Errors
}
StartStop defines the unified interface for managing service lifecycle operations and error tracking. It provides a comprehensive set of methods to control, monitor, and inspect the state of a long-running service.
This interface is designed for maximum flexibility and reusability, combining functionalities from two distinct interfaces:
- libsrv.Runner: for core service lifecycle management (start, stop, restart, status).
- liberr.Errors: for robust and centralized error collection and retrieval.
All methods exposed by the StartStop interface are guaranteed to be thread-safe, allowing for concurrent interactions from various parts of an application.
func New ¶
New creates and initializes a new StartStop runner instance. This is the primary constructor function for the package, allowing users to define the core logic of their service.
Parameters:
start: A `runner.FuncAction` function (i.e., `func(ctx context.Context) error`) that encapsulates the main execution logic of the service. This function is expected to be blocking and will be run in its own dedicated goroutine when `Start()` is called. The `context.Context` provided to this function will be cancelled when `Stop()` is invoked, serving as a signal for the service to initiate its graceful shutdown procedures. Any error returned by this function will be captured and stored by the runner.
stop: A `runner.FuncAction` function (i.e., `func(ctx context.Context) error`) that defines the graceful shutdown procedure for the service. This function is called when `Stop()` or `Restart()` methods are invoked on the runner. It receives a `context.Context` which can be used to enforce shutdown timeouts or cancellation. Any error returned by this function will also be captured.
Behavior of the returned StartStop instance:
- **Single Instance Execution**: The runner ensures that only one instance of the `start` function is actively running at any given time. Subsequent calls to `Start()` while the service is already running will trigger an internal `Stop()` before restarting.
- **Thread Safety**: All internal state management, including the running status, start time, context cancellation, and error collection, is handled using `sync.Mutex` and `sync/atomic` primitives to guarantee thread safety across concurrent calls to the runner's methods.
- **Uptime Tracking**: The runner automatically records the start time of the service, enabling accurate `Uptime()` reporting without additional user logic.
- **Error Aggregation**: Any errors returned by the `start` or `stop` functions are automatically added to an internal error pool, which can be queried via `ErrorsLast()` and `ErrorsList()`. This provides a centralized mechanism for monitoring service health and diagnosing issues.
Returns:
- A concrete implementation of the `StartStop` interface (specifically, an instance of the internal `run` struct), configured with the provided start and stop functions.
Example of creating a new runner:
// Define the service's start and stop logic
myServiceStart := func(ctx context.Context) error {
log.Println("My service is starting...")
// Simulate work that blocks until context is cancelled
<-ctx.Done()
log.Println("My service context cancelled, preparing to exit.")
return ctx.Err() // Return context cancellation error or nil
}
myServiceStop := func(ctx context.Context) error {
log.Println("My service is stopping gracefully...")
// Perform cleanup, close connections, etc.
time.Sleep(500 * time.Millisecond) // Simulate cleanup time
log.Println("My service stopped.")
return nil
}
// Create the runner instance
runner := startStop.New(myServiceStart, myServiceStop)
// Now 'runner' can be used to control the lifecycle of 'myService'.