startStop

package
v1.22.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 17, 2026 License: MIT Imports: 10 Imported by: 0

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:

  1. Lifecycle Management: Inherited from libsrv.Runner, providing Start(), Stop(), Restart(), IsRunning(), and Uptime() methods.
  2. 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:

  1. It checks if the service is already running. If so, it attempts to stop it first.
  2. It acquires a mutex to prevent concurrent Start/Stop operations.
  3. It clears any previous errors from the internal error pool.
  4. It creates a new "done" channel, which will be closed when the service's goroutine exits.
  5. It creates a new cancellable context for the service's lifetime.
  6. It launches the user-provided 'start' function in a new goroutine.
  7. It releases the mutex.

When the 'start' function returns (either due to completion or error):

  1. A deferred function captures any returned error and adds it to the internal error pool.
  2. The service state is updated to "stopped" (atomic flag and start time reset).
  3. The internal context's cancel function is called.
  4. The internal wait channel ('done') is closed to signal termination to any waiting Stop() call.

When Stop() is called:

  1. It performs a fast check: if the service is not running, it returns immediately.
  2. It acquires a mutex to serialize stop operations.
  3. It calls the internal context's cancel function to signal the 'start' function to terminate.
  4. It executes the user-provided 'stop' function in a background goroutine with a timeout.
  5. It releases the mutex.
  6. 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

View Source
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

func New(start, stop func(ctx context.Context) error) StartStop

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'.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL