< Summary

Information
Class: Elsa.Scheduling.ScheduledTasks.ScheduledSpecificInstantTask
Assembly: Elsa.Scheduling
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Scheduling/ScheduledTasks/ScheduledSpecificInstantTask.cs
Line coverage
92%
Covered lines: 78
Uncovered lines: 6
Coverable lines: 84
Total lines: 150
Line coverage: 92.8%
Branch coverage
87%
Covered branches: 7
Total branches: 8
Branch coverage: 87.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
.ctor(...)100%11100%
.ctor(...)100%210%
Cancel()100%44100%
Schedule()100%2292.45%
System.IDisposable.Dispose()50%22100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Scheduling/ScheduledTasks/ScheduledSpecificInstantTask.cs

#LineLine coverage
 1using Elsa.Common;
 2using Elsa.Mediator.Contracts;
 3using Elsa.Scheduling.Commands;
 4using Elsa.Scheduling.Options;
 5using Elsa.Scheduling.Services;
 6using Microsoft.Extensions.DependencyInjection;
 7using Microsoft.Extensions.Logging;
 8using Timer = System.Timers.Timer;
 9using OptionsFactory = Microsoft.Extensions.Options.Options;
 10
 11namespace Elsa.Scheduling.ScheduledTasks;
 12
 13/// <summary>
 14/// A task that is scheduled to execute at a specific instant.
 15/// </summary>
 16public class ScheduledSpecificInstantTask : IScheduledTask, IDisposable
 17{
 018    private static readonly PastDueScheduleStaggerer DefaultPastDueScheduleStaggerer = new(OptionsFactory.Create(new Sch
 19    private readonly ITask _task;
 20    private readonly ISystemClock _systemClock;
 21    private readonly IServiceScopeFactory _scopeFactory;
 22    private readonly ILogger<ScheduledSpecificInstantTask> _logger;
 23    private readonly PastDueScheduleStaggerer _pastDueScheduleStaggerer;
 24    private readonly DateTimeOffset _startAt;
 25    private readonly CancellationTokenSource _cancellationTokenSource;
 2026    private readonly SemaphoreSlim _executionSemaphore = new(1, 1);
 27    private Timer? _timer;
 28    private bool _executing;
 29    private bool _cancellationRequested;
 30    private bool _disposed;
 31
 32    /// <summary>
 33    /// Initializes a new instance of <see cref="ScheduledSpecificInstantTask"/>.
 34    /// </summary>
 35    public ScheduledSpecificInstantTask(
 36        ITask task,
 37        DateTimeOffset startAt,
 38        ISystemClock systemClock,
 39        IServiceScopeFactory scopeFactory,
 40        ILogger<ScheduledSpecificInstantTask> logger)
 041        : this(task, startAt, systemClock, scopeFactory, logger, DefaultPastDueScheduleStaggerer)
 42    {
 043    }
 44
 45    /// <summary>
 46    /// Initializes a new instance of <see cref="ScheduledSpecificInstantTask"/>.
 47    /// </summary>
 48    [ActivatorUtilitiesConstructor]
 2049    public ScheduledSpecificInstantTask(
 2050        ITask task,
 2051        DateTimeOffset startAt,
 2052        ISystemClock systemClock,
 2053        IServiceScopeFactory scopeFactory,
 2054        ILogger<ScheduledSpecificInstantTask> logger,
 2055        PastDueScheduleStaggerer pastDueScheduleStaggerer)
 56    {
 2057        _task = task;
 2058        _systemClock = systemClock;
 2059        _scopeFactory = scopeFactory;
 2060        _logger = logger;
 2061        _pastDueScheduleStaggerer = pastDueScheduleStaggerer;
 2062        _startAt = startAt;
 2063        _cancellationTokenSource = new();
 64
 2065        Schedule();
 2066    }
 67
 68    /// <inheritdoc />
 69    public void Cancel()
 70    {
 1371        _timer?.Dispose();
 72
 1373        if (_executing)
 74        {
 675            _cancellationRequested = true;
 676            return;
 77        }
 78
 779        _cancellationTokenSource.Cancel();
 780    }
 81
 82    private void Schedule()
 83    {
 2084        var now = _systemClock.UtcNow;
 2085        var delay = _startAt - now;
 2086        var adjustedDelay = _pastDueScheduleStaggerer.GetDelay(delay);
 87
 2088        if (delay <= TimeSpan.Zero)
 89        {
 690            _logger.LogDebug("Calculated delay is {Delay} which is not positive. Using catch-up delay of {CatchUpDelay}"
 91        }
 92
 2093        _timer = new(adjustedDelay.TotalMilliseconds)
 2094        {
 2095            Enabled = true
 2096        };
 97
 2098        _timer.Elapsed += async (_, _) =>
 2099        {
 6100            _timer?.Dispose();
 6101            _timer = null;
 20102
 20103            // Check if disposed before proceeding
 6104            if (_disposed) return;
 20105
 6106            using var scope = _scopeFactory.CreateScope();
 6107            var commandSender = scope.ServiceProvider.GetRequiredService<ICommandSender>();
 20108
 20109            // Check disposed again before accessing CancellationTokenSource
 6110            if (_disposed) return;
 20111
 6112            var cancellationToken = _cancellationTokenSource.Token;
 6113            if (!cancellationToken.IsCancellationRequested)
 20114            {
 6115                var acquired = false;
 20116                try
 20117                {
 6118                    acquired = await _executionSemaphore.WaitAsync(0, cancellationToken);
 6119                    if (!acquired) return;
 6120                    _executing = true;
 6121                    await commandSender.SendAsync(new RunScheduledTask(_task), cancellationToken);
 20122
 6123                    if (_cancellationRequested)
 20124                    {
 6125                        _cancellationRequested = false;
 6126                        _cancellationTokenSource.Cancel();
 20127                    }
 6128                }
 0129                catch (Exception e)
 20130                {
 0131                    _logger.LogError(e, "Error executing scheduled task");
 0132                }
 20133                finally
 20134                {
 6135                    _executing = false;
 6136                    if (acquired && !_disposed)
 6137                        _executionSemaphore.Release();
 20138                }
 20139            }
 26140        };
 20141    }
 142
 143    void IDisposable.Dispose()
 144    {
 8145        _disposed = true;
 8146        _timer?.Dispose();
 8147        _cancellationTokenSource.Dispose();
 8148        _executionSemaphore.Dispose();
 8149    }
 150}