Skip to content

Add support for multiple named schedulers in Microsoft DI#3000

Merged
lahma merged 6 commits into
3.xfrom
multiple-hosted-schedulers
Apr 4, 2026
Merged

Add support for multiple named schedulers in Microsoft DI#3000
lahma merged 6 commits into
3.xfrom
multiple-hosted-schedulers

Conversation

@lahma

@lahma lahma commented Apr 4, 2026

Copy link
Copy Markdown
Member

Summary

Closes #2109

  • Adds AddQuartz(string name, ...) overloads to register multiple independent schedulers in a single DI container
  • Each named scheduler gets isolated QuartzOptions (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 completely unchanged

New types

  • NamedSchedulerFactory -- per-scheduler factory extending StdSchedulerFactory with listener/calendar filtering by options name
  • NamedSchedulerHostedService -- IHostedService managing all named schedulers lifecycle
  • SchedulerNameRegistry -- tracks registered named scheduler names
  • SchedulerListenerConfiguration -- configuration class for scheduler listeners (enables named isolation)

Target API

services.AddQuartz("Scheduler1", q => {
    q.AddJob<EmailJob>(j => j.WithIdentity("email"));
    q.AddTrigger(t => t.ForJob("email").WithCronSchedule("0 0/5 * * * ?"));
});
services.AddQuartz("Scheduler2", q => {
    q.UsePersistentStore(s => { /* ... */ });
    q.AddJob<ReportJob>(j => j.WithIdentity("report"));
    q.AddTrigger(t => t.ForJob("report").WithCronSchedule("0 0 * * * ?"));
});
services.AddQuartzHostedService(o => o.WaitForJobsToComplete = true);

Test plan

  • 13 new unit tests covering: options isolation, listener isolation, calendar isolation, registry tracking, mixed default+named mode, empty name validation, hosted service registration
  • Full test suite passes (1240 passed, 4 skipped -- no regressions)
  • Builds clean on both net10.0 and netstandard2.0
  • Manual integration test with two named schedulers

🤖 Generated with Claude Code

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]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 configure QuartzOptions as 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.

Comment thread src/Quartz.Extensions.Hosting/QuartzServiceCollectionExtensions.cs
Comment thread src/Quartz.Extensions.DependencyInjection/SchedulerNameRegistry.cs Outdated
Comment thread docs/documentation/quartz-3.x/packages/multiple-schedulers.md Outdated
Comment thread src/Quartz.Extensions.DependencyInjection/NamedSchedulerFactory.cs Outdated
lahma and others added 2 commits April 4, 2026 14:43
- 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]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Comment thread src/Quartz.Extensions.DependencyInjection/NamedSchedulerFactory.cs Outdated
Comment thread src/Quartz.Extensions.Hosting/NamedSchedulerHostedService.cs
- 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]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Comment thread src/Quartz.Extensions.Hosting/QuartzServiceCollectionExtensions.cs
- 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]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Comment thread src/Quartz.Extensions.DependencyInjection/NamedSchedulerFactory.cs
Comment thread src/Quartz.Extensions.DependencyInjection/NamedSchedulerFactory.cs Outdated
- 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]>
@sonarqubecloud

sonarqubecloud Bot commented Apr 4, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@lahma
lahma merged commit cb0c746 into 3.x Apr 4, 2026
19 checks passed
@lahma
lahma deleted the multiple-hosted-schedulers branch April 4, 2026 13:05
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]>
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]>
@xtuker

xtuker commented May 8, 2026

Copy link
Copy Markdown

The changes contain breaking changes because the order of calling AddQuartz() and AddQuartzHostedService() is now important.

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.

3 participants