feat(jobs): add declarative task scheduler API (closes #159)#207
Merged
Conversation
Deploying simplemodule-website with
|
| Latest commit: |
67f2010
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://3f5e576f.simplemodule-website.pages.dev |
| Branch Preview URL: | https://task-scheduler.simplemodule-website.pages.dev |
Layers a Laravel-style fluent scheduling surface on top of the existing BackgroundJobs infrastructure so modules can declare recurring jobs at startup time. - IScheduler / IScheduledJob<T> with Cron/Daily/DailyAt/EveryMinutes/ Hourly/Weekdays/Timezone/WithoutOverlapping/OnOneServer/WithPayload - Modules call services.AddScheduledJobs(scheduler => ...) from their ConfigureServices; first call creates the shared SchedulerRegistry - Hosted SchedulerService ticks every 30 s (configurable): reconciles registered definitions to ScheduledJobStates, optionally takes the OnOneServer lease, then enqueues due jobs through IJobQueue - WithoutOverlapping backed by JobMutexes; mutex released by JobProcessorService when the job finishes (success or failure) - OnOneServer backed by JobLeases with TTL-based takeover - Per-definition try/catch so a bad cron never stops the loop - sm jobs list-scheduled CLI prints next/last run via raw ADO.NET (Sqlite + Postgres) against the configured database - 31 xUnit tests covering DSL, mutex contention, leader election, tick enqueue path, failure isolation, and WithoutOverlapping skip - docs/scheduler.md walks through registration, semantics, and config
- Reuse CronCalculator across BackgroundJobsService and JobProcessorService —
removes three duplicate "Split(' ').Length > 5 ? IncludeSeconds : Standard"
blocks. Adds CronCalculator.GetNextOccurrenceUtc(...) for the legacy path.
- Auto-register module job types from AddScheduledJobs so callers no longer
need a separate AddModuleJob<T>() call — single point of registration.
- Skip the no-op UPDATE in SchedulerReconciler when nothing has changed
(avoids per-tick write storm).
- Fresh DbContext scope per due definition in SchedulerService — prevents
the mutex / queue SaveChangesAsync from prematurely flushing other tracked
ScheduledJobState mutations mid-tick.
- Make IJobMutex, IInstanceLeader, DatabaseJobMutex, DatabaseInstanceLeader,
and SchedulerService internal; only Contracts surface stays public.
- Add [NoDtoGeneration] to ModuleJobRegistration (was leaking into types.ts).
- Promote "mutex:" literal to SchedulerOptions.MutexPrefix.
- Simplify CLI ShortType() to mirror BackgroundJobsInternalConstants.GetShortTypeName.
f800313 to
67f2010
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ISchedulerAPI inBackgroundJobs.Contractsso modules can declare recurring jobs at startup viaservices.AddScheduledJobs(scheduler => scheduler.Job<T>().DailyAt("02:00")...). Builder supportsCron / EveryMinutes / Hourly / Daily / DailyAt / Weekdays / Timezone / WithoutOverlapping / OnOneServer / WithPayload.SchedulerServiceticks every 30 s (configurable). Each tick reconciles in-memory definitions to a newScheduledJobStatestable, optionally acquires theOnOneServerlease, then enqueues due jobs through the existingIJobQueue. Per-definitiontry/catchkeeps one bad cron from stopping the loop.WithoutOverlappingis backed by a per-job mutex row (JobMutexes).JobProcessorServicereleases the mutex on completion or failure of anyschedule:-sentinel entry.OnOneServeris backed by a single-lease row (JobLeases) with TTL-based takeover.sm jobs list-scheduledCLI readsScheduledJobStatesvia raw ADO.NET (Sqlite or Postgres), falls back toappsettings.json, and renders Name / Job type / Cron / TZ / Next run / Last run / flags.docs/scheduler.mdcovering quick-start, DSL reference, reconciliation/mutex/lease semantics, and configuration knobs.Closes #159.
Test plan
dotnet buildclean across the whole solution underTreatWarningsAsErrors.dotnet test— full suite green (1015 tests, 0 failures), including 31 new tests undermodules/BackgroundJobs/tests/.../Scheduler/:ScheduledJobBuilderTests— DSL → cron rendering + validation (bad time, bad timezone, duplicate names, default name).DatabaseJobMutexTests— first-wins, contention, same-owner refresh, takeover after TTL, release.DatabaseInstanceLeaderTests— same shape for theOnOneServerlease.SchedulerServiceTickTests— due-job enqueue, future-job skip, bad-cron isolation,OnOneServerlets only one host enqueue,WithoutOverlappingskips when the mutex is held externally.sm jobs list-scheduled --helprenders correctly via the new branch.