< Summary

Information
Class: Elsa.Hosting.Management.HostedServices.InstanceHeartbeatMonitorService
Assembly: Elsa.Hosting.Management
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Hosting.Management/HostedServices/InstanceHeartbeatMonitorService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 33
Coverable lines: 33
Total lines: 91
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 12
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
StartAsync(...)100%210%
StopAsync(...)0%620%
Dispose()0%620%
MonitorHeartbeats(...)100%210%
MonitorHeartbeatsAsync()0%7280%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Hosting.Management/HostedServices/InstanceHeartbeatMonitorService.cs

#LineLine coverage
 1using Elsa.Common;
 2using Elsa.Hosting.Management.Notifications;
 3using Elsa.Hosting.Management.Options;
 4using Elsa.KeyValues.Contracts;
 5using Elsa.KeyValues.Models;
 6using Elsa.Mediator.Contracts;
 7using JetBrains.Annotations;
 8using Medallion.Threading;
 9using Microsoft.Extensions.DependencyInjection;
 10using Microsoft.Extensions.Hosting;
 11using Microsoft.Extensions.Options;
 12
 13namespace Elsa.Hosting.Management.HostedServices;
 14
 15/// <summary>
 16/// Service to check the heartbeats of all running instances and determine whether instances have stopped working.
 17/// </summary>
 18[UsedImplicitly]
 19public class InstanceHeartbeatMonitorService : IHostedService, IDisposable
 20{
 21    private readonly IServiceProvider _serviceProvider;
 22    private readonly HeartbeatOptions _heartbeatOptions;
 23    private Timer? _timer;
 24
 25    /// <summary>
 26    /// Creates a new instance of the <see cref="InstanceHeartbeatMonitorService"/>
 27    /// </summary>
 028    public InstanceHeartbeatMonitorService(IServiceProvider serviceProvider, IOptions<HeartbeatOptions> heartbeatOptions
 29    {
 030        _serviceProvider = serviceProvider;
 031        _heartbeatOptions = heartbeatOptions.Value;
 032    }
 33
 34    /// <inheritdoc />
 35    public Task StartAsync(CancellationToken cancellationToken)
 36    {
 037        _timer = new Timer(MonitorHeartbeats, null, TimeSpan.Zero, _heartbeatOptions.Interval);
 038        return Task.CompletedTask;
 39    }
 40
 41    /// <inheritdoc />
 42    public Task StopAsync(CancellationToken cancellationToken)
 43    {
 044        _timer?.Change(Timeout.Infinite, Timeout.Infinite);
 045        return Task.CompletedTask;
 46    }
 47
 48    /// <inheritdoc />
 49    public void Dispose()
 50    {
 051        _timer?.Dispose();
 052    }
 53
 54    private void MonitorHeartbeats(object? state)
 55    {
 056        _ = Task.Run(async () => await MonitorHeartbeatsAsync());
 057    }
 58
 59    private async Task MonitorHeartbeatsAsync()
 60    {
 061        using var scope = _serviceProvider.CreateScope();
 062        var lockProvider = scope.ServiceProvider.GetRequiredService<IDistributedLockProvider>();
 063        var store = scope.ServiceProvider.GetRequiredService<IKeyValueStore>();
 064        var notificationSender = scope.ServiceProvider.GetRequiredService<INotificationSender>();
 065        var systemClock = scope.ServiceProvider.GetRequiredService<ISystemClock>();
 66
 67        const string lockKey = "InstanceHeartbeatMonitorService";
 068        await using var monitorLock = await lockProvider.TryAcquireLockAsync(lockKey, TimeSpan.Zero);
 069        if (monitorLock == null)
 70            return;
 71
 072        var filter = new KeyValueFilter
 073        {
 074            StartsWith = true,
 075            Key = InstanceHeartbeatService.HeartbeatKeyPrefix
 076        };
 077        var heartbeats = await store.FindManyAsync(filter, default);
 78
 079        foreach (var heartbeat in heartbeats)
 80        {
 081            var lastHeartbeat = DateTimeOffset.Parse(heartbeat.SerializedValue).UtcDateTime;
 82
 083            if (systemClock.UtcNow - lastHeartbeat <= _heartbeatOptions.Timeout)
 84                continue;
 85
 086            var instanceName = heartbeat.Key[InstanceHeartbeatService.HeartbeatKeyPrefix.Length..];
 087            await notificationSender.SendAsync(new HeartbeatTimedOut(instanceName));
 088            await store.DeleteAsync(heartbeat.Key, default);
 089        }
 090    }
 91}