Fix potential race condition to Quartz scheduler causing duplicate scheduler error

This commit is contained in:
Sipke Schoorstra 2021-06-04 14:52:46 +02:00
parent 1b9e8078e3
commit b4f83f43aa
4 changed files with 52 additions and 15 deletions

View file

@ -28,6 +28,7 @@ namespace Elsa
Action<QuartzHostedServiceOptions>? configureQuartzHostedService = default)
{
timersOptions.Services
.AddSingleton<QuartzSchedulerProvider>()
.AddSingleton<IWorkflowDefinitionScheduler, QuartzWorkflowDefinitionScheduler>()
.AddSingleton<IWorkflowInstanceScheduler, QuartzWorkflowInstanceScheduler>()
.AddSingleton<ICrontabParser, QuartzCrontabParser>()

View file

@ -0,0 +1,36 @@
using System.Threading;
using System.Threading.Tasks;
using Quartz;
namespace Elsa.Activities.Temporal.Quartz.Services
{
public class QuartzSchedulerProvider
{
private readonly ISchedulerFactory _schedulerFactory;
private readonly SemaphoreSlim _semaphore = new(1);
private IScheduler? _scheduler;
public QuartzSchedulerProvider(ISchedulerFactory schedulerFactory) => _schedulerFactory = schedulerFactory;
public async Task<IScheduler> GetSchedulerAsync(CancellationToken cancellationToken)
{
if (_scheduler != null)
return _scheduler;
await _semaphore.WaitAsync(cancellationToken);
try
{
if (_scheduler != null)
return _scheduler;
_scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
return _scheduler!;
}
finally
{
_semaphore.Release();
}
}
}
}

View file

@ -12,13 +12,13 @@ namespace Elsa.Activities.Temporal.Quartz.Services
public class QuartzWorkflowDefinitionScheduler : IWorkflowDefinitionScheduler
{
private static readonly string RunWorkflowJobKey = nameof(RunQuartzWorkflowDefinitionJob);
private readonly ISchedulerFactory _schedulerFactory;
private readonly QuartzSchedulerProvider _schedulerProvider;
private readonly ILogger _logger;
private readonly SemaphoreSlim _semaphore = new(1);
public QuartzWorkflowDefinitionScheduler(ISchedulerFactory schedulerFactory, ILogger<QuartzWorkflowDefinitionScheduler> logger)
public QuartzWorkflowDefinitionScheduler(QuartzSchedulerProvider schedulerProvider, ILogger<QuartzWorkflowDefinitionScheduler> logger)
{
_schedulerFactory = schedulerFactory;
_schedulerProvider = schedulerProvider;
_logger = logger;
}
@ -41,7 +41,7 @@ namespace Elsa.Activities.Temporal.Quartz.Services
public async Task UnscheduleAsync(string workflowDefinitionId, string activityId, CancellationToken cancellationToken)
{
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
var scheduler = await _schedulerProvider.GetSchedulerAsync(cancellationToken);
var trigger = CreateTriggerKey(workflowDefinitionId, activityId);
var existingTrigger = await scheduler.GetTrigger(trigger, cancellationToken);
@ -51,7 +51,7 @@ namespace Elsa.Activities.Temporal.Quartz.Services
public async Task UnscheduleAsync(string workflowDefinitionId, CancellationToken cancellationToken)
{
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
var scheduler = await _schedulerProvider.GetSchedulerAsync(cancellationToken);
var groupName = CreateTriggerGroupKey(workflowDefinitionId);
var existingTriggers = await scheduler.GetTriggerKeys(GroupMatcher<TriggerKey>.GroupEquals(groupName), cancellationToken);
@ -61,18 +61,18 @@ namespace Elsa.Activities.Temporal.Quartz.Services
public async Task UnscheduleAllAsync(CancellationToken cancellationToken)
{
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
var scheduler = await _schedulerProvider.GetSchedulerAsync(cancellationToken);
var jobKeys = await scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupStartsWith("workflow-definition"), cancellationToken);
await scheduler.DeleteJobs(jobKeys, cancellationToken);
}
private async Task ScheduleJob(ITrigger trigger, CancellationToken cancellationToken)
{
var scheduler = await _schedulerProvider.GetSchedulerAsync(cancellationToken);
await _semaphore.WaitAsync(cancellationToken);
try
{
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
var existingTrigger = await scheduler.GetTrigger(trigger.Key, cancellationToken);
// For workflow definitions we only schedule the job if one doesn't exist already because another node may have created it beforehand.

View file

@ -12,13 +12,13 @@ namespace Elsa.Activities.Temporal.Quartz.Services
public class QuartzWorkflowInstanceScheduler : IWorkflowInstanceScheduler
{
private static readonly string RunWorkflowJobKey = nameof(RunQuartzWorkflowInstanceJob);
private readonly ISchedulerFactory _schedulerFactory;
private readonly QuartzSchedulerProvider _schedulerProvider;
private readonly ILogger _logger;
private readonly SemaphoreSlim _semaphore = new(1);
public QuartzWorkflowInstanceScheduler(ISchedulerFactory schedulerFactory, ILogger<QuartzWorkflowInstanceScheduler> logger)
public QuartzWorkflowInstanceScheduler(QuartzSchedulerProvider schedulerProvider, ILogger<QuartzWorkflowInstanceScheduler> logger)
{
_schedulerFactory = schedulerFactory;
_schedulerProvider = schedulerProvider;
_logger = logger;
}
@ -41,7 +41,7 @@ namespace Elsa.Activities.Temporal.Quartz.Services
public async Task UnscheduleAsync(string workflowInstanceId, string activityId, CancellationToken cancellationToken)
{
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
var scheduler = await _schedulerProvider. GetSchedulerAsync(cancellationToken);
var trigger = CreateTriggerKey(workflowInstanceId, activityId);
var existingTrigger = await scheduler.GetTrigger(trigger, cancellationToken);
@ -51,7 +51,7 @@ namespace Elsa.Activities.Temporal.Quartz.Services
public async Task UnscheduleAsync(string workflowInstanceId, CancellationToken cancellationToken)
{
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
var scheduler = await _schedulerProvider. GetSchedulerAsync(cancellationToken);
var groupName = CreateTriggerGroupKey(workflowInstanceId);
var existingTriggers = await scheduler.GetTriggerKeys(GroupMatcher<TriggerKey>.GroupEquals(groupName), cancellationToken);
@ -61,18 +61,18 @@ namespace Elsa.Activities.Temporal.Quartz.Services
public async Task UnscheduleAllAsync(CancellationToken cancellationToken)
{
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
var scheduler = await _schedulerProvider. GetSchedulerAsync(cancellationToken);
var jobKeys = await scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupStartsWith("workflow-instance"), cancellationToken);
await scheduler.DeleteJobs(jobKeys, cancellationToken);
}
private async Task ScheduleJob(ITrigger trigger, CancellationToken cancellationToken)
{
var scheduler = await _schedulerProvider. GetSchedulerAsync(cancellationToken);
await _semaphore.WaitAsync(cancellationToken);
try
{
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
var existingTrigger = await scheduler.GetTrigger(trigger.Key, cancellationToken);
if (existingTrigger != null)
@ -102,7 +102,7 @@ namespace Elsa.Activities.Temporal.Quartz.Services
var groupName = CreateTriggerGroupKey(workflowInstanceId);
return new TriggerKey($"activity:{activityId}", groupName);
}
private string CreateTriggerGroupKey(string workflowInstanceId) => $"workflow-instance:{workflowInstanceId}";
}
}