< 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
88%
Covered lines: 60
Uncovered lines: 8
Coverable lines: 68
Total lines: 132
Line coverage: 88.2%
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%1133.33%

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    {
 2821        var cancellationToken = args.CancellationToken;
 2822        var tenantScope = args.TenantScope;
 2823        var taskExecutor = tenantScope.ServiceProvider.GetRequiredService<ITaskExecutor>();
 24
 2825        _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 26
 27        // Step 1: Run startup tasks (with dependency ordering)
 2828        await RunStartupTasksAsync(tenantScope, taskExecutor, cancellationToken);
 29
 30        // Step 2: Run background tasks
 2831        await RunBackgroundTasksAsync(tenantScope, taskExecutor, cancellationToken);
 32
 33        // Step 3: Start recurring tasks
 2834        await StartRecurringTasksAsync(tenantScope, taskExecutor, cancellationToken);
 2835    }
 36
 37    public async Task TenantDeactivatedAsync(TenantDeactivatedEventArgs args)
 38    {
 1239        var tenantScope = args.TenantScope;
 40
 41        // Cancel all running tasks
 1242        _cancellationTokenSource.Cancel();
 43
 44        // Wait for background tasks to complete (with cancellation they should finish quickly)
 1245        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
 9659        foreach (var timer in _scheduledTimers)
 3660            await timer.DisposeAsync();
 1261        _scheduledTimers.Clear();
 62
 63        // Stop recurring tasks
 1264        var recurringTasks = tenantScope.ServiceProvider.GetServices<IRecurringTask>();
 9665        foreach (var task in recurringTasks)
 3666            await task.StopAsync(args.CancellationToken);
 1267    }
 68
 69    private async Task RunStartupTasksAsync(ITenantScope tenantScope, ITaskExecutor taskExecutor, CancellationToken canc
 70    {
 2871        var startupTasks = tenantScope.ServiceProvider.GetServices<IStartupTask>()
 25272            .OrderBy(x => x.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0f)
 2873            .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.
 2877        var sortedTasks = TopologicalTaskSorter.Sort(startupTasks).ToList();
 56078        foreach (var task in sortedTasks)
 25279            await taskExecutor.ExecuteTaskAsync(task, cancellationToken);
 2880    }
 81
 82    private Task RunBackgroundTasksAsync(ITenantScope tenantScope, ITaskExecutor taskExecutor, CancellationToken cancell
 83    {
 2884        var backgroundTasks = tenantScope.ServiceProvider.GetServices<IBackgroundTask>();
 2885        var backgroundTaskStarter = tenantScope.ServiceProvider.GetRequiredService<IBackgroundTaskStarter>();
 86
 11287        foreach (var backgroundTask in backgroundTasks)
 88        {
 2889            var task = backgroundTaskStarter
 2890                .StartAsync(backgroundTask, _cancellationTokenSource.Token)
 2891                .ContinueWith(t => taskExecutor.ExecuteTaskAsync(backgroundTask, _cancellationTokenSource.Token),
 2892                    cancellationToken,
 2893                    TaskContinuationOptions.RunContinuationsAsynchronously,
 2894                    TaskScheduler.Default)
 2895                .Unwrap();
 96
 2897            if (!task.IsCompleted)
 2898                _runningBackgroundTasks.Add(task);
 99        }
 100
 28101        return Task.CompletedTask;
 102    }
 103
 104    private async Task StartRecurringTasksAsync(ITenantScope tenantScope, ITaskExecutor taskExecutor, CancellationToken 
 105    {
 28106        var recurringTasks = tenantScope.ServiceProvider.GetServices<IRecurringTask>().ToList();
 107
 224108        foreach (var task in recurringTasks)
 109        {
 84110            var schedule = scheduleManager.GetScheduleFor(task.GetType());
 84111            var timer = schedule.CreateTimer(async () =>
 84112            {
 84113                try
 84114                {
 2131115                    await taskExecutor.ExecuteTaskAsync(task, _cancellationTokenSource.Token);
 2131116                }
 0117                catch (OperationCanceledException e)
 84118                {
 0119                    logger.LogInformation(e, "Recurring task {TaskType} was cancelled", task.GetType().Name);
 0120                }
 0121                catch (Exception e)
 84122                {
 84123                    // Log but don't rethrow - recurring tasks should not crash the host
 0124                    logger.LogError(e, "Recurring task {TaskType} failed with an error", task.GetType().Name);
 0125                }
 2215126            }, logger);
 127
 84128            _scheduledTimers.Add(timer);
 84129            await task.StartAsync(cancellationToken);
 130        }
 28131    }
 132}