Interface ISchedulerRuntime
- Namespace
- Quartz
- Assembly
- Quartz.dll
Adds and removes schedulers in a container that has already been built.
public interface ISchedulerRuntime : ISchedulerRegistry
- Inherited Members
Remarks
A scheduler registered with AddQuartz is a set of registrations fixed when the container was
built, and a name the container never heard of has none. This is the other door: a scheduler added
here is built into a container of its own, holding its own thread pool, job store, connection
provider, plugins and listeners, and resolving everything else — the application's services, its
jobs, its options — from the application's container. It is bound into the same
ISchedulerRepository as every other scheduler, so the HTTP API, the
dashboard and GetAllSchedulers(CancellationToken) see it without knowing it arrived
late.
It extends ISchedulerRegistry: QuerySchedulers(CancellationToken) lists what the container registered and what was added at runtime alike, the latter with Runtime. Resolving either interface answers with the same object, so an application that only reads the listing need not know this one exists.
Registered by AddQuartz, so any container with Quartz in it has one. What it holds is the
container's for as long as the container lives: a scheduler still running when the host stops is
shut down with the rest, and one still running when the container is disposed is shut down then.
One thing it deliberately does not do is delete data. A removed tenant's rows under its
SCHED_NAME stay exactly where they are, and its SCHEDULER_STATE row expires the way a
stopped node's does. Deleting a tenant's data is the application's decision, not a side effect of
removing its scheduler.
Methods
Add(string, Action<IQuartzBuilder>?, SchedulerAddOptions, CancellationToken)
Builds a scheduler under a name the container does not hold, binds it into the repository and — unless told otherwise — starts it.
ValueTask<IScheduler> Add(string schedulerName, Action<IQuartzBuilder>? configure = null, SchedulerAddOptions options = default, CancellationToken cancellationToken = default)
Parameters
schedulerNamestringThe scheduler's name, which is also its instance name and the name of its options.
configureAction<IQuartzBuilder>Configures the scheduler, as an
AddQuartz(name, …)callback does.optionsSchedulerAddOptionsSettings for the scheduler and whether to start it. The default is a scheduler configured entirely by
configureand started.cancellationTokenCancellationTokenThe cancellation instruction.
Returns
- ValueTask<IScheduler>
The scheduler, created and bound.
Remarks
configure is the same IQuartzBuilder an
AddQuartz(name, …) callback is given, applied in the same order to the same phases, so a
recipe reads identically whichever door it came through. Its
Services is this scheduler's collection rather than the
application's: what it registers there belongs to this scheduler and goes away with it.
Whatever ConfigureAllQuartzSchedulers said about every scheduler in the container is
applied here too, after configure, exactly where a scheduler registered at
startup receives it.
This fails soft: a name that cannot be added is refused with a SchedulerConfigException saying which rule and what to do about it, and nothing is left behind — no half-built scheduler in the repository, and nothing in the listing. A name is refused when the container registered it, when a scheduler is already bound under it, when one has already been added under it, and when the host is stopping.
The scheduler is reached afterwards through LookupScheduler(string, CancellationToken) or
the repository. It is not reachable as
[FromKeyedServices("name")] IScheduler: that is a container registration, and the point of
this method is a scheduler the container was never told about.
Exceptions
- SchedulerConfigException
The name cannot be added, or the scheduler could not be built from what it was configured with.
Remove(string, bool, CancellationToken)
Shuts down a scheduler this runtime added, unbinds it from the repository and releases everything built for it.
ValueTask<bool> Remove(string schedulerName, bool waitForJobsToComplete = false, CancellationToken cancellationToken = default)
Parameters
schedulerNamestringThe scheduler's name.
waitForJobsToCompleteboolWhether to wait for the jobs that are running to finish before the scheduler shuts down.
cancellationTokenCancellationTokenThe cancellation instruction.
Returns
- ValueTask<bool>
true when a scheduler was removed, false when this runtime held none under that name.
Remarks
A name this runtime did not add is not an error: it answers false, so removing twice, or removing a tenant that was shut down by hand, says what happened rather than throwing. A name the container registered is a different matter and is refused — the container owns those, and Shutdown(bool, CancellationToken) is how one of them stops.
Nothing is deleted from the store. See the remarks on ISchedulerRuntime.
Exceptions
- SchedulerConfigException
The container registered this name.
Restart(string, SchedulerRestartOptions, CancellationToken)
Shuts a scheduler down and builds another one from the recipe that built it.
ValueTask<IScheduler> Restart(string schedulerName, SchedulerRestartOptions options = default, CancellationToken cancellationToken = default)
Parameters
schedulerNamestringThe scheduler's name.
optionsSchedulerRestartOptionsHow long to wait for the outgoing scheduler's jobs, and whether to start the new one. The default waits thirty seconds and starts the new scheduler if the old one was running.
cancellationTokenCancellationTokenThe cancellation instruction.
Returns
- ValueTask<IScheduler>
The new scheduler, created and bound.
Remarks
Nothing is restarted, and the name is the only thing the two schedulers share. A scheduler's
thread pool, job store, connection provider, plugins and listeners are all one-way — every one of
them refuses work once it has been shut down — so a second generation is a second set of
instances, built by running the recipe again into a container of its own. That is what
Add(string, Action<IQuartzBuilder>?, SchedulerAddOptions, CancellationToken) does for a name the container never heard of, and it is what this does for a
name it did: AddQuartz(name, …) records what it was told, so a registered scheduler is
restartable too.
The order is build, drain, create — and each step is where it is for a reason. The new generation's container is built first, so a recipe that no longer works leaves the old scheduler running rather than nothing at all. The old scheduler is then shut down waiting for its jobs, because a persistent store's recovery sweep runs over the whole scheduler name unfiltered by instance id: a new generation that started while the old one's jobs ran would move their triggers back to waiting and delete their fired-trigger rows underneath them. Only once that drain has finished is the new scheduler created, which is when its store is initialized and its declared jobs and triggers are applied.
A restart is observable from outside the process. With a fixed instance id the new generation
checks in under the same SCHEDULER_STATE row; with AUTO it takes a new one and the
old row expires the way a stopped node's does. Either way a clustered peer sees what it sees when
a node is restarted, because that is what this is. A job that outlives the drain is at-least-once:
the new generation's recovery re-fires it exactly as it would after a crash.
A name this runtime does not hold and the container did not register is a SchedulerNotFoundException. A name it holds but whose scheduler was shut down — by hand, by the host, or by a restart whose drain gave up — is not an error: there is simply nothing to shut down first, and this builds the next generation.
Exceptions
- SchedulerNotFoundException
Neither this runtime nor the container holds a scheduler of that name.
- SchedulerConfigException
The scheduler has no recipe that can be replayed — the default scheduler, or one whose recipe supplies a part as an instance — or the recipe no longer builds. The old scheduler keeps running.
- SchedulerRestartException
The outgoing scheduler's jobs were still running when the drain gave up. The old scheduler is shut down and the new one was not built; ask again once the work has finished.