< Summary

Information
Class: Elsa.Common.Multitenancy.EventHandlers.TenantTaskManager
Assembly: Elsa.Common
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Common/Multitenancy/EventHandlers/TenantTaskManager.cs
Line coverage
92%
Covered lines: 58
Uncovered lines: 5
Coverable lines: 63
Total lines: 127
Line coverage: 92%
Branch coverage
100%
Covered branches: 16
Total branches: 16
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
TenantActivatedAsync()100%11100%
TenantDeactivatedAsync()100%6686.66%
RunStartupTasksAsync()100%22100%
RunBackgroundTasksAsync(...)100%44100%
StartRecurringTasksAsync()100%22100%
<StartRecurringTasksAsync()100%1150%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Common/Multitenancy/EventHandlers/TenantTaskManager.cs

#LineLine coverage
 1using System.Reflection;
 2using Elsa.Common.Helpers;
 3using Elsa.Common.RecurringTasks;
 4using Microsoft.Extensions.DependencyInjection;
 5using Microsoft.Extensions.Logging;
 6
 7namespace 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>
 713public class TenantTaskManager(RecurringTaskScheduleManager scheduleManager, ILogger<TenantTaskManager> logger) : ITenan
 14{
 715    private readonly ICollection<Task> _runningBackgroundTasks = new List<Task>();
 716    private readonly ICollection<ScheduledTimer> _scheduledTimers = new List<ScheduledTimer>();
 17    private CancellationTokenSource _cancellationTokenSource = null!;
 18
 19    public async Task TenantActivatedAsync(TenantActivatedEventArgs args)
 20    {
 721        var cancellationToken = args.CancellationToken;
 722        var tenantScope = args.TenantScope;
 723        var taskExecutor = tenantScope.ServiceProvider.GetRequiredService<ITaskExecutor>();
 24
 725        _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 26
 27        // Step 1: Run startup tasks (with dependency ordering)
 728        await RunStartupTasksAsync(tenantScope, taskExecutor, cancellationToken);
 29
 30        // Step 2: Run background tasks
 731        await RunBackgroundTasksAsync(tenantScope, taskExecutor, cancellationToken);
 32
 33        // Step 3: Start recurring tasks
 734        await StartRecurringTasksAsync(tenantScope, taskExecutor, cancellationToken);
 735    }
 36
 37    public async Task TenantDeactivatedAsync(TenantDeactivatedEventArgs args)
 38    {
 339        var tenantScope = args.TenantScope;
 40
 41        // Cancel all running tasks
 342        _cancellationTokenSource.Cancel();
 43
 44        // Wait for background tasks to complete (with cancellation they should finish quickly)
 345        if (_runningBackgroundTasks.Any())
 46        {
 47            try
 48            {
 349                await Task.WhenAll(_runningBackgroundTasks);
 350            }
 051            catch (OperationCanceledException)
 52            {
 53                // Expected when tasks are cancelled
 054            }
 355            _runningBackgroundTasks.Clear();
 56        }
 57
 58        // Stop all recurring task timers
 2459        foreach (var timer in _scheduledTimers)
 960            await timer.DisposeAsync();
 361        _scheduledTimers.Clear();
 62
 63        // Stop recurring tasks
 364        var recurringTasks = tenantScope.ServiceProvider.GetServices<IRecurringTask>();
 2465        foreach (var task in recurringTasks)
 966            await task.StopAsync(args.CancellationToken);
 367    }
 68
 69    private async Task RunStartupTasksAsync(ITenantScope tenantScope, ITaskExecutor taskExecutor, CancellationToken canc
 70    {
 771        var startupTasks = tenantScope.ServiceProvider.GetServices<IStartupTask>()
 6372            .OrderBy(x => x.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0f)
 773            .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.
 777        var sortedTasks = TopologicalTaskSorter.Sort(startupTasks).ToList();
 14078        foreach (var task in sortedTasks)
 6379            await taskExecutor.ExecuteTaskAsync(task, cancellationToken);
 780    }
 81
 82    private Task RunBackgroundTasksAsync(ITenantScope tenantScope, ITaskExecutor taskExecutor, CancellationToken cancell
 83    {
 784        var backgroundTasks = tenantScope.ServiceProvider.GetServices<IBackgroundTask>();
 785        var backgroundTaskStarter = tenantScope.ServiceProvider.GetRequiredService<IBackgroundTaskStarter>();
 86
 2887        foreach (var backgroundTask in backgroundTasks)
 88        {
 789            var task = backgroundTaskStarter
 790                .StartAsync(backgroundTask, _cancellationTokenSource.Token)
 791                .ContinueWith(t => taskExecutor.ExecuteTaskAsync(backgroundTask, _cancellationTokenSource.Token),
 792                    cancellationToken,
 793                    TaskContinuationOptions.RunContinuationsAsynchronously,
 794                    TaskScheduler.Default)
 795                .Unwrap();
 96
 797            if (!task.IsCompleted)
 798                _runningBackgroundTasks.Add(task);
 99        }
 100
 7101        return Task.CompletedTask;
 102    }
 103
 104    private async Task StartRecurringTasksAsync(ITenantScope tenantScope, ITaskExecutor taskExecutor, CancellationToken 
 105    {
 7106        var recurringTasks = tenantScope.ServiceProvider.GetServices<IRecurringTask>().ToList();
 107
 56108        foreach (var task in recurringTasks)
 109        {
 21110            var schedule = scheduleManager.GetScheduleFor(task.GetType());
 21111            var timer = schedule.CreateTimer(async () =>
 21112            {
 21113                try
 21114                {
 16115                    await taskExecutor.ExecuteTaskAsync(task, _cancellationTokenSource.Token);
 16116                }
 0117                catch (OperationCanceledException e)
 21118                {
 0119                    logger.LogInformation(e, "Recurring task {TaskType} was cancelled", task.GetType().Name);
 0120                }
 37121            });
 122
 21123            _scheduledTimers.Add(timer);
 21124            await task.StartAsync(cancellationToken);
 125        }
 7126    }
 127}