Found by the multi-tenancy audit for alpha.2; the documentation is being corrected to describe it, this issue is about whether to change it.
A job is handed different collaborators depending on whether the container holds a registration for its type:
AddJob<T> registers the job type with TryAddScoped (src/Quartz/Configuration/QuartzBuilderExtensions.cs, the TryAddScoped(jobType) helper). MicrosoftDependencyInjectionJobFactory then resolves it from the fire scope, so the container activates it — and a constructor that takes ISchedulerFactory, IJobStore, IThreadPool or IOptions<QuartzSchedulerOptions> receives the default scheduler's (or fails container validation in a container with only named schedulers).
- A job type the container does not hold is created through
JobActivatorCache on the scheduler-scoped provider, and the same constructor receives its own scheduler's parts.
So registering a job — the documented, recommended thing to do — changes which scheduler's collaborators it sees. It works in every single-scheduler test and surprises in production with two tenants.
Options
- Refuse the ambiguity at startup. Validate registered job types' constructors: a parameter of a per-scheduler type (
ISchedulerFactory, IScheduler, IJobStore, IThreadPool, IOptions<QuartzSchedulerOptions>, …) fails ValidateOnStart with a message pointing at IJobExecutionContext.Scheduler / IJobExecutionContextAccessor. Cheap, honest, and it matches the guidance the docs give anyway. Cost: a job that genuinely wants to inject its scheduler cannot, which today only half works.
- Make registered jobs scheduler-aware. Have the job factory build registered job types through the scheduler-scoped provider as well. This is only coherent for scoped and transient registrations — a singleton job type shared by two schedulers can hold only one set of parts — so it would need to refuse or special-case singletons, and it changes the lifetime story
AddJobType<T>(ServiceLifetime) just settled.
- Document only. The alpha.2 docs pass says both halves on
multi-tenancy.md. That is the floor, not the answer.
I lean to (1) for 4.0: it removes a trap without adding a mechanism, and (2) can still be built later if a real case for injecting a scheduler into a job appears.
Found by the multi-tenancy audit for alpha.2; the documentation is being corrected to describe it, this issue is about whether to change it.
A job is handed different collaborators depending on whether the container holds a registration for its type:
AddJob<T>registers the job type withTryAddScoped(src/Quartz/Configuration/QuartzBuilderExtensions.cs, theTryAddScoped(jobType)helper).MicrosoftDependencyInjectionJobFactorythen resolves it from the fire scope, so the container activates it — and a constructor that takesISchedulerFactory,IJobStore,IThreadPoolorIOptions<QuartzSchedulerOptions>receives the default scheduler's (or fails container validation in a container with only named schedulers).JobActivatorCacheon the scheduler-scoped provider, and the same constructor receives its own scheduler's parts.So registering a job — the documented, recommended thing to do — changes which scheduler's collaborators it sees. It works in every single-scheduler test and surprises in production with two tenants.
Options
ISchedulerFactory,IScheduler,IJobStore,IThreadPool,IOptions<QuartzSchedulerOptions>, …) failsValidateOnStartwith a message pointing atIJobExecutionContext.Scheduler/IJobExecutionContextAccessor. Cheap, honest, and it matches the guidance the docs give anyway. Cost: a job that genuinely wants to inject its scheduler cannot, which today only half works.AddJobType<T>(ServiceLifetime)just settled.multi-tenancy.md. That is the floor, not the answer.I lean to (1) for 4.0: it removes a trap without adding a mechanism, and (2) can still be built later if a real case for injecting a scheduler into a job appears.