Skip to content

Feat/scheduler#29

Merged
hyp3rd merged 2 commits intomainfrom
feat/scheduler
Mar 2, 2026
Merged

Feat/scheduler#29
hyp3rd merged 2 commits intomainfrom
feat/scheduler

Conversation

@hyp3rd
Copy link
Copy Markdown
Owner

@hyp3rd hyp3rd commented Mar 2, 2026

No description provided.

hyp3rd added 2 commits March 2, 2026 00:31
…on/pruning

- Add context.Context to Scheduler public API (e.g., NewScheduler, Schedule,
  Remove, Stop, JobCount, JobIDs, JobStatus/JobStatuses, JobHistory,
  QueryJobStatuses/QueryJobHistory, reconcileRecoveredState, etc.)
- Update storage interfaces to require context across all operations
- SQLite storage:
  - Introduce finished_at_unix_nano column and handle bootstrap migration
  - Add history retention options (WithSQLiteHistoryRetention,
    WithSQLiteHistoryMaxAge, WithSQLiteHistoryMaxRowsPerJob)
  - Implement PruneHistory / PruneHistoryWithRetention and related helpers
  - Provide NewSQLiteJobsStorageWithOptions for options-based setup
- Update examples and tests to use context (t.Context()) and new APIs
- Docs: update README and PRD with SQLite retention and pruning guidance

BREAKING CHANGE: Scheduler and storage method signatures now take
context.Context. All call sites must be updated to pass a context.
SQLite history schema adds finished_at_unix_nano; migration is handled
automatically during bootstrap.
- Introduce pollTimeout (5s) and create ctx via context.WithTimeout; defer cancel
- Pass ctx to NewScheduler and NewSQLiteJobsStorageWithOptions
- Update calls: Stop() → Stop(ctx), Schedule(job) → Schedule(ctx, job)
- Update lookups: JobStatus(id) → JobStatus(ctx, id), JobHistory(id) → JobHistory(ctx, id)
- SQLite example: PruneHistory() → PruneHistory(ctx)

Aligns examples with the new context-aware API and ensures graceful shutdowns and bounded polling.
Copilot AI review requested due to automatic review settings March 2, 2026 15:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the scheduler and storage layers to be context-aware and adds SQLite job-history retention/pruning capabilities, updating tests and examples accordingly.

Changes:

  • Add context.Context parameters across scheduler and JobsStorage APIs.
  • Implement SQLite history retention (max-age, max-rows-per-job) plus manual pruning APIs and a schema migration column for retention metadata.
  • Update scheduler tests, storage contract tests, and examples to use the new context-aware APIs.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/scheduler_test.go Updates scheduler tests to pass contexts and refactors status-query assertions into helpers.
pkg/scheduler/storage_sqlite.go Adds history retention options, prune APIs, and a finished_at_unix_nano column used for age-based pruning.
pkg/scheduler/storage_contract_test.go Updates contract tests for ctx-aware storage and adds retention/pruning behavior tests for SQLite.
pkg/scheduler/storage.go Updates JobsStorage interface and in-memory implementation to accept context.Context.
pkg/scheduler/scheduler_internal_test.go Updates internal scheduler tests to use ctx-aware constructors and methods.
pkg/scheduler/scheduler.go Makes scheduler APIs ctx-aware and threads contexts through storage/status/history operations and job execution.
__examples/scheduler_sqlite/scheduler_sqlite.go Updates SQLite example to use ctx-aware APIs and demonstrates pruning.
__examples/scheduler/scheduler.go Updates basic scheduler example to use ctx-aware APIs.
README.md Attempts to document SQLite retention/pruning features (currently has signature mismatches).
PRD.md Updates product notes to include SQLite history retention/pruning capabilities.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
Comment on lines 255 to +260
dbPath := filepath.Join(os.TempDir(), "go-again-scheduler-example.db")
storage, err := scheduler.NewSQLiteJobsStorage(dbPath)
storage, err := scheduler.NewSQLiteJobsStorageWithOptions(
dbPath,
scheduler.WithSQLiteHistoryMaxAge(24*time.Hour),
scheduler.WithSQLiteHistoryMaxRowsPerJob(100),
)
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README example no longer matches the updated SQLite storage constructor signature: NewSQLiteJobsStorageWithOptions now requires a context.Context as the first argument. As written, this snippet won’t compile and should be updated to pass a ctx (and likely update the rest of the snippet to use the new ctx-taking scheduler APIs as well).

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +281 to +285
pruned, err := storage.PruneHistory()
if err != nil {
panic(err)
}
fmt.Println("pruned rows:", pruned)
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQLiteJobsStorage.PruneHistory now requires a context.Context parameter. This example calls it with no args, so it won’t compile; update it to pass the same ctx used to construct the storage/scheduler (or another appropriate context).

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines 329 to +331
- `NewSQLiteJobsStorage(path)` provides a built-in durable storage implementation for `WithJobsStorage(...)`; call `Close()` when finished.
- `NewSQLiteJobsStorageWithOptions(path, ...)` configures SQLite retention controls: `WithSQLiteHistoryMaxAge(duration)`, `WithSQLiteHistoryMaxRowsPerJob(n)`, and `WithSQLiteHistoryRetention(...)`.
- `SQLiteJobsStorage.PruneHistory()` and `PruneHistoryWithRetention(...)` provide manual pruning for periodic cleanup jobs.
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These bullets document the SQLite APIs without the new context.Context parameters (e.g., NewSQLiteJobsStorageWithOptions and PruneHistory/PruneHistoryWithRetention). Please update the documented signatures to include the ctx arguments so readers don’t copy/paste non-compiling examples.

Suggested change
- `NewSQLiteJobsStorage(path)` provides a built-in durable storage implementation for `WithJobsStorage(...)`; call `Close()` when finished.
- `NewSQLiteJobsStorageWithOptions(path, ...)` configures SQLite retention controls: `WithSQLiteHistoryMaxAge(duration)`, `WithSQLiteHistoryMaxRowsPerJob(n)`, and `WithSQLiteHistoryRetention(...)`.
- `SQLiteJobsStorage.PruneHistory()` and `PruneHistoryWithRetention(...)` provide manual pruning for periodic cleanup jobs.
- `NewSQLiteJobsStorage(ctx context.Context, path)` provides a built-in durable storage implementation for `WithJobsStorage(...)`; call `Close()` when finished.
- `NewSQLiteJobsStorageWithOptions(ctx context.Context, path, ...)` configures SQLite retention controls: `WithSQLiteHistoryMaxAge(duration)`, `WithSQLiteHistoryMaxRowsPerJob(n)`, and `WithSQLiteHistoryRetention(...)`.
- `SQLiteJobsStorage.PruneHistory(ctx context.Context)` and `PruneHistoryWithRetention(ctx context.Context, ...)` provide manual pruning for periodic cleanup jobs.

Copilot uses AI. Check for mistakes.
Comment on lines 495 to 505
func (s *Scheduler) runJob(ctx context.Context, entry *jobEntry) {
defer s.wg.Done()
defer s.cleanupJob(entry)
defer s.cleanupJob(ctx, entry)

job := entry.job

terminalState := JobStateCompleted

defer func() {
s.finalizeJobState(job.ID, terminalState)
s.finalizeJobState(ctx, job.ID, terminalState)
}()
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runJob defers cleanupJob and finalizeJobState using the job’s ctx. If that ctx is canceled (e.g., if callers pass a request-scoped context into Schedule), the SQLite storage operations inside these paths can be aborted, leaving active-job rows and/or terminal status updates unpersisted. Consider using a non-cancelable context for best-effort persistence (e.g., context.WithoutCancel(...) or the scheduler’s lifecycle context) for cleanup/finalization writes, while still respecting cancellation for the execution loop itself.

Copilot uses AI. Check for mistakes.
Comment on lines +894 to +913
func pruneHistoryOlderThanForJobTx(ctx context.Context, tx *sql.Tx, id string, cutoffUnixNano int64) (int64, error) {
res, err := tx.ExecContext(
ctx,
`DELETE FROM scheduler_job_history WHERE id = ? AND finished_at_unix_nano > 0 AND finished_at_unix_nano < ?`,
id,
cutoffUnixNano,
)
if err != nil {
return 0, ewrap.Wrapf(ErrStorageOperation, "prune execution history by age for job failed: %v", err)
}

return rowsAffected(res)
}

func pruneHistoryOlderThanTx(ctx context.Context, tx *sql.Tx, cutoffUnixNano int64) (int64, error) {
res, err := tx.ExecContext(
ctx,
`DELETE FROM scheduler_job_history WHERE finished_at_unix_nano > 0 AND finished_at_unix_nano < ?`,
cutoffUnixNano,
)
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Age-based pruning ignores rows where finished_at_unix_nano is 0 (> 0 filter). After migrating an existing DB, all pre-existing history rows will have the new column defaulted to 0, so MaxAge retention and PruneHistory* won’t remove any of that existing history by age. Consider backfilling finished_at_unix_nano from payload_json during bootstrap/migration, or adjusting the pruning semantics for 0 values so retention behaves as expected on upgraded databases.

Copilot uses AI. Check for mistakes.
@hyp3rd hyp3rd merged commit 1c25391 into main Mar 2, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants