From ac48e54ab85e14a18c4f2c24f96bcc7a7a365b44 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 5 Dec 2024 19:25:16 +0100 Subject: [PATCH] Introduce job unscheduling functionality Add a new method `UnscheduledAsync` to unschedule jobs across various components, including the job queue, background activity scheduler, and Hangfire integration. This enhancement provides a more robust way to remove jobs from scheduling, complementing the existing cancellation functionality. This fixes an issue where a background activity execution job that resumed a workflow, which in turn would remove any associated bookmarks, which in turn would cancel the background job while that job is still executing and has to e.g. persist changes made to the DB. --- src/common/Elsa.Mediator/Contracts/IJobQueue.cs | 7 +++++++ src/common/Elsa.Mediator/Services/JobQueue.cs | 10 ++++++++++ .../Services/HangfireBackgroundActivityScheduler.cs | 6 ++++++ .../Contracts/IBackgroundActivityScheduler.cs | 5 +++++ .../Handlers/CancelBackgroundActivities.cs | 2 +- .../Services/LocalBackgroundActivityScheduler.cs | 6 ++++++ 6 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/common/Elsa.Mediator/Contracts/IJobQueue.cs b/src/common/Elsa.Mediator/Contracts/IJobQueue.cs index 802de6a22..c29fef950 100644 --- a/src/common/Elsa.Mediator/Contracts/IJobQueue.cs +++ b/src/common/Elsa.Mediator/Contracts/IJobQueue.cs @@ -23,6 +23,13 @@ public interface IJobQueue /// The ID of the job. string Enqueue(Func job); + /// + /// Dequeues a job. + /// + /// The ID of the job to dequeue. + /// true if the job was dequeued; otherwise, false. + bool Dequeue(string jobId); + /// /// Cancels a job. /// diff --git a/src/common/Elsa.Mediator/Services/JobQueue.cs b/src/common/Elsa.Mediator/Services/JobQueue.cs index 4ef6623f8..738e9d804 100644 --- a/src/common/Elsa.Mediator/Services/JobQueue.cs +++ b/src/common/Elsa.Mediator/Services/JobQueue.cs @@ -40,6 +40,16 @@ public class JobQueue(IJobsChannel jobsChannel, ILogger logger) : IJob return jobItem.JobId; } + /// + public bool Dequeue(string jobId) + { + if (!_pendingItems.TryRemove(jobId, out _)) + if (!_scheduledItems.TryRemove(jobId, out _)) + return false; + + return true; + } + /// public bool Cancel(string jobId) { diff --git a/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs b/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs index e2a23ae0c..798d6cfd2 100644 --- a/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs +++ b/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs @@ -32,6 +32,12 @@ public class HangfireBackgroundActivityScheduler(IBackgroundJobClient background return Task.FromResult(jobId); } + public Task UnscheduledAsync(string jobId, CancellationToken cancellationToken = default) + { + backgroundJobClient.Delete(jobId); + return Task.CompletedTask; + } + /// public Task CancelAsync(string jobId, CancellationToken cancellationToken = default) { diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IBackgroundActivityScheduler.cs b/src/modules/Elsa.Workflows.Runtime/Contracts/IBackgroundActivityScheduler.cs index 844bfba8c..f9795b0e8 100644 --- a/src/modules/Elsa.Workflows.Runtime/Contracts/IBackgroundActivityScheduler.cs +++ b/src/modules/Elsa.Workflows.Runtime/Contracts/IBackgroundActivityScheduler.cs @@ -26,6 +26,11 @@ public interface IBackgroundActivityScheduler /// The cancellation token. /// A handle representing the asynchronous invocation. Task ScheduleAsync(ScheduledBackgroundActivity scheduledBackgroundActivity, CancellationToken cancellationToken = default); + + /// + /// Removes the specified job from the schedule. + /// + Task UnscheduledAsync(string jobId, CancellationToken cancellationToken = default); /// /// Cancels the specified job. diff --git a/src/modules/Elsa.Workflows.Runtime/Handlers/CancelBackgroundActivities.cs b/src/modules/Elsa.Workflows.Runtime/Handlers/CancelBackgroundActivities.cs index aa85e8140..efb2e63d1 100644 --- a/src/modules/Elsa.Workflows.Runtime/Handlers/CancelBackgroundActivities.cs +++ b/src/modules/Elsa.Workflows.Runtime/Handlers/CancelBackgroundActivities.cs @@ -22,7 +22,7 @@ public class CancelBackgroundActivities(IBackgroundActivityScheduler backgroundA { var payload = removedBookmark.GetPayload(); if (payload.JobId != null) - await backgroundActivityScheduler.CancelAsync(payload.JobId, cancellationToken); + await backgroundActivityScheduler.UnscheduledAsync(payload.JobId, cancellationToken); } } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/LocalBackgroundActivityScheduler.cs b/src/modules/Elsa.Workflows.Runtime/Services/LocalBackgroundActivityScheduler.cs index 291b89431..219af276e 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/LocalBackgroundActivityScheduler.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/LocalBackgroundActivityScheduler.cs @@ -27,6 +27,12 @@ public class LocalBackgroundActivityScheduler(IJobQueue jobQueue, IServiceScopeF return Task.FromResult(jobId); } + public Task UnscheduledAsync(string jobId, CancellationToken cancellationToken = default) + { + jobQueue.Dequeue(jobId); + return Task.CompletedTask; + } + /// public Task CancelAsync(string jobId, CancellationToken cancellationToken = default) {