Add support for multiple named schedulers in Microsoft DI#3000
Merged
Conversation
Add AddQuartz(string name, ...) overloads that allow registering multiple independent schedulers in a single DI container. Each named scheduler gets isolated options, jobs, triggers, listeners, and calendars via MS Options named instances. AddQuartzHostedService() automatically starts all registered schedulers (both default and named). No breaking changes - existing single-scheduler AddQuartz() API is unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
There was a problem hiding this comment.
Pull request overview
Adds first-class support for registering and running multiple named Quartz.NET schedulers within a single Microsoft DI container, with per-scheduler isolation of options (jobs/triggers), listeners, and calendars, and hosting integration to start/stop all registered schedulers.
Changes:
- Introduces
AddQuartz(string name, ...)overloads that configureQuartzOptionsas named options instances and track named schedulers via a registry. - Adds hosting support (
NamedSchedulerHostedService) to start/stop all named schedulers alongside the existing default scheduler hosted service. - Updates DI listener/calendar plumbing to scope registrations to either default (unnamed) or named schedulers, plus adds docs and unit tests.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Quartz.Tests.Unit/Extensions/DependencyInjection/MultipleSchedulerTests.cs | Adds unit tests covering named options isolation, listener/calendar isolation, registry behavior, and hosted service registration behavior. |
| src/Quartz.Extensions.Hosting/QuartzServiceCollectionExtensions.cs | Updates hosted service registration to include named-scheduler hosted service and conditionally register the default hosted service. |
| src/Quartz.Extensions.Hosting/NamedSchedulerHostedService.cs | New hosted service that creates/starts/stops all named schedulers tracked in the registry. |
| src/Quartz.Extensions.DependencyInjection/ServiceCollectionExtensions.cs | Adds AddQuartz(name, ...) overloads and routes job/trigger/calendar configuration into the correct named options instance. |
| src/Quartz.Extensions.DependencyInjection/NamedSchedulerFactory.cs | New factory to create/initialize a single named scheduler and apply only that scheduler’s listeners/calendars/jobs. |
| src/Quartz.Extensions.DependencyInjection/SchedulerNameRegistry.cs | New registry for tracking named schedulers registered into the container. |
| src/Quartz.Extensions.DependencyInjection/SchedulerListenerConfiguration.cs | New configuration record for per-scheduler scheduler-listener registrations. |
| src/Quartz.Extensions.DependencyInjection/ServiceCollectionQuartzConfigurator.cs | Tags listener registrations with an options name and avoids polluting global listener DI registrations for named schedulers. |
| src/Quartz.Extensions.DependencyInjection/ServiceCollectionSchedulerFactory.cs | Filters default-scheduler listener/calendar configuration so named scheduler config doesn’t leak into the default scheduler. |
| src/Quartz.Extensions.DependencyInjection/JobListenerConfiguration.cs | Extends config to include options-name scoping and optional factory/instance support for named schedulers. |
| src/Quartz.Extensions.DependencyInjection/TriggerListenerConfiguration.cs | Extends config to include options-name scoping and optional factory/instance support for named schedulers. |
| src/Quartz.Extensions.DependencyInjection/CalendarConfiguration.cs | Adds options-name scoping so calendars can be isolated per scheduler. |
| src/Quartz.Extensions.DependencyInjection/IServiceCollectionQuartzConfigurator.cs | Adds internal OptionsName so fluent job/trigger/calendar registration can target named options. |
| src/Quartz.Extensions.DependencyInjection/AssemblyInfoExtras.cs | Adds InternalsVisibleTo for Quartz.Extensions.Hosting to access internal multi-scheduler types. |
| docs/documentation/quartz-3.x/packages/multiple-schedulers.md | New documentation page describing named schedulers usage, isolation, and limitations. |
| docs/documentation/quartz-3.x/packages/microsoft-di-integration.md | Links to the new multiple schedulers documentation. |
| docs/documentation/quartz-3.x/packages/hosted-services-integration.md | Links to the new multiple schedulers documentation. |
| docs/.vuepress/config.js | Adds the new documentation page to the sidebar navigation. |
- Add duplicate-name guard to SchedulerNameRegistry (throws on re-registration) - Make StopAsync resilient: try/catch per scheduler, AggregateException on failures - Clean up partially-created schedulers on startup failure - Prevent SchedulerName setter override in named configurator (throws) - Guard UseJobFactory/UseTypeLoader/UsePersistentStore against global DI side effects from named schedulers (skip services.Replace/AddSingleton for named path) - Fix UseJobFactory to configure named options key instead of default - Add null check on properties parameter in named AddQuartz overload - Remove unused using Quartz.Xml - Simplify SchedulerNameRegistry registration pattern - Use explicit types instead of var per .editorconfig - Add tests: duplicate names, ScheduleJob with named, SchedulerName setter conflict, null properties, AddQuartzHostedService without AddQuartz Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Clarify registration order comment in AddQuartzHostedService - Fix docs to recommend ISchedulerRepository for named-only scenarios since ISchedulerFactory is not registered without unnamed AddQuartz() Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- NamedSchedulerFactory.InstantiateType<T>: resolve by concrete implementationType first (not interface) to avoid picking up singletons from other schedulers that break per-scheduler isolation - NamedSchedulerHostedService: clean up already-created schedulers on OperationCanceledException, and relax StopAsync guard so it shuts down schedulers even when startupTask was never assigned Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Use TryAddEnumerable for QuartzHostedService to prevent duplicate registrations when AddQuartzHostedService() is called multiple times - Re-force quartz.scheduler.instanceName after configure callback to prevent SetProperty()/Properties[] from desyncing the options key from the actual scheduler instance name Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Coalesce null Matchers to Array.Empty in NamedSchedulerFactory to prevent NRE when params matchers are explicitly passed as null - Add warning to docs about AddQuartz() ordering before AddQuartzHostedService() for the default scheduler Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
lahma
added a commit
that referenced
this pull request
Apr 4, 2026
Port of 3.x PR #3000 to 4.x, adapted for main branch idioms: - Files in src/Quartz/Configuration/ and src/Quartz/Hosting/ (consolidated into core Quartz assembly) - ValueTask return types on interfaces (IJob, ISchedulerListener, etc.) - IHostedLifecycleService with Starting/Started/Stopping/Stopped hooks - GetDbConnectionManager (lowercase b) naming convention - ContainerConfigurationProcessor takes ILogger, TimeProvider - Collection literals [], ArgumentException.ThrowIfNullOrWhiteSpace - No #if conditional compilation or netstandard2.0 support - Full nullable reference types - 4.x docs under docs/documentation/quartz-4.x/packages/ Includes all review fixes from 3.x PR: - Duplicate name guard, shutdown resilience, partial startup cleanup - SchedulerName setter protection, InstantiateType isolation - UseJobFactory/UseTypeLoader/UsePersistentStore guards for named - Null matchers coalescing, idempotent hosted service registration - Instance name drift prevention via re-force after configure Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This was referenced Apr 4, 2026
lahma
added a commit
that referenced
this pull request
Apr 4, 2026
Main branch is the publish source for all docs including 3.x. Add the 3.x multiple-schedulers doc page, sidebar entry, and see-also links that were part of the 3.x PR (#3000). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
lahma
added a commit
that referenced
this pull request
Apr 4, 2026
* Port multiple named schedulers to 4.x (main) Port of 3.x PR #3000 to 4.x, adapted for main branch idioms: - Files in src/Quartz/Configuration/ and src/Quartz/Hosting/ (consolidated into core Quartz assembly) - ValueTask return types on interfaces (IJob, ISchedulerListener, etc.) - IHostedLifecycleService with Starting/Started/Stopping/Stopped hooks - GetDbConnectionManager (lowercase b) naming convention - ContainerConfigurationProcessor takes ILogger, TimeProvider - Collection literals [], ArgumentException.ThrowIfNullOrWhiteSpace - No #if conditional compilation or netstandard2.0 support - Full nullable reference types - 4.x docs under docs/documentation/quartz-4.x/packages/ Includes all review fixes from 3.x PR: - Duplicate name guard, shutdown resilience, partial startup cleanup - SchedulerName setter protection, InstantiateType isolation - UseJobFactory/UseTypeLoader/UsePersistentStore guards for named - Null matchers coalescing, idempotent hosted service registration - Instance name drift prevention via re-force after configure Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * Fix review findings: InstantiateType, cancellation, RegisterSingleton - NamedSchedulerFactory.InstantiateType: throw clear InvalidOperationException instead of passing null to ObjectUtils.InstantiateType (guaranteed crash) - NamedSchedulerHostedService: re-throw OperationCanceledException after cleanup so host knows startup was aborted - ServiceCollectionQuartzConfigurator.RegisterSingleton: guard for named schedulers to prevent global DI contamination Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * Add 3.x documentation for multiple schedulers Main branch is the publish source for all docs including 3.x. Add the 3.x multiple-schedulers doc page, sidebar entry, and see-also links that were part of the 3.x PR (#3000). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
|
The changes contain breaking changes because the order of calling |
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
Closes #2109
AddQuartz(string name, ...)overloads to register multiple independent schedulers in a single DI containerQuartzOptions(jobs, triggers), listeners, and calendars via MS Options named instancesAddQuartzHostedService()automatically starts all registered schedulers (both default and named)AddQuartz()API is completely unchangedNew types
NamedSchedulerFactory-- per-scheduler factory extendingStdSchedulerFactorywith listener/calendar filtering by options nameNamedSchedulerHostedService--IHostedServicemanaging all named schedulers lifecycleSchedulerNameRegistry-- tracks registered named scheduler namesSchedulerListenerConfiguration-- configuration class for scheduler listeners (enables named isolation)Target API
Test plan
net10.0andnetstandard2.0🤖 Generated with Claude Code