runner

package
v1.19.2 Latest Latest
Warning

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

Go to latest
Published: Feb 4, 2026 License: MIT Imports: 6 Imported by: 0

README

Runner Package

Go Version

Thread-safe lifecycle management for long-running services and periodic tasks with context cancellation, error tracking, and uptime monitoring.


Table of Contents


Overview

The runner package provides production-ready lifecycle management for Go services and periodic tasks. It handles complex scenarios like graceful shutdown, error collection, concurrent operations, and automatic cleanup with minimal boilerplate code.

Design Philosophy
  1. Thread-Safe: All operations use atomic primitives and mutexes for concurrent safety
  2. Context-Aware: Full support for context cancellation and timeout propagation
  3. Error Tracking: Automatic error collection with retrieval interfaces
  4. Clean Shutdown: Exponential backoff polling for graceful cleanup
  5. Panic Recovery: Built-in panic recovery to prevent process crashes

Key Features

  • Service Lifecycle Management: Start, stop, and restart long-running services with proper cleanup
  • Periodic Execution: Execute functions at regular intervals with automatic lifecycle management
  • Thread-Safe Operations: Atomic operations (atomic.Value), mutex protection (sync.Mutex)
  • Context Cancellation: Proper propagation and handling of context cancellation
  • Error Collection: Track all errors from operations with ErrorsLast() and ErrorsList()
  • Uptime Tracking: Monitor how long a service has been running
  • Panic Recovery: Automatic recovery with stack traces to prevent crashes
  • Idempotent Operations: Safe to call Start/Stop multiple times
  • Clean State Transitions: Automatic cleanup of previous instances on restart

Installation

go get github.com/nabbar/golib/runner

Architecture

Package Structure

The package is organized into specialized subpackages for different execution patterns:

runner/
├── interface.go         # Core Runner interface and function types
├── tools.go            # Utility functions (RecoveryCaller, RunNbr, RunTick)
├── startStop/          # Service lifecycle management (Start/Stop pattern)
│   ├── interface.go    # StartStop interface and constructor
│   └── model.go        # Implementation with state management
└── ticker/             # Periodic execution (ticker pattern)
    ├── interface.go    # Ticker interface and constructor
    └── model.go        # Implementation with time.Ticker
Component Overview
┌──────────────────────────────────────────────────────┐
│                  Runner Interface                     │
│  Start() Stop() Restart() IsRunning() Uptime()      │
└───────────┬────────────────────────────┬─────────────┘
            │                            │
   ┌────────▼──────────┐      ┌─────────▼──────────┐
   │    startStop      │      │      ticker        │
   │                   │      │                    │
   │ Service lifecycle │      │ Periodic execution │
   │ Start/Stop funcs  │      │ time.Ticker based  │
   │ Blocking pattern  │      │ Regular intervals  │
   └───────────────────┘      └────────────────────┘
Component Purpose Pattern Thread-Safe
Runner Interface Common lifecycle operations Interface N/A
startStop Long-running services (HTTP server, listeners) Start blocks, Stop cleans up
ticker Periodic tasks (cron-like, health checks) Executes function every N duration
Utilities Helper functions (recovery, polling) Standalone functions
Execution Patterns

startStop Pattern (Blocking Service)

  • Start function blocks until service terminates
  • Stop function triggers graceful shutdown
  • Use case: HTTP servers, database connections, message consumers

ticker Pattern (Periodic Execution)

  • Function executes at regular intervals
  • Continues until stopped or context cancelled
  • Use case: Health checks, metrics collection, data synchronization

Performance

Memory Efficiency

The runner package maintains minimal memory overhead:

  • Atomic Operations: Lock-free reads for state checks (IsRunning(), Uptime())
  • Shared Context: Single context per runner instance
  • Error Pooling: Efficient error collection using github.com/nabbar/golib/errors/pool
  • Zero Allocations: State checks use atomic operations without heap allocations
Thread Safety

All operations are thread-safe through:

  • Atomic Values: libatm.Value[T] for lock-free reads (start time, cancel function)
  • Mutex Protection: sync.Mutex for Start/Stop/Restart serialization
  • Exponential Backoff: Efficient polling for cleanup completion (1ms → 10ms)
  • Goroutine Safety: Multiple goroutines can safely call operations concurrently
Cleanup Guarantees
Stop Operation Flow:
├─ Cancel context (immediate)
├─ Poll for cleanup (exponential backoff)
│  └─ 1ms → 2ms → 4ms → 8ms → 10ms (max)
└─ Return after max 2 seconds

Stop Guarantees:

  • Context cancellation: Immediate
  • Cleanup detection: Up to 2 seconds with exponential backoff
  • Idempotent: Safe to call multiple times
  • No goroutine leaks: Verified with race detector

Use Cases

This package is designed for scenarios requiring reliable lifecycle management:

HTTP Servers

  • Start server with graceful shutdown support
  • Track uptime and running state
  • Collect startup/shutdown errors
  • Restart on configuration changes

Background Workers

  • Process message queues with lifecycle control
  • Graceful shutdown on termination signals
  • Error tracking for debugging
  • Uptime monitoring for health checks

Periodic Tasks

  • Execute health checks every N seconds
  • Scheduled data synchronization
  • Metrics collection at regular intervals
  • Cache cleanup and maintenance jobs

Database Connections

  • Maintain connection pools with lifecycle management
  • Automatic reconnection with restart
  • Monitor connection uptime
  • Clean shutdown of connections

Scheduled Jobs

  • Cron-like periodic execution
  • Log rotation and archival
  • Report generation
  • Data backup operations

Quick Start

HTTP Server with startStop

Manage an HTTP server lifecycle with graceful shutdown:

package main

import (
    "context"
    "fmt"
    "net/http"
    "time"
    
    "github.com/nabbar/golib/runner/startStop"
)

func main() {
    srv := &http.Server{
        Addr:    ":8080",
        Handler: http.DefaultServeMux,
    }
    
    // Create runner with start and stop functions
    runner := startStop.New(
        func(ctx context.Context) error {
            // Start function blocks until server stops
            fmt.Println("Starting HTTP server on :8080")
            return srv.ListenAndServe()
        },
        func(ctx context.Context) error {
            // Stop function performs graceful shutdown
            fmt.Println("Shutting down HTTP server")
            return srv.Shutdown(ctx)
        },
    )
    
    // Start the server
    if err := runner.Start(context.Background()); err != nil {
        panic(err)
    }
    
    fmt.Printf("Server running (uptime: %v)\n", runner.Uptime())
    
    // Let it run for a while
    time.Sleep(5 * time.Second)
    
    // Stop the server
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    
    if err := runner.Stop(ctx); err != nil {
        fmt.Printf("Stop error: %v\n", err)
    }
    
    // Check for errors during lifecycle
    if err := runner.ErrorsLast(); err != nil {
        fmt.Printf("Server errors: %v\n", err)
    }
}
Periodic Health Check with ticker

Execute a function at regular intervals:

package main

import (
    "context"
    "fmt"
    "time"
    
    "github.com/nabbar/golib/runner/ticker"
)

func main() {
    checkCount := 0
    
    // Create ticker that runs every 2 seconds
    tick := ticker.New(2*time.Second, func(ctx context.Context, t *time.Ticker) error {
        checkCount++
        fmt.Printf("Health check #%d at %v\n", checkCount, time.Now())
        
        // Simulate occasional errors
        if checkCount%5 == 0 {
            return fmt.Errorf("health check warning at count %d", checkCount)
        }
        return nil
    })
    
    // Start the ticker
    if err := tick.Start(context.Background()); err != nil {
        panic(err)
    }
    
    fmt.Printf("Ticker started (running: %v)\n", tick.IsRunning())
    
    // Let it run for 10 seconds
    time.Sleep(10 * time.Second)
    
    // Stop the ticker
    if err := tick.Stop(context.Background()); err != nil {
        fmt.Printf("Stop error: %v\n", err)
    }
    
    // Check collected errors
    errors := tick.ErrorsList()
    fmt.Printf("Total errors: %d\n", len(errors))
    for i, err := range errors {
        fmt.Printf("  Error %d: %v\n", i+1, err)
    }
}
Context Cancellation

Automatic shutdown when context is cancelled:

package main

import (
    "context"
    "fmt"
    "time"
    
    "github.com/nabbar/golib/runner/ticker"
)

func main() {
    // Create context with 5 second timeout
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    tick := ticker.New(1*time.Second, func(ctx context.Context, t *time.Ticker) error {
        fmt.Printf("Tick at %v\n", time.Now())
        return nil
    })
    
    if err := tick.Start(ctx); err != nil {
        panic(err)
    }
    
    // Wait for context to expire
    <-ctx.Done()
    
    // Ticker automatically stops when context is cancelled
    time.Sleep(100 * time.Millisecond)
    fmt.Printf("Ticker running: %v (stopped automatically)\n", tick.IsRunning())
}
Utility Functions

The package provides utility functions for common patterns:

package main

import (
    "context"
    "fmt"
    "time"
    
    "github.com/nabbar/golib/runner"
)

func main() {
    // RunNbr: Retry up to N times with custom check and action
    success := runner.RunNbr(5,
        func() bool {
            // Check if condition is met
            return serverIsReady()
        },
        func() {
            // Action to perform between checks
            time.Sleep(100 * time.Millisecond)
        },
    )
    fmt.Printf("Server ready: %v\n", success)
    
    // RunTick: Poll with timeout and ticker interval
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    ready := runner.RunTick(ctx, 100*time.Millisecond, 5*time.Second,
        func() bool {
            return databaseIsConnected()
        },
        func() {
            fmt.Println("Waiting for database...")
        },
    )
    fmt.Printf("Database ready: %v\n", ready)
}

func serverIsReady() bool {
    // Check server readiness
    return true
}

func databaseIsConnected() bool {
    // Check database connection
    return true
}
Panic Recovery

Built-in panic recovery prevents crashes:

package main

import (
    "context"
    
    "github.com/nabbar/golib/runner/startStop"
)

func main() {
    runner := startStop.New(
        func(ctx context.Context) error {
            // This panic will be recovered automatically
            panic("something went wrong!")
        },
        func(ctx context.Context) error {
            return nil
        },
    )
    
    // Start will not crash the process
    _ = runner.Start(context.Background())
    
    // Recovery message is printed to stderr with stack trace
    // Process continues running
}

Subpackages

startStop Subpackage

Lifecycle management for long-running services with blocking start and graceful stop operations.

Features

  • Start function executes asynchronously (runs in goroutine)
  • Stop function triggers graceful shutdown
  • Automatic cleanup detection with exponential backoff
  • Context cancellation support
  • Error tracking for both start and stop operations
  • Uptime monitoring

Interface

type StartStop interface {
    // Runner interface methods
    Start(ctx context.Context) error
    Stop(ctx context.Context) error
    Restart(ctx context.Context) error
    IsRunning() bool
    Uptime() time.Duration
    
    // Error tracking methods
    ErrorsLast() error
    ErrorsList() []error
}

State Management

State Transitions:
┌─────────┐  Start()  ┌─────────┐  Stop()   ┌─────────┐
│ Stopped │ ───────> │ Running │ ───────> │ Stopped │
└─────────┘          └─────────┘          └─────────┘
     ▲                                          │
     └──────────── Restart() ───────────────────┘

Example: Message Queue Worker

import (
    "context"
    "github.com/nabbar/golib/runner/startStop"
)

func NewWorker(queue MessageQueue) startStop.StartStop {
    return startStop.New(
        func(ctx context.Context) error {
            // Start consuming messages (blocks)
            for {
                select {
                case <-ctx.Done():
                    return nil
                case msg := <-queue.Messages():
                    if err := processMessage(msg); err != nil {
                        return err
                    }
                }
            }
        },
        func(ctx context.Context) error {
            // Stop consuming and cleanup
            return queue.Close()
        },
    )
}

See GoDoc for complete API.


ticker Subpackage

Execute functions at regular intervals with automatic lifecycle management.

Features

  • Executes function every N duration using time.Ticker
  • Automatic goroutine lifecycle management
  • Context cancellation support
  • Error collection from all executions
  • Configurable tick interval (minimum 1ms, default 30s)
  • Graceful shutdown with cleanup detection

Interface

type Ticker interface {
    // Runner interface methods
    Start(ctx context.Context) error
    Stop(ctx context.Context) error
    Restart(ctx context.Context) error
    IsRunning() bool
    Uptime() time.Duration
    
    // Error tracking methods
    ErrorsLast() error
    ErrorsList() []error
}

Execution Flow

Ticker Lifecycle:
Start() ──> goroutine created
             ↓
         time.Ticker created
             ↓
         ┌───────────────┐
         │  Tick Loop    │ ──> Execute function
         │  <-ticker.C   │ ──> Collect errors
         │  <-ctx.Done() │ ──> Check cancellation
         └───────────────┘
             ↓
Stop() ──> Context cancelled
             ↓
         Cleanup: ticker.Stop(), clear uptime

Example: Metrics Collection

import (
    "context"
    "time"
    "github.com/nabbar/golib/runner/ticker"
)

func StartMetricsCollector() ticker.Ticker {
    return ticker.New(30*time.Second, func(ctx context.Context, t *time.Ticker) error {
        // Collect metrics
        metrics := collectSystemMetrics()
        
        // Send to monitoring service
        if err := sendMetrics(metrics); err != nil {
            return err // Error is collected automatically
        }
        
        return nil
    })
}

// Usage
collector := StartMetricsCollector()
collector.Start(context.Background())

// Runs every 30 seconds until stopped
time.Sleep(5 * time.Minute)

collector.Stop(context.Background())

// Check for errors during collection
if err := collector.ErrorsLast(); err != nil {
    log.Printf("Metrics collection errors: %v", err)
}

Example: Cache Cleanup

func StartCacheCleanup(cache *Cache) ticker.Ticker {
    return ticker.New(10*time.Minute, func(ctx context.Context, t *time.Ticker) error {
        // Remove expired entries
        removed := cache.RemoveExpired()
        log.Printf("Removed %d expired cache entries", removed)
        return nil
    })
}

See GoDoc for complete API.


Best Practices

Always Use Context

// ✅ Good: Proper context usage
func startService(ctx context.Context) {
    runner := startStop.New(startFunc, stopFunc)
    if err := runner.Start(ctx); err != nil {
        log.Fatal(err)
    }
}

// ❌ Bad: Using background context everywhere
func startServiceBad() {
    runner := startStop.New(startFunc, stopFunc)
    runner.Start(context.Background()) // Can't be cancelled externally
}

Handle Errors

// ✅ Good: Check and handle errors
func runService(ctx context.Context) error {
    runner := startStop.New(startFunc, stopFunc)
    
    if err := runner.Start(ctx); err != nil {
        return fmt.Errorf("start failed: %w", err)
    }
    
    // ... later ...
    
    if err := runner.Stop(ctx); err != nil {
        return fmt.Errorf("stop failed: %w", err)
    }
    
    // Check for operational errors
    if errs := runner.ErrorsList(); len(errs) > 0 {
        return fmt.Errorf("service errors: %v", errs)
    }
    
    return nil
}

// ❌ Bad: Ignoring errors
func runServiceBad() {
    runner := startStop.New(startFunc, stopFunc)
    runner.Start(context.Background())
    // No error checking!
}

Graceful Shutdown

// ✅ Good: Proper shutdown with timeout
func main() {
    runner := startStop.New(startFunc, stopFunc)
    runner.Start(context.Background())
    
    // Handle shutdown signals
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
    <-sigChan
    
    // Stop with timeout
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    
    if err := runner.Stop(ctx); err != nil {
        log.Printf("Stop error: %v", err)
    }
}

// ❌ Bad: Abrupt shutdown
func mainBad() {
    runner := startStop.New(startFunc, stopFunc)
    runner.Start(context.Background())
    // Process exits without cleanup
}

Appropriate Ticker Intervals

// ✅ Good: Reasonable intervals
tick := ticker.New(30*time.Second, healthCheckFunc)  // Every 30 seconds
tick := ticker.New(5*time.Minute, cleanupFunc)       // Every 5 minutes

// ❌ Bad: Too frequent, wastes CPU
tick := ticker.New(10*time.Millisecond, func(ctx context.Context, t *time.Ticker) error {
    // Heavy operation every 10ms = 100 times/second!
    return doHeavyWork()
})

Check Running State

// ✅ Good: Check before operations
if runner.IsRunning() {
    uptime := runner.Uptime()
    log.Printf("Service uptime: %v", uptime)
} else {
    log.Println("Service is not running")
}

// ✅ Good: Idempotent operations
runner.Stop(ctx) // Safe to call even if not running

Error Collection in Tickers

// ✅ Good: Errors don't stop the ticker
tick := ticker.New(1*time.Second, func(ctx context.Context, t *time.Ticker) error {
    if err := performCheck(); err != nil {
        // Return error - ticker continues, error is collected
        return fmt.Errorf("check failed: %w", err)
    }
    return nil
})

// Later, review all errors
tick.Stop(context.Background())
for i, err := range tick.ErrorsList() {
    log.Printf("Error %d: %v", i+1, err)
}

Testing

Test Suite: 100+ specs using Ginkgo v2 and Gomega (≥80% coverage)

# Run tests
go test ./...

# With coverage
go test -cover ./...

# With race detection (recommended)
CGO_ENABLED=1 go test -race ./...

# Using Ginkgo CLI
ginkgo -race -cover

Coverage Areas

  • Lifecycle operations (Start, Stop, Restart)
  • Concurrent operations and thread safety
  • Error collection and retrieval
  • Context cancellation
  • Edge cases (nil contexts, quick exits, panics)
  • Uptime tracking

Quality Assurance

  • ✅ Zero data races (verified with -race)
  • ✅ Thread-safe concurrent operations
  • ✅ Proper goroutine cleanup
  • ✅ Panic recovery without crashes

See TESTING.md for detailed testing documentation.


Contributing

Contributions are welcome! Please follow these guidelines:

Code Contributions

  • Do not use AI to generate package implementation code
  • AI may assist with tests, documentation, and bug fixing
  • All contributions must pass go test -race
  • Maintain or improve test coverage (≥80%)
  • Follow existing code style and patterns

Documentation

  • Update README.md for new features
  • Add examples for common use cases
  • Keep TESTING.md synchronized with test changes
  • Document all public functions and types

Testing

  • Write tests for all new features
  • Test edge cases and error conditions
  • Verify thread safety with race detector
  • Add comments explaining complex scenarios

Pull Requests

  • Provide clear description of changes
  • Reference related issues
  • Include test results
  • Update documentation

See CONTRIBUTING.md for detailed guidelines.


Future Enhancements

Potential improvements for future versions:

Advanced Scheduling

  • Cron-like scheduling syntax
  • Multiple schedule support
  • Skip overlapping executions
  • Timezone-aware scheduling

Health Monitoring

  • Built-in health check endpoints
  • Automatic restart on failures
  • Exponential backoff for retries
  • Circuit breaker integration

Metrics & Observability

  • Execution duration tracking
  • Success/failure rate metrics
  • Prometheus integration
  • OpenTelemetry tracing

Advanced Features

  • Graceful reload without downtime
  • Priority-based execution
  • Resource-aware scheduling
  • Distributed coordination (etcd/consul)

Suggestions and contributions are welcome via GitHub issues.


AI Transparency Notice

In accordance with Article 50.4 of the EU AI Act, AI assistance has been used for testing, documentation, and bug fixing under human supervision.


License

MIT License - See LICENSE file for details.


Resources

Documentation

Overview

Package runner provides thread-safe lifecycle management for long-running services and periodic tasks. It offers two execution patterns:

  • startStop: Service lifecycle with blocking start and graceful stop
  • ticker: Periodic execution at regular intervals

All implementations are thread-safe with atomic operations, context-aware, and include automatic error collection and panic recovery.

Example usage with HTTP server:

runner := startStop.New(
    func(ctx context.Context) error {
        return httpServer.ListenAndServe()  // Blocks until stopped
    },
    func(ctx context.Context) error {
        return httpServer.Shutdown(ctx)     // Graceful shutdown
    },
)
runner.Start(context.Background())
defer runner.Stop(context.Background())

Example usage with periodic task:

ticker := ticker.New(30*time.Second, func(ctx context.Context, tck *time.Ticker) error {
    return performHealthCheck()  // Executes every 30 seconds
})
ticker.Start(context.Background())
defer ticker.Stop(context.Background())

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RecoveryCaller

func RecoveryCaller(proc string, rec any, data ...any)

RecoveryCaller logs a recovered panic with a stack trace to stderr. This function is safe to call with nil rec (it will do nothing), making it convenient for use in defer statements with recover().

It prints:

  • The process name and panic value
  • Any additional data passed via the data parameter
  • Up to 10 frames of stack trace with file paths and line numbers

The output is written to stderr to avoid interfering with normal program output. This function is used internally by all runner implementations to prevent panics from crashing the entire process.

Parameters:

  • proc: A descriptive name for the process/function where the panic occurred
  • rec: The value returned by recover() (can be nil)
  • data: Optional additional data to include in the output

Example usage in a defer statement:

defer func() {
	if r := recover(); r != nil {
		runner.RecoveryCaller("myservice/mypackage/myfunction", r)
	}
}()

Example with additional data:

defer func() {
	if r := recover(); r != nil {
		runner.RecoveryCaller("myservice/mypackage/myfunction", r, "WorkerID:", workerID)
	}
}()

Output format:

Recovering process 'process-name': panic-value
additional-data
  trace #0 => Line: 123 - File: /path/to/file.go
  trace #1 => Line: 456 - File: /path/to/other.go

func RunNbr

func RunNbr(max uint8, chk FunCheck, run FunRun) bool

RunNbr performs a polling operation up to max attempts, executing chk() to test a condition and run() between checks. This is useful for retry logic with a fixed number of attempts.

The function executes in this order:

  1. Check condition with chk()
  2. If true, return true immediately
  3. Otherwise, execute run() (typically a sleep or state update)
  4. Repeat up to max times
  5. Perform final check and return result

Parameters:

  • max: Maximum number of retry attempts (0 means only final check)
  • chk: Function to check if condition is met
  • run: Function to execute between checks (e.g., time.Sleep)

Returns true if the condition is met within max attempts, false otherwise.

Example:

success := runner.RunNbr(10,
    func() bool { return server.IsReady() },
    func() { time.Sleep(100 * time.Millisecond) },
)

func RunTick

func RunTick(ctx context.Context, tick, max time.Duration, chk FunCheck, run FunRun) bool

RunTick performs a polling operation with timeout and tick interval, executing chk() to test a condition and run() between checks. This is useful for waiting on async operations with both time limits and context cancellation support.

The function uses a time.Ticker to check the condition at regular intervals until either the condition is met, the context is cancelled, or the maximum duration is exceeded.

Parameters:

  • ctx: Context for cancellation (returns false if cancelled)
  • tick: Interval between checks
  • max: Maximum total duration to wait
  • chk: Function to check if condition is met
  • run: Function to execute between checks

Returns:

  • true if the condition is met within max duration
  • false if context is cancelled or max duration exceeded

Example:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

ready := runner.RunTick(ctx,
    500*time.Millisecond,  // Check every 500ms
    10*time.Second,         // Give up after 10s
    func() bool { return db.IsConnected() },
    func() { log.Println("Waiting for database...") },
)

Types

type FunCheck

type FunCheck func() bool

FunCheck is a function type that checks if a condition is met. It returns true if the condition is satisfied, false otherwise. Used by RunNbr and RunTick for polling operations.

type FunRun

type FunRun func()

FunRun is a function type that performs an action between checks. Typically used for sleep operations or state updates in polling loops. Used by RunNbr and RunTick for actions between condition checks.

type FuncAction

type FuncAction func(ctx context.Context) error

FuncAction represents a function that performs an action with context support. It is used by the startStop subpackage for both start and stop operations. The function should respect context cancellation and return any errors encountered.

The start function typically blocks until the service terminates, while the stop function performs cleanup and graceful shutdown operations.

type FuncTicker

type FuncTicker func(ctx context.Context, tck *time.Ticker) error

FuncTicker represents a function executed by the ticker at regular intervals. It receives both the context (which will be cancelled on Stop) and the underlying time.Ticker for advanced control if needed.

The function is called repeatedly until the ticker is stopped or the context is cancelled. Errors are collected and can be retrieved via ErrorsLast() or ErrorsList(). Returning an error does not stop the ticker.

Example:

func(ctx context.Context, tck *time.Ticker) error {
    if err := performTask(); err != nil {
        return fmt.Errorf("task failed: %w", err)
    }
    return nil
}

type Runner

type Runner interface {
	// Start launches the runner with the given context. For startStop runners,
	// the start function executes asynchronously in a goroutine. For ticker
	// runners, execution begins at regular intervals.
	//
	// If the runner is already running, it will be stopped first to ensure
	// clean state transition. The context is used to create a cancellable
	// child context for the runner's lifecycle.
	//
	// Returns an error if the context is nil. Operational errors are collected
	// internally and retrievable via the Errors interface (if available).
	Start(ctx context.Context) error

	// Stop gracefully stops the runner if it is currently running. This is an
	// idempotent operation - calling Stop on an already stopped runner is safe
	// and returns nil immediately.
	//
	// The method cancels the runner's context and waits for cleanup to complete
	// using exponential backoff polling (up to 2 seconds). For startStop runners,
	// the stop function is also called asynchronously.
	//
	// Returns an error if the context is nil. The context is used for timeout
	// control but not for the stop operation itself.
	Stop(ctx context.Context) error

	// Restart atomically stops the runner (if running) and starts it again with
	// a fresh state. This is equivalent to calling Stop() followed by Start(),
	// but ensures atomicity through mutex protection.
	//
	// Previous errors are cleared on restart. The context is used for both
	// the stop and start operations.
	//
	// Returns an error if the context is nil or if the stop/start operations fail.
	Restart(ctx context.Context) error

	// IsRunning returns true if the runner is currently active. This is determined
	// by checking if the uptime is greater than zero.
	//
	// This method uses atomic operations for lock-free reads and is safe to call
	// concurrently from multiple goroutines.
	IsRunning() bool

	// Uptime returns the duration since the runner was started. Returns 0 if the
	// runner is not currently running.
	//
	// This method uses atomic operations for lock-free reads and is safe to call
	// concurrently from multiple goroutines. The uptime is calculated from the
	// atomically stored start time.
	Uptime() time.Duration
}

Runner defines the core interface for lifecycle management of services and tasks. It provides operations to start, stop, restart, and monitor the running state of a managed component.

All methods are thread-safe and can be called concurrently from multiple goroutines. State transitions are properly synchronized using atomic operations and mutexes.

Implementations include:

  • startStop.StartStop: For long-running services with blocking start functions
  • ticker.Ticker: For periodic task execution at regular intervals

type WaitNotify

type WaitNotify interface {
	// StartWaitNotify begins waiting for notifications with the given context.
	StartWaitNotify(ctx context.Context)

	// StopWaitNotify stops waiting for notifications.
	StopWaitNotify()
}

WaitNotify is an optional interface that can be implemented by runners to support waiting for startup completion or receiving notifications. This interface is currently not implemented by the standard runner types but is defined for future extensibility.

Directories

Path Synopsis
Package startStop provides a thread-safe runner for managing service lifecycle with start and stop operations.
Package startStop provides a thread-safe runner for managing service lifecycle with start and stop operations.
Package ticker provides a ticker-based runner implementation that executes a function at regular intervals.
Package ticker provides a ticker-based runner implementation that executes a function at regular intervals.

Jump to

Keyboard shortcuts

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