< Summary

Information
Class: Elsa.Scheduling.Services.LocalScheduler
Assembly: Elsa.Scheduling
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Scheduling/Services/LocalScheduler.cs
Line coverage
59%
Covered lines: 31
Uncovered lines: 21
Coverable lines: 52
Total lines: 128
Line coverage: 59.6%
Branch coverage
21%
Covered branches: 3
Total branches: 14
Branch coverage: 21.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ScheduleAsync(...)100%11100%
ScheduleAsync(...)100%11100%
ClearScheduleAsync(...)100%11100%
ClearScheduleAsync(...)100%210%
RegisterScheduledTask(...)25%5457.14%
RemoveScheduledTask(...)100%22100%
RemoveScheduledTasks(...)0%7280%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Scheduling/Services/LocalScheduler.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using System.Collections.Generic;
 3using Microsoft.Extensions.Logging;
 4using Elsa.Extensions;
 5
 6namespace Elsa.Scheduling.Services;
 7
 8/// <summary>
 9/// Represents a local, in-memory scheduler that schedules tasks in-process.
 10/// </summary>
 11public class LocalScheduler : IScheduler
 12{
 13    private readonly IServiceProvider _serviceProvider;
 14    private readonly ILogger<LocalScheduler> _logger;
 13515    private readonly ConcurrentDictionary<string, IScheduledTask> _scheduledTasks = new();
 13516    private readonly ConcurrentDictionary<IScheduledTask, ICollection<string>> _scheduledTaskKeys = new();
 17
 18    // Note: Using lock instead of SemaphoreSlim because:
 19    // 1. All critical sections are synchronous (dictionary operations only)
 20    // 2. Methods return ValueTask.CompletedTask (not truly async)
 21    // 3. No await inside critical sections
 22    // 4. lock has zero allocation overhead, perfect for fast synchronous operations
 13523    private readonly object _lock = new();
 24
 25    /// <summary>
 26    /// Initializes a new instance of the <see cref="LocalScheduler"/> class.
 27    /// </summary>
 13528    public LocalScheduler(IServiceProvider serviceProvider, ILogger<LocalScheduler> logger)
 29    {
 13530        _serviceProvider = serviceProvider;
 13531        _logger = logger;
 13532    }
 33
 34    /// <inheritdoc />
 35    public ValueTask ScheduleAsync(string name, ITask task, ISchedule schedule, CancellationToken cancellationToken = de
 36    {
 2137        return ScheduleAsync(name, task, schedule, null, cancellationToken);
 38    }
 39
 40    /// <inheritdoc />
 41    public ValueTask ScheduleAsync(string name, ITask task, ISchedule schedule, IEnumerable<string>? keys = null, Cancel
 42    {
 2143        var scheduleContext = new ScheduleContext(_serviceProvider, task);
 2144        var scheduledTask = schedule.Schedule(scheduleContext);
 45
 2146        lock (_lock)
 47        {
 2148            RegisterScheduledTask(name, scheduledTask, keys);
 2149        }
 50
 2151        return ValueTask.CompletedTask;
 52    }
 53
 54    /// <inheritdoc />
 55    public ValueTask ClearScheduleAsync(string name, CancellationToken cancellationToken = default)
 56    {
 3357        lock (_lock)
 58        {
 3359            RemoveScheduledTask(name);
 3360        }
 61
 3362        return ValueTask.CompletedTask;
 63    }
 64
 65    /// <inheritdoc />
 66    public ValueTask ClearScheduleAsync(IEnumerable<string> keys, CancellationToken cancellationToken = default)
 67    {
 068        lock (_lock)
 69        {
 070            RemoveScheduledTasks(keys);
 071        }
 72
 073        return ValueTask.CompletedTask;
 74    }
 75
 76
 77    private void RegisterScheduledTask(string name, IScheduledTask scheduledTask, IEnumerable<string>? keys = null)
 78    {
 2179        _scheduledTasks.AddOrUpdate(
 2180            name,
 2181            addValueFactory: _ => scheduledTask,
 2182            updateValueFactory: (_, existingScheduledTask) =>
 2183            {
 084                existingScheduledTask.Cancel();
 085                var removed = _scheduledTaskKeys.TryRemove(existingScheduledTask, out var _);
 086                if (!removed)
 087                    _logger.LogWarning("Tried to remove scheduled task keys for an existing scheduled task, but it was n
 088                return scheduledTask;
 2189            });
 90
 2191        if (keys != null)
 092            _scheduledTaskKeys[scheduledTask] = keys.ToList();
 2193    }
 94
 95
 96    private void RemoveScheduledTask(string name)
 97    {
 3398        if (_scheduledTasks.TryGetValue(name, out var existingScheduledTask))
 99        {
 19100            _scheduledTaskKeys.Remove(existingScheduledTask, out _);
 19101            _scheduledTasks.Remove(name, out _);
 19102            existingScheduledTask.Cancel();
 103        }
 33104    }
 105
 106    private void RemoveScheduledTasks(IEnumerable<string> keys)
 107    {
 0108        foreach (var key in keys)
 109        {
 0110            var scheduledTasks = _scheduledTaskKeys.Where(x => x.Value.Contains(key)).Select(x => x.Key).Distinct().ToLi
 111
 0112            foreach (var scheduledTask in scheduledTasks)
 113            {
 114                // Collect all keys in _scheduledTasks that map to this scheduledTask
 0115                var matchingTaskKeys = _scheduledTasks.Where(x => x.Value == scheduledTask).Select(x => x.Key).ToList();
 0116                foreach (var taskKey in matchingTaskKeys)
 117                {
 0118                    var removed = _scheduledTasks.TryRemove(taskKey, out _);
 0119                    if (!removed)
 0120                        _logger.LogWarning("Failed to remove scheduled task with key '{TaskKey}' for '{Key}' from _sched
 121                }
 122
 0123                _scheduledTaskKeys.Remove(scheduledTask, out _);
 0124                scheduledTask.Cancel();
 125            }
 126        }
 0127    }
 128}