using Elsa.Common.Multitenancy; using Elsa.Hangfire.Extensions; using Elsa.Hangfire.Jobs; using Elsa.Scheduling; using Hangfire; using Hangfire.Storage; namespace Elsa.Hangfire.Services; /// /// An implementation of that uses Hangfire. /// public class HangfireWorkflowScheduler( IBackgroundJobClient backgroundJobClient, IRecurringJobManager recurringJobManager, ITenantAccessor tenantAccessor, JobStorage jobStorage) : IWorkflowScheduler { /// public ValueTask ScheduleAtAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default) { var tenantId = tenantAccessor.Tenant?.Id; backgroundJobClient.Schedule(job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), at); return ValueTask.CompletedTask; } /// public ValueTask ScheduleAtAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default) { var tenantId = tenantAccessor.Tenant?.Id; backgroundJobClient.Schedule(job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), at); return ValueTask.CompletedTask; } /// public async ValueTask ScheduleRecurringAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, DateTimeOffset startAt, TimeSpan interval, CancellationToken cancellationToken = default) { await ScheduleCronAsync(taskName, request, interval.ToCronExpression(), cancellationToken); } /// public async ValueTask ScheduleRecurringAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, DateTimeOffset startAt, TimeSpan interval, CancellationToken cancellationToken = default) { await ScheduleCronAsync(taskName, request, interval.ToCronExpression(), cancellationToken); } /// public ValueTask ScheduleCronAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default) { var tenantId = tenantAccessor.Tenant?.Id; recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), cronExpression); return ValueTask.CompletedTask; } /// public ValueTask ScheduleCronAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default) { var tenantId = tenantAccessor.Tenant?.Id; recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), cronExpression); return ValueTask.CompletedTask; } /// public ValueTask UnscheduleAsync(string taskName, CancellationToken cancellationToken = default) { DeleteJobByTaskName(taskName); return ValueTask.CompletedTask; } private void DeleteJobByTaskName(string taskName) { var scheduledJobIds = GetScheduledJobIds(taskName); foreach (var jobId in scheduledJobIds) backgroundJobClient.Delete(jobId); var queuedJobsIds = GetQueuedJobIds(taskName); foreach (var jobId in queuedJobsIds) backgroundJobClient.Delete(jobId); var recurringJobIds = GetRecurringJobIds(taskName); foreach (var jobId in recurringJobIds) recurringJobManager.RemoveIfExists(jobId); } private IEnumerable GetScheduledJobIds(string taskName) { return jobStorage.EnumerateScheduledJobs(taskName) .Select(x => x.Key) .Distinct() .ToList(); } private IEnumerable GetQueuedJobIds(string taskName) { return jobStorage.EnumerateQueuedJobs("default", taskName) .Select(x => x.Key) .Distinct() .ToList(); } private IEnumerable GetRecurringJobIds(string taskName) { using var connection = jobStorage.GetConnection(); var jobs = connection.GetRecurringJobs().Where(x => x.Job.Type == typeof(TJob) && (string)x.Job.Args[0] == taskName); return jobs.Select(x => x.Id).Distinct().ToList(); } }