< 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>
 313public class TenantTaskManager(RecurringTaskScheduleManager scheduleManager, ILogger<TenantTaskManager> logger) : ITenan
 14{
 315    private readonly ICollection<Task> _runningBackgroundTasks = new List<Task>();
 316    private readonly ICollection<ScheduledTimer> _scheduledTimers = new List<ScheduledTimer>();
 17    private CancellationTokenSource _cancellationTokenSource = null!;
 18
 19    public async Task TenantActivatedAsync(TenantActivatedEventArgs args)
 20    {
 1221        var cancellationToken = args.CancellationToken;
 1222        var tenantScope = args.TenantScope;
 1223        var taskExecutor = tenantScope.ServiceProvider.GetRequiredService<ITaskExecutor>();
 24
 1225        _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 26
 27        // Step 1: Run startup tasks (with dependency ordering)
 1228        await RunStartupTasksAsync(tenantScope, taskExecutor, cancellationToken);
 29
 30        // Step 2: Run background tasks
 1231        await RunBackgroundTasksAsync(tenantScope, taskExecutor, cancellationToken);
 32
 33        // Step 3: Start recurring tasks
 1234        await StartRecurringTasksAsync(tenantScope, taskExecutor, cancellationToken);
 1235    }
 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    {
 1271        var startupTasks = tenantScope.ServiceProvider.GetServices<IStartupTask>()
 10872            .OrderBy(x => x.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0f)
 1273            .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.
 1277        var sortedTasks = TopologicalTaskSorter.Sort(startupTasks).ToList();
 24078        foreach (var task in sortedTasks)
 10879            await taskExecutor.ExecuteTaskAsync(task, cancellationToken);
 1280    }
 81
 82    private Task RunBackgroundTasksAsync(ITenantScope tenantScope, ITaskExecutor taskExecutor, CancellationToken cancell
 83    {
 1284        var backgroundTasks = tenantScope.ServiceProvider.GetServices<IBackgroundTask>();
 1285        var backgroundTaskStarter = tenantScope.ServiceProvider.GetRequiredService<IBackgroundTaskStarter>();
 86
 4887        foreach (var backgroundTask in backgroundTasks)
 88        {
 1289            var task = backgroundTaskStarter
 1290                .StartAsync(backgroundTask, _cancellationTokenSource.Token)
 1291                .ContinueWith(t => taskExecutor.ExecuteTaskAsync(backgroundTask, _cancellationTokenSource.Token),
 1292                    cancellationToken,
 1293                    TaskContinuationOptions.RunContinuationsAsynchronously,
 1294                    TaskScheduler.Default)
 1295                .Unwrap();
 96
 1297            if (!task.IsCompleted)
 1298                _runningBackgroundTasks.Add(task);
 99        }
 100
 12101        return Task.CompletedTask;
 102    }
 103
 104    private async Task StartRecurringTasksAsync(ITenantScope tenantScope, ITaskExecutor taskExecutor, CancellationToken 
 105    {
 12106        var recurringTasks = tenantScope.ServiceProvider.GetServices<IRecurringTask>().ToList();
 107
 96108        foreach (var task in recurringTasks)
 109        {
 36110            var schedule = scheduleManager.GetScheduleFor(task.GetType());
 36111            var timer = schedule.CreateTimer(async () =>
 36112            {
 36113                try
 36114                {
 76115                    await taskExecutor.ExecuteTaskAsync(task, _cancellationTokenSource.Token);
 76116                }
 0117                catch (OperationCanceledException e)
 36118                {
 0119                    logger.LogInformation(e, "Recurring task {TaskType} was cancelled", task.GetType().Name);
 0120                }
 0121                catch (Exception e)
 36122                {
 36123                    // 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                }
 112126            }, logger);
 127
 36128            _scheduledTimers.Add(timer);
 36129            await task.StartAsync(cancellationToken);
 130        }
 12131    }
 132}