Background Task Runner
A self-contained Go package for running background tasks with periodic execution and on-demand signaling capabilities.
Purpose
This package provides a robust background task runner that:
- Executes work functions periodically (default: 60 seconds)
- Supports on-demand execution via signals
- Prevents concurrent executions through work coalescing
- Provides graceful shutdown with configurable timeout
- Tracks execution statistics for observability
- Maintains thread-safety for concurrent access
Core Components
- Runner: Main orchestrator that manages the background task lifecycle
- WorkFunc: User-defined function signature for task execution
- Stats: Thread-safe statistics tracking (cycles, timestamps, errors)
- Options: Configuration pattern for customizing behavior
Usage
Basic Example
package main
import (
"context"
"log/slog"
"time"
"queryorchestration/internal/backgroundtask"
)
func main() {
// Define configuration
config := map[string]any{
"dbConnection": db,
"apiClient": client,
}
// Define work function
workFunc := func(ctx context.Context, logger *slog.Logger, cfg map[string]any) error {
// Perform background work here
logger.Info("executing background task")
return nil
}
// Initialize runner
runner, err := backgroundtask.Initialize(
config,
slog.Default(),
workFunc,
backgroundtask.WithInterval(30*time.Second), // Optional: custom interval
)
if err != nil {
panic(err)
}
// Start runner
if err := runner.Run(); err != nil {
panic(err)
}
// Signal immediate work (optional)
runner.Signal()
// Graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
runner.Shutdown(ctx)
}
Key Features
Work Coalescing
Multiple rapid signals are coalesced into a single execution to prevent resource exhaustion:
runner.Signal() // Triggers work
runner.Signal() // Coalesced if previous work still running
runner.Signal() // Sets pending flag for next execution
Statistics Tracking
Monitor runner performance and health:
stats := runner.Stats()
fmt.Printf("Work cycles: %d\n", stats.WorkCycles())
fmt.Printf("Last error: %v\n", stats.LastErr())
fmt.Printf("Last execution: %v\n", stats.LastStart())
Graceful Shutdown
Ensures clean termination with timeout protection:
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
if err := runner.Shutdown(ctx); err != nil {
log.Printf("shutdown timeout: %v", err)
}
Testing
The package includes comprehensive test helpers for unit testing:
// Use provided test configuration
config := backgroundtask.NewTestConfigMap()
testConfig, _ := backgroundtask.GetTestConfig(config)
// Use test work functions
runner, _ := backgroundtask.Initialize(
config,
slog.Default(),
backgroundtask.TestWorkFunc, // Configurable test function
)
// Wait for work cycles in tests
backgroundtask.WaitForWorkCycles(runner, 3, 5*time.Second)
Design Principles
- No External Dependencies: Uses only standard library and slog
- Thread-Safe: All operations safe for concurrent access
- Testable: Fast intervals and test helpers for unit testing
- Observable: Built-in statistics for monitoring
- Graceful: Clean shutdown with context cancellation
- Defensive: Prevents concurrent executions and resource leaks
Configuration
Options
WithInterval(duration): Set custom execution interval (default: 60s)
WorkFunc Requirements
- Must accept
context.Contextfor cancellation - Must accept
*slog.Loggerfor structured logging - Must accept
map[string]anyfor configuration passing - Should respect context cancellation for graceful shutdown
- Should return meaningful errors for observability
Error Handling
The runner continues operation even when work functions return errors:
- Errors are logged with details
- Last error is stored in stats for monitoring
- Runner remains functional for next execution
- No panic or crash on work function errors
Thread Safety
All public methods are thread-safe:
Signal()can be called from any goroutineStats()provides consistent snapshotsShutdown()is idempotent with sync.Once protection