GitHub - pardnchiu/go-scheduler: (Golang Package) Scheduler with standard cron and task dependencies (original) (raw)
Note
This README was translated by ChatGPT 4o
Go Scheduler
Lightweight Golang scheduler supporting standard cron expressions, custom descriptors, custom intervals, and task dependencies. Easy scheduling with Go
Originally designed for the scheduling functionality used in threat score decay calculations for pardnchiu/go-ip-sentry
- Core Features
- Flowchart
- Dependencies
- Usage
- Configuration
- Supported Formats
- Available Functions
- Task Dependencies
- Timeout Mechanism
- Upcoming Features
- License
- Star
- Author
Core Features
Flexible Syntax
Supports standard cron expressions, custom descriptors (@hourly, @daily, @weekly, etc.), and custom interval (@every) syntax. Zero learning curve - if you know cron expressions, you can use it.
Task Dependencies
Supports prerequisite dependency tasks, multiple dependencies, dependency timeout control, and failure handling mechanisms.
Efficient Architecture
Uses Golang standard library heap, focusing on core functionality. Min-heap based task scheduling with concurrent task execution and management, featuring panic recovery and dynamic task add/remove capabilities, ensuring optimal performance in high-volume task scenarios.
Flowchart
Main Process
flowchart TD
A[Initialize] --> B{Is Running?}
B -->|No| B0[Start Execution]
B0 --> C[Calculate Initial Tasks]
C --> D[Initialize Tasks]
D --> E[Start Main Loop]
E --> H{Check Heap Status}
E -->|No Tasks
Wait for Events| Q
E -->|Has Tasks
Set Next Task Timer| Q
B -->|Yes
Wait for Trigger| Q[Listen Events]
Q --> R{Event Type} R -->|Timer Expired| R1[Execute Due Tasks] R -->|Add Task| R2[Add to Heap] R -->|Remove Task| R3[Remove from Heap] R -->|Stop Signal| R4[Cleanup and Exit]
R1 --> S[Pop Task from Heap] S --> R5[Calculate Next Execution Time] R5 --> E S --> T{Check if Enabled} T -->|Disabled| T0[Skip Task] T0 --> E T -->|Enabled| T1[Execute Task Function]
R2 --> V[Parse Schedule] V --> W[Create Task Object] W --> X[Add to Heap]
R3 --> Y[Find Task by ID] Y --> Z[Mark as Disabled] Z --> AA[Remove from Heap]
X --> E AA --> E
R4 --> BB[Wait for Running Tasks to Complete] BB --> CC[Close Channels] CC --> DD[Scheduler Stopped]
Loading
Dependency Process
flowchart TD
A[Task Added to Execution Queue] --> B{Check Dependencies}
B -->|No Dependencies| B0[Skip Dependency Process]
B0 --> Z[End]
B -->|Has Dependencies| B1{Dependencies Complete?}
B1 -->|No| B10[Wait for Dependencies]
B10 --> C{Dependency Wait Timeout?}
C -->|No| C0[Continue Waiting]
C0 --> D{Dependency Resolved?}
D -->|Failed
Mark Failed| V
D -->|Completed| B11
D -->|Still Waiting| B10
C -->|Yes
Mark Failed| V
B1 -->|Yes| B11[Execute]
B11 -->|Mark Running| E{Task Timeout Exists?}
E -->|No| E0[Execute Action]
E0 --> R{Execution Result}
R -->|Success
Mark Complete| V[Update Task Result]
R -->|Error
Mark Failed| V
R -->|Panic
Recover and Mark Failed| V
E -->|Yes| E1{Task Timeout?}
E1 -->|Timeout
Mark Failed
Trigger Timeout Action| V
E1 -->|Not Timeout| E0
B1 -->|Failed
Mark Failed| V
V --> X[Record Execution Result]
X --> Y[Notify Dependent Tasks]
Y --> Z[End]Loading
Dependencies
github.com/pardnchiu/go-logger(< v0.3.1)
For performance and stability, non-standard library packages are deprecated fromv0.3.1, now usinglog/slog
Usage
Installation
Note
Latest commit may change, recommended to use tagged versions
Commits containing only documentation updates or non-functional changes will be rebased later
go get github.com/pardnchiu/go-scheduler@[VERSION]
git clone --depth 1 --branch [VERSION] https://github.com/pardnchiu/go-scheduler.git
Initialization
Basic Usage
package main
import ( "fmt" "log" "time"
cron "github.com/pardnchiu/go-scheduler" )
func main() { // Initialize (optional configuration) scheduler, err := cron.New(cron.Config{ Location: time.Local, }) if err != nil { log.Fatal(err) }
// Start scheduler scheduler.Start()
// Add tasks id1, _ := scheduler.Add("@daily", func() { fmt.Println("Daily execution") }, "Backup task")
id2, _ := scheduler.Add("@every 5m", func() { fmt.Println("Execute every 5 minutes") })
// View task list tasks := scheduler.List() fmt.Printf("Currently have %d tasks\n", len(tasks))
// Remove specific task scheduler.Remove(id1)
// Remove all tasks scheduler.RemoveAll()
// Graceful shutdown ctx := scheduler.Stop() <-ctx.Done() }
Task Dependencies
package main
import ( "fmt" "log" "time"
cron "github.com/pardnchiu/go-scheduler" )
func main() { scheduler, err := cron.New(cron.Config{}) if err != nil { log.Fatal(err) }
scheduler.Start() defer func() { ctx := scheduler.Stop() <-ctx.Done() }()
// Task A: Data preparation taskA, _ := scheduler.Add("0 1 * * *", func() error { fmt.Println("Preparing data...") time.Sleep(2 * time.Second) return nil }, "Data preparation")
// Task B: Data processing
taskB, _ := scheduler.Add("0 2 * * *", func() error {
fmt.Println("Processing data...")
time.Sleep(3 * time.Second)
return nil
}, "Data processing")
// Task C: Report generation (depends on A and B) taskC, _ := scheduler.Add("0 3 * * *", func() error { fmt.Println("Generating report...") time.Sleep(1 * time.Second) return nil }, "Report generation", []Wait{{ID: taskA}, {ID: taskB}})
// Task D: Email sending (depends on C) _, _ = scheduler.Add("0 4 * * *", func() error { fmt.Println("Sending email...") return nil }, "Email notification", []Wait{{ID: taskC}})
time.Sleep(10 * time.Second) }
Configuration
type Config struct { Location *time.Location // Timezone setting (default: time.Local) }
Supported Formats
Standard
5-field format:
minute hour day month weekday
Supports range syntax1-5and1,3,5
// Every minute scheduler.Add("* * * * *", task)
// Daily at midnight scheduler.Add("0 0 * * *", task)
// Every 15 minutes scheduler.Add("*/15 * * * *", task)
// First day of month at 6 AM scheduler.Add("0 6 1 * *", task)
// Monday to Wednesday, and Friday scheduler.Add("0 0 * * 1-3,5", task)
Custom
// January 1st at midnight scheduler.Add("@yearly", task)
// First day of month at midnight scheduler.Add("@monthly", task)
// Every Sunday at midnight scheduler.Add("@weekly", task)
// Daily at midnight scheduler.Add("@daily", task)
// Every hour on the hour scheduler.Add("@hourly", task)
// Every 30 seconds (minimum interval: 30 seconds) scheduler.Add("@every 30s", task)
// Every 5 minutes scheduler.Add("@every 5m", task)
// Every 2 hours scheduler.Add("@every 2h", task)
// Every 12 hours scheduler.Add("@every 12h", task)
Available Functions
Scheduler Management
New()- Create new scheduler instance
scheduler, err := cron.New(config)- Sets up task heap and communication channels
Start()- Start scheduler instance- Starts the scheduling loop
Stop()- Stop scheduler
ctx := scheduler.Stop()
<-ctx.Done() // Wait for all tasks to complete- Sends stop signal to main loop
- Returns context that completes when all running tasks finish
- Ensures graceful shutdown without interrupting tasks
Task Management
Add()- Add scheduled task
// Basic usage (no return value)
taskID, err := scheduler.Add("0 /2 * * ", func() {
// Task logic
})
// Task with error return (supports dependencies)
taskID, err := scheduler.Add("@daily", func() error {
// Task logic
return nil
}, "Backup task")
// Task with timeout control
taskID, err := scheduler.Add("@hourly", func() error {
// Long-running task
time.Sleep(10 * time.Second)
return nil
}, "Data processing", 5time.Second)
// Task with timeout callback
taskID, err := scheduler.Add("@daily", func() error {
// Potentially timeout-prone task
return heavyProcessing()
}, "Critical backup", 30time.Second, func() {
log.Println("Backup task timed out, please check system status")
})
// Task with dependencies
taskID, err := scheduler.Add("@daily", func() error {
// Task that depends on other tasks
return processData()
}, "Data processing", []Wait{{ID: taskA}, {ID: taskB}})
// Task with dependencies and timeout
taskID, err := scheduler.Add("@daily", func() error {
return generateReport()
}, "Report generation", []Wait{
{ID: taskA, Delay: 30 * time.Second},
{ID: taskB, Delay: 45 * time.Second},
})- Parses schedule syntax
- Generates unique task ID for management
- Supports variadic parameter configuration
*string: Task description
*time.Duration: Task execution timeout
*func(): Callback function triggered on timeout
*[]Wait: Dependency task configuration (recommended format)
*[]int64: Dependency task ID list (will be removed after v2.0) - Supports two action function types
*func(): No error return, doesn't support dependencies
*func() error: Has error return, supports dependencies
Remove()- Cancel task schedule- Removes task from scheduling queue
- Safe to call regardless of scheduler state
RemoveAll()- Remove all tasks- Immediately removes all scheduled tasks
- Doesn't affect currently running tasks
List()- Get task list
tasks := scheduler.List()
Task Dependencies
Basic Usage
- No dependencies: Execute directly
- Has dependencies: Execute through worker pool and dependency manager
- Single dependency: Task B executes after Task A completes
- Multiple dependencies: Task C waits for both Task A and B to complete
- Dependency task timeout: Maximum time to wait for dependency completion (default 1 minute)
Dependency Examples
Failure handling strategies:
// Skip: Continue execution when dependency fails taskC, _ := scheduler.Add("0 3 * * *", func() error { fmt.Println("Generating report...") return nil }, "Report generation", []Wait{ {ID: taskA, State: Skip}, // Skip when taskA fails {ID: taskB, State: Stop}, // Stop when taskB fails (default) })
Custom timeout:
// Set independent wait time for each dependency taskC, _ := scheduler.Add("0 3 * * *", func() error { fmt.Println("Generating report...") return nil }, "Report generation", []Wait{ {ID: taskA, Delay: 30 * time.Second}, // Wait 30 seconds {ID: taskB, Delay: 45 * time.Second}, // Wait 45 seconds })
Combined usage:
// Combine failure strategies with custom timeout taskC, _ := scheduler.Add("0 3 * * *", func() error { fmt.Println("Generating report...") return nil }, "Report generation", []Wait{ {ID: taskA, Delay: 30 * time.Second, State: Skip}, {ID: taskB, Delay: 45 * time.Second, State: Stop}, })
Task Status
const ( TaskPending // Waiting TaskRunning // Running TaskCompleted // Completed TaskFailed // Failed / Timeout )
Timeout Mechanism
When execution time exceeds the set Delay:
- Interrupts task execution
- Triggers
OnDelayfunction (if set) - Logs timeout
- Continues with next schedule
Features
- Timeout implemented using
context.WithTimeout - Timeout doesn't affect other task execution
- If action completes before timeout, timeout won't trigger
Upcoming Features
Enhanced Task Dependencies
- Status callbacks: Add
OnTimeoutandOnFailedcallback functions for monitoring and responding to dependency task exception states
Task Completion Trigger Refactor
- Event-driven: Replace current polling with fully
channel-based approach to reduce CPU usage - Dependency awakening: Implement active notification mechanism when dependency tasks complete, eliminating ineffective polling checks
License
This project is licensed under MIT.
Star
Author
邱敬幃 Pardn Chiu
©️ 2025 邱敬幃 Pardn Chiu