-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
StdSchedulerFactory and derived factories are not threadsafe #1587
Milestone
Comments
Would you like to offer a PR against the 3.x branch which would improve things? |
Done. In the interim if anyone is waiting for a release you could also e.g. public class ThreadsafeSchedulerFactory : ISchedulerFactory {
private readonly ISchedulerFactory _schedulerFactory;
private readonly SemaphoreSlim _semaphore;
private bool _initialized;
public ThreadsafeSchedulerFactory(IServiceProvider serviceProvider) {
// ServiceCollectionSchedulerFactory is internal, so we need to instantiate it via reflection
const string implementationTypeName = "Quartz.ServiceCollectionSchedulerFactory";
var implementationType = typeof(Quartz.ServiceCollectionExtensions).Assembly.GetType(implementationTypeName);
if(implementationType is null)
throw new Exception($"Unable to resolve type {implementationTypeName}.");
_schedulerFactory = (ISchedulerFactory)ActivatorUtilities.CreateInstance(serviceProvider, implementationType);
_semaphore = new SemaphoreSlim(1, 1);
_initialized = false;
}
public async Task<IScheduler> GetScheduler(CancellationToken cancellationToken = default) {
if(_initialized)
return await _schedulerFactory.GetScheduler(cancellationToken);
await _semaphore.WaitAsync(cancellationToken);
try {
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
_initialized = true;
return scheduler;
} finally {
_semaphore.Release();
}
}
// These methods just forward to SchedulerRepostiory.Instance and probably should not be used
public Task<IReadOnlyList<IScheduler>> GetAllSchedulers(CancellationToken cancellationToken = default) =>
_schedulerFactory.GetAllSchedulers(cancellationToken);
public Task<IScheduler> GetScheduler(string schedName, CancellationToken cancellationToken = default) =>
_schedulerFactory.GetScheduler(schedName, cancellationToken);
} and then: services.Replace(ServiceDescriptor.Singleton<ISchedulerFactory, ThreadsafeSchedulerFactory>()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
GetScheduler(CancellationToken)
usesSchedulerRepository.Instance
to check if the the scheduler already exists. If it does not, then thenInstantiate()
later adds the scheduler to the repository singleton:quartznet/src/Quartz/Impl/StdSchedulerFactory.cs
Lines 1099 to 1113 in 7d0dc1b
In the intervening time another thread may call GetScheduler, resulting in an exception from SchedulerRepository.Bind:
quartznet/src/Quartz/Impl/SchedulerRepository.cs
Lines 53 to 64 in 7d0dc1b
This is problematic under dependency injection where the ISchedulerFactory seems to be the point of access for the scheduler. I see #1265, however this does not address the issue as the race occurs within StdSchedulerFactory prior to acquiring the semaphore.
Also related: #1453.
The text was updated successfully, but these errors were encountered: