| | | 1 | | using System.Reflection; |
| | | 2 | | using Elsa.Common.Helpers; |
| | | 3 | | using Elsa.Common.RecurringTasks; |
| | | 4 | | using Microsoft.Extensions.DependencyInjection; |
| | | 5 | | using Microsoft.Extensions.Logging; |
| | | 6 | | |
| | | 7 | | namespace Elsa.Common.Multitenancy.EventHandlers; |
| | | 8 | | |
| | | 9 | | /// <summary> |
| | | 10 | | /// Manages the lifecycle of startup, background, and recurring tasks for tenants. |
| | | 11 | | /// Executes tasks in the proper sequence: startup tasks first, then background tasks, then recurring tasks. |
| | | 12 | | /// </summary> |
| | 7 | 13 | | public class TenantTaskManager(RecurringTaskScheduleManager scheduleManager, ILogger<TenantTaskManager> logger) : ITenan |
| | | 14 | | { |
| | 7 | 15 | | private readonly ICollection<Task> _runningBackgroundTasks = new List<Task>(); |
| | 7 | 16 | | private readonly ICollection<ScheduledTimer> _scheduledTimers = new List<ScheduledTimer>(); |
| | | 17 | | private CancellationTokenSource _cancellationTokenSource = null!; |
| | | 18 | | |
| | | 19 | | public async Task TenantActivatedAsync(TenantActivatedEventArgs args) |
| | | 20 | | { |
| | 7 | 21 | | var cancellationToken = args.CancellationToken; |
| | 7 | 22 | | var tenantScope = args.TenantScope; |
| | 7 | 23 | | var taskExecutor = tenantScope.ServiceProvider.GetRequiredService<ITaskExecutor>(); |
| | | 24 | | |
| | 7 | 25 | | _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); |
| | | 26 | | |
| | | 27 | | // Step 1: Run startup tasks (with dependency ordering) |
| | 7 | 28 | | await RunStartupTasksAsync(tenantScope, taskExecutor, cancellationToken); |
| | | 29 | | |
| | | 30 | | // Step 2: Run background tasks |
| | 7 | 31 | | await RunBackgroundTasksAsync(tenantScope, taskExecutor, cancellationToken); |
| | | 32 | | |
| | | 33 | | // Step 3: Start recurring tasks |
| | 7 | 34 | | await StartRecurringTasksAsync(tenantScope, taskExecutor, cancellationToken); |
| | 7 | 35 | | } |
| | | 36 | | |
| | | 37 | | public async Task TenantDeactivatedAsync(TenantDeactivatedEventArgs args) |
| | | 38 | | { |
| | 3 | 39 | | var tenantScope = args.TenantScope; |
| | | 40 | | |
| | | 41 | | // Cancel all running tasks |
| | 3 | 42 | | _cancellationTokenSource.Cancel(); |
| | | 43 | | |
| | | 44 | | // Wait for background tasks to complete (with cancellation they should finish quickly) |
| | 3 | 45 | | if (_runningBackgroundTasks.Any()) |
| | | 46 | | { |
| | | 47 | | try |
| | | 48 | | { |
| | 3 | 49 | | await Task.WhenAll(_runningBackgroundTasks); |
| | 3 | 50 | | } |
| | 0 | 51 | | catch (OperationCanceledException) |
| | | 52 | | { |
| | | 53 | | // Expected when tasks are cancelled |
| | 0 | 54 | | } |
| | 3 | 55 | | _runningBackgroundTasks.Clear(); |
| | | 56 | | } |
| | | 57 | | |
| | | 58 | | // Stop all recurring task timers |
| | 24 | 59 | | foreach (var timer in _scheduledTimers) |
| | 9 | 60 | | await timer.DisposeAsync(); |
| | 3 | 61 | | _scheduledTimers.Clear(); |
| | | 62 | | |
| | | 63 | | // Stop recurring tasks |
| | 3 | 64 | | var recurringTasks = tenantScope.ServiceProvider.GetServices<IRecurringTask>(); |
| | 24 | 65 | | foreach (var task in recurringTasks) |
| | 9 | 66 | | await task.StopAsync(args.CancellationToken); |
| | 3 | 67 | | } |
| | | 68 | | |
| | | 69 | | private async Task RunStartupTasksAsync(ITenantScope tenantScope, ITaskExecutor taskExecutor, CancellationToken canc |
| | | 70 | | { |
| | 7 | 71 | | var startupTasks = tenantScope.ServiceProvider.GetServices<IStartupTask>() |
| | 63 | 72 | | .OrderBy(x => x.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0f) |
| | 7 | 73 | | .ToList(); |
| | | 74 | | |
| | | 75 | | // First apply OrderAttribute to determine a base order, then perform topological sorting. |
| | | 76 | | // The topological sort is the final ordering step to ensure dependency constraints are respected. |
| | 7 | 77 | | var sortedTasks = TopologicalTaskSorter.Sort(startupTasks).ToList(); |
| | 140 | 78 | | foreach (var task in sortedTasks) |
| | 63 | 79 | | await taskExecutor.ExecuteTaskAsync(task, cancellationToken); |
| | 7 | 80 | | } |
| | | 81 | | |
| | | 82 | | private Task RunBackgroundTasksAsync(ITenantScope tenantScope, ITaskExecutor taskExecutor, CancellationToken cancell |
| | | 83 | | { |
| | 7 | 84 | | var backgroundTasks = tenantScope.ServiceProvider.GetServices<IBackgroundTask>(); |
| | 7 | 85 | | var backgroundTaskStarter = tenantScope.ServiceProvider.GetRequiredService<IBackgroundTaskStarter>(); |
| | | 86 | | |
| | 28 | 87 | | foreach (var backgroundTask in backgroundTasks) |
| | | 88 | | { |
| | 7 | 89 | | var task = backgroundTaskStarter |
| | 7 | 90 | | .StartAsync(backgroundTask, _cancellationTokenSource.Token) |
| | 7 | 91 | | .ContinueWith(t => taskExecutor.ExecuteTaskAsync(backgroundTask, _cancellationTokenSource.Token), |
| | 7 | 92 | | cancellationToken, |
| | 7 | 93 | | TaskContinuationOptions.RunContinuationsAsynchronously, |
| | 7 | 94 | | TaskScheduler.Default) |
| | 7 | 95 | | .Unwrap(); |
| | | 96 | | |
| | 7 | 97 | | if (!task.IsCompleted) |
| | 7 | 98 | | _runningBackgroundTasks.Add(task); |
| | | 99 | | } |
| | | 100 | | |
| | 7 | 101 | | return Task.CompletedTask; |
| | | 102 | | } |
| | | 103 | | |
| | | 104 | | private async Task StartRecurringTasksAsync(ITenantScope tenantScope, ITaskExecutor taskExecutor, CancellationToken |
| | | 105 | | { |
| | 7 | 106 | | var recurringTasks = tenantScope.ServiceProvider.GetServices<IRecurringTask>().ToList(); |
| | | 107 | | |
| | 56 | 108 | | foreach (var task in recurringTasks) |
| | | 109 | | { |
| | 21 | 110 | | var schedule = scheduleManager.GetScheduleFor(task.GetType()); |
| | 21 | 111 | | var timer = schedule.CreateTimer(async () => |
| | 21 | 112 | | { |
| | 21 | 113 | | try |
| | 21 | 114 | | { |
| | 16 | 115 | | await taskExecutor.ExecuteTaskAsync(task, _cancellationTokenSource.Token); |
| | 16 | 116 | | } |
| | 0 | 117 | | catch (OperationCanceledException e) |
| | 21 | 118 | | { |
| | 0 | 119 | | logger.LogInformation(e, "Recurring task {TaskType} was cancelled", task.GetType().Name); |
| | 0 | 120 | | } |
| | 37 | 121 | | }); |
| | | 122 | | |
| | 21 | 123 | | _scheduledTimers.Add(timer); |
| | 21 | 124 | | await task.StartAsync(cancellationToken); |
| | | 125 | | } |
| | 7 | 126 | | } |
| | | 127 | | } |