diff --git a/src/modules/scheduling/Elsa.Scheduling.Quartz/Extensions/JobExecutionExtensions.cs b/src/modules/scheduling/Elsa.Scheduling.Quartz/Extensions/JobExecutionExtensions.cs
index c0b3f099..4f9aa497 100644
--- a/src/modules/scheduling/Elsa.Scheduling.Quartz/Extensions/JobExecutionExtensions.cs
+++ b/src/modules/scheduling/Elsa.Scheduling.Quartz/Extensions/JobExecutionExtensions.cs
@@ -1,4 +1,5 @@
using Elsa.Common.Multitenancy;
+using Elsa.Scheduling.Quartz.Jobs;
using Quartz;
namespace Elsa.Scheduling.Quartz;
@@ -18,4 +19,27 @@ internal static class JobExecutionExtensions
return await tenantFinder.FindByIdAsync(tenantId, context.CancellationToken);
}
+
+ ///
+ /// Executes delete job if allowed
+ ///
+ /// The Quartz job execution context.
+ /// The Quartz job key.
+ /// The cancellation token.
+ public static async Task DeleteJob(this IJobExecutionContext context, JobKey jobKey, CancellationToken cancellationToken = default)
+ {
+ if (IsJobAllowedToBeDeleted(jobKey.Name))
+ await context.Scheduler.DeleteJob(jobKey, cancellationToken);
+ }
+
+ ///
+ /// Checks if the job is allowed to be deleted by name
+ ///
+ /// Name of the job to check
+ /// False if the job is one of the required ones, otherwise true
+ private static bool IsJobAllowedToBeDeleted(string jobName)
+ {
+ return jobName != nameof(ResumeWorkflowJob)
+ && jobName != nameof(RunWorkflowJob);
+ }
}
\ No newline at end of file
diff --git a/src/modules/scheduling/Elsa.Scheduling.Quartz/Jobs/ResumeWorkflowJob.cs b/src/modules/scheduling/Elsa.Scheduling.Quartz/Jobs/ResumeWorkflowJob.cs
index 139fabfd..afb93bd2 100644
--- a/src/modules/scheduling/Elsa.Scheduling.Quartz/Jobs/ResumeWorkflowJob.cs
+++ b/src/modules/scheduling/Elsa.Scheduling.Quartz/Jobs/ResumeWorkflowJob.cs
@@ -57,7 +57,7 @@ public async Task Execute(IJobExecutionContext context)
catch (Exception e)
{
logger.LogError(e, "An error occurred while resuming workflow instance {WorkflowInstanceId}", workflowInstanceId);
- await context.Scheduler.DeleteJob(context.JobDetail.Key, cancellationToken);
+ await context.DeleteJob(context.JobDetail.Key, cancellationToken);
}
}
}
diff --git a/src/modules/scheduling/Elsa.Scheduling.Quartz/Jobs/RunWorkflowJob.cs b/src/modules/scheduling/Elsa.Scheduling.Quartz/Jobs/RunWorkflowJob.cs
index 3db56cf2..60ab3056 100644
--- a/src/modules/scheduling/Elsa.Scheduling.Quartz/Jobs/RunWorkflowJob.cs
+++ b/src/modules/scheduling/Elsa.Scheduling.Quartz/Jobs/RunWorkflowJob.cs
@@ -58,7 +58,7 @@ public async Task Execute(IJobExecutionContext context)
catch (WorkflowGraphNotFoundException e)
{
logger.LogWarning(e, "Could not find workflow graph for workflow definition handle {WorkflowDefinitionHandle}", startRequest.WorkflowDefinitionHandle);
- await context.Scheduler.DeleteJob(context.JobDetail.Key, cancellationToken);
+ await context.Scheduler.UnscheduleJob(context.Trigger.Key, cancellationToken);
}
catch (Exception e) when (transientExceptionDetector.IsTransient(e))
{
@@ -68,7 +68,7 @@ public async Task Execute(IJobExecutionContext context)
catch (Exception e)
{
logger.LogError(e, "An error occurred while starting workflow {WorkflowDefinitionHandle} with correlation ID {CorrelationId}", startRequest.WorkflowDefinitionHandle, startRequest.CorrelationId);
- await context.Scheduler.DeleteJob(context.JobDetail.Key, cancellationToken);
+ await context.DeleteJob(context.JobDetail.Key, cancellationToken);
}
}
}
diff --git a/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Helpers/QuartzJobTestHelper.cs b/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Helpers/QuartzJobTestHelper.cs
index 346d42be..7828315a 100644
--- a/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Helpers/QuartzJobTestHelper.cs
+++ b/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Helpers/QuartzJobTestHelper.cs
@@ -20,10 +20,11 @@ public static class QuartzJobTestHelper
/// Creates a mock job execution context with the specified job data.
///
public static (IJobExecutionContext Context, Mock Scheduler) CreateJobExecutionContext(
- IDictionary jobData)
+ IDictionary jobData,
+ string? jobKeyName = null)
{
var jobDataMap = new JobDataMap(jobData);
- var jobKey = new JobKey("test-job");
+ var jobKey = new JobKey(jobKeyName ?? "test-job");
var triggerKey = new TriggerKey("test-trigger");
var jobDetail = new Mock();
@@ -130,6 +131,18 @@ public void VerifyRescheduled() =>
///
public void VerifyDeleted() =>
scheduler.Verify(s => s.DeleteJob(It.IsAny(), It.IsAny()), Times.Once);
+
+ ///
+ /// Verifies that the scheduler did not delete a job.
+ ///
+ public void VerifyNotDeleted() =>
+ scheduler.Verify(s => s.DeleteJob(It.IsAny(), It.IsAny()), Times.Never);
+
+ ///
+ /// Verifies that the scheduler unscheduled a job exactly once.
+ ///
+ public void VerifyUnscheduled() =>
+ scheduler.Verify(s => s.UnscheduleJob(It.IsAny(), It.IsAny()), Times.Once);
}
///
diff --git a/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Jobs/ResumeWorkflowJobTests.cs b/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Jobs/ResumeWorkflowJobTests.cs
index d28d93d1..490ed75e 100644
--- a/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Jobs/ResumeWorkflowJobTests.cs
+++ b/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Jobs/ResumeWorkflowJobTests.cs
@@ -65,17 +65,22 @@ public async Task Execute_TransientException_ReschedulesJob(Type exceptionType,
}
[Theory]
- [InlineData(typeof(InvalidOperationException))]
- [InlineData(typeof(ArgumentException))]
- public async Task Execute_NonTransientException_DeletesJob(Type exceptionType)
+ [InlineData(typeof(InvalidOperationException), null)]
+ [InlineData(typeof(ArgumentException), null)]
+ [InlineData(typeof(InvalidOperationException), "ResumeWorkflowJob")]
+ [InlineData(typeof(ArgumentException), "ResumeWorkflowJob")]
+ public async Task Execute_NonTransientException_DeletesJob(Type exceptionType, string? jobKeyName)
{
- var (context, scheduler) = CreateJobExecutionContext();
+ var (context, scheduler) = CreateJobExecutionContext(jobKeyName: jobKeyName);
_transientDetector.SetupIsTransient(false);
_workflowRuntime.SetupCreateClientThrows((Exception)Activator.CreateInstance(exceptionType)!);
await _job.Execute(context);
- scheduler.VerifyDeleted();
+ if (string.IsNullOrEmpty(jobKeyName))
+ scheduler.VerifyDeleted();
+ else
+ scheduler.VerifyNotDeleted();
}
[Fact]
@@ -121,7 +126,8 @@ public async Task Execute_WithActivityHandle_DeserializesCorrectly()
Assert.Equal("activity-123", capturedRequest.ActivityHandle?.ActivityId);
}
- private static (IJobExecutionContext, Mock) CreateJobExecutionContext(string? activityHandle = null)
+ private static (IJobExecutionContext, Mock) CreateJobExecutionContext(string? activityHandle = null,
+ string? jobKeyName = null)
{
var jobData = new Dictionary
{
@@ -132,6 +138,6 @@ private static (IJobExecutionContext, Mock) CreateJobExecutionC
if (activityHandle != null)
jobData.Add("ActivityHandle", activityHandle);
- return QuartzJobTestHelper.CreateJobExecutionContext(jobData);
+ return QuartzJobTestHelper.CreateJobExecutionContext(jobData, jobKeyName);
}
}
diff --git a/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Jobs/RunWorkflowJobTests.cs b/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Jobs/RunWorkflowJobTests.cs
index 6ab6a553..9c0562b3 100644
--- a/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Jobs/RunWorkflowJobTests.cs
+++ b/test/modules/scheduling/Elsa.Scheduling.Quartz.UnitTests/Jobs/RunWorkflowJobTests.cs
@@ -65,7 +65,7 @@ public async Task Execute_WorkflowGraphNotFound_DeletesJob()
await _job.Execute(context);
- scheduler.VerifyDeleted();
+ scheduler.VerifyUnscheduled();
}
[Theory]
@@ -83,17 +83,22 @@ public async Task Execute_TransientException_ReschedulesJob(Type exceptionType,
}
[Theory]
- [InlineData(typeof(InvalidOperationException))]
- [InlineData(typeof(ArgumentException))]
- public async Task Execute_NonTransientException_DeletesJob(Type exceptionType)
+ [InlineData(typeof(InvalidOperationException), null)]
+ [InlineData(typeof(ArgumentException), null)]
+ [InlineData(typeof(InvalidOperationException), "RunWorkflowJob")]
+ [InlineData(typeof(ArgumentException), "RunWorkflowJob")]
+ public async Task Execute_NonTransientException_DeletesJob(Type exceptionType, string? jobKeyName)
{
- var (context, scheduler) = CreateJobExecutionContext();
+ var (context, scheduler) = CreateJobExecutionContext(jobKeyName);
_transientDetector.SetupIsTransient(false);
_workflowStarter.SetupStartWorkflowThrows((Exception)Activator.CreateInstance(exceptionType)!);
await _job.Execute(context);
- scheduler.VerifyDeleted();
+ if (string.IsNullOrEmpty(jobKeyName))
+ scheduler.VerifyDeleted();
+ else
+ scheduler.VerifyNotDeleted();
}
[Fact]
@@ -111,11 +116,11 @@ public async Task Execute_UsesCorrectWorkflowDefinitionHandle()
Assert.Equal("workflow-def-123", capturedRequest.WorkflowDefinitionHandle.DefinitionVersionId);
}
- private static (IJobExecutionContext, Mock) CreateJobExecutionContext() =>
+ private static (IJobExecutionContext, Mock) CreateJobExecutionContext(string? jobKeyName = null) =>
QuartzJobTestHelper.CreateJobExecutionContext(new Dictionary
{
{ "DefinitionVersionId", "workflow-def-123" },
{ "CorrelationId", "corr-123" },
{ "TriggerActivityId", "trigger-123" }
- });
+ }, jobKeyName);
}