Job Scheduler
Relevant source files
The following files were used as context for generating this wiki page:
The Job Scheduler runs background tasks in-process for the GoCasa application. It manages recurring work such as database backups and integration synchronization. The scheduler executes tasks based on specific timing conditions and ensures that failures in one job do not interrupt others.
Sources: internal/app/scheduler.go:12-14, README.md:31-33
Architecture and Components
The scheduler uses a simple internal registry of jobs. Each job consists of a name, a predicate function to determine if it is due, and a execution function.
Data Structures
The system relies on two primary structures:
| Structure | Description |
|---|---|
Scheduler | Manages the list of jobs, thread safety via a mutex, and the execution loop. |
job | Represents a single unit of work with a due check and a run action. |
Sources: internal/app/scheduler.go:15-26
Execution Logic
The scheduler operates through a ticking mechanism. In production, it typically ticks every 30 seconds. When a tick occurs, the scheduler iterates through its registered jobs. If a job's due function returns true, the scheduler executes the job's run function.
The diagram shows the sequence of checking and executing jobs during a scheduler tick.
Sources: internal/app/scheduler.go:34-51
Key System Jobs
GoCasa registers several critical background tasks during application startup.
Database Backups
The application performs a VACUUM INTO operation daily at 03:00 (in the configured TZ). It maintains the newest 14 backups. If the system starts and finds the latest backup is older than 24 hours, it triggers an immediate backup.
Sources: README.md:31-33, internal/app/app.go:88-91
Integration Synchronization
The scheduler manages external integrations through a job named "integrations". This job checks for due synchronizations every tick.
Sources: internal/app/app.go:92
Implementation Details
Thread Safety and Error Handling
The Scheduler uses a sync.Mutex to protect the job slice during registration and execution. The runJob function includes a recovery block to catch panics, preventing a single failing job from crashing the entire scheduler or application.
func runJob(ctx context.Context, j job, now time.Time) (err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("panic: %v", p)
}
}()
return j.run(ctx, now)
}
Sources: internal/app/scheduler.go:53-61
Registration and Running
You add jobs using the Add method. The Run method starts a persistent loop using a time.Ticker until the provided context is cancelled.
| Function | Purpose |
|---|---|
Add | Registers a new background job with the scheduler. |
RunDue | Manages a single pass over all registered jobs to execute those currently due. |
Run | Starts the background execution loop at a specified interval. |
Sources: internal/app/scheduler.go:28-82
Testing Context
In test environments, the scheduler uses a fake clock to allow deterministic testing of time-based jobs. Tests call RunDue directly after manipulating the fake clock to verify that jobs execute only when expected.
This diagram illustrates how tests trigger scheduled jobs using a controlled time source.
Sources: internal/app/scheduler.go:13-14, internal/app/app_test.go:49-62
Summary
The Job Scheduler provides a reliable, in-process mechanism for GoCasa to handle background maintenance. By separating the "due" logic from the execution logic and implementing robust error recovery, it ensures that essential services like backups and data syncing remain operational without requiring external task managers.