159 lines
4.4 KiB
Markdown
159 lines
4.4 KiB
Markdown
|
|
# 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
|
||
|
|
|
||
|
|
```go
|
||
|
|
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:
|
||
|
|
```go
|
||
|
|
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:
|
||
|
|
```go
|
||
|
|
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:
|
||
|
|
```go
|
||
|
|
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:
|
||
|
|
|
||
|
|
```go
|
||
|
|
// 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
|
||
|
|
|
||
|
|
1. **No External Dependencies**: Uses only standard library and slog
|
||
|
|
2. **Thread-Safe**: All operations safe for concurrent access
|
||
|
|
3. **Testable**: Fast intervals and test helpers for unit testing
|
||
|
|
4. **Observable**: Built-in statistics for monitoring
|
||
|
|
5. **Graceful**: Clean shutdown with context cancellation
|
||
|
|
6. **Defensive**: Prevents concurrent executions and resource leaks
|
||
|
|
|
||
|
|
## Configuration
|
||
|
|
|
||
|
|
### Options
|
||
|
|
- `WithInterval(duration)`: Set custom execution interval (default: 60s)
|
||
|
|
|
||
|
|
### WorkFunc Requirements
|
||
|
|
- Must accept `context.Context` for cancellation
|
||
|
|
- Must accept `*slog.Logger` for structured logging
|
||
|
|
- Must accept `map[string]any` for 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 goroutine
|
||
|
|
- `Stats()` provides consistent snapshots
|
||
|
|
- `Shutdown()` is idempotent with sync.Once protection
|