Table of Contents

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

schedulerName string

The scheduler's name, which is also its instance name and the name of its options.

configure Action<IQuartzBuilder>

Configures the scheduler, as an AddQuartz(name, …) callback does.

options SchedulerAddOptions

Settings for the scheduler and whether to start it. The default is a scheduler configured entirely by configure and started.

cancellationToken CancellationToken

The 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

schedulerName string

The scheduler's name.

waitForJobsToComplete bool

Whether to wait for the jobs that are running to finish before the scheduler shuts down.

cancellationToken CancellationToken

The 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

schedulerName string

The scheduler's name.

options SchedulerRestartOptions

How 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.

cancellationToken CancellationToken

The 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.