From 9cdff8c318159624eb3f0a24ad03665170aeaba7 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 20 May 2026 23:39:35 +0200 Subject: [PATCH] Handle missing distributed lock provider in readiness check --- .../ElsaDistributedLockHealthCheck.cs | 12 +++++++++- .../ElsaDistributedLockHealthCheckTests.cs | 24 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/modules/Elsa.Workflows.Runtime/HealthChecks/ElsaDistributedLockHealthCheck.cs b/src/modules/Elsa.Workflows.Runtime/HealthChecks/ElsaDistributedLockHealthCheck.cs index 756089f3c..c585ce685 100644 --- a/src/modules/Elsa.Workflows.Runtime/HealthChecks/ElsaDistributedLockHealthCheck.cs +++ b/src/modules/Elsa.Workflows.Runtime/HealthChecks/ElsaDistributedLockHealthCheck.cs @@ -1,5 +1,6 @@ using Elsa.Common; using Medallion.Threading; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; namespace Elsa.Workflows.Runtime.HealthChecks; @@ -7,7 +8,7 @@ namespace Elsa.Workflows.Runtime.HealthChecks; /// /// Verifies that the configured distributed lock provider can acquire and release a probe lock. /// -public class ElsaDistributedLockHealthCheck(IDistributedLockProvider distributedLockProvider) : IHealthCheck +public class ElsaDistributedLockHealthCheck(IServiceProvider serviceProvider) : IHealthCheck { private static readonly string LockName = $"elsa-health-check-{Environment.MachineName}-{Guid.NewGuid():N}"; private static readonly TimeSpan LockAcquisitionTimeout = TimeSpan.FromSeconds(1); @@ -17,6 +18,15 @@ public class ElsaDistributedLockHealthCheck(IDistributedLockProvider distributed { try { + var distributedLockProvider = serviceProvider.GetService(); + if (distributedLockProvider == null) + { + return HealthCheckResult.Degraded("Elsa distributed lock provider is not registered.", data: new Dictionary + { + ["category"] = "distributed-locks" + }); + } + await using var handle = await distributedLockProvider.TryAcquireLockAsync(LockName, LockAcquisitionTimeout, cancellationToken); if (handle == null) { diff --git a/test/unit/Elsa.Workflows.Runtime.UnitTests/HealthChecks/ElsaDistributedLockHealthCheckTests.cs b/test/unit/Elsa.Workflows.Runtime.UnitTests/HealthChecks/ElsaDistributedLockHealthCheckTests.cs index 297eaa90a..bfebf682e 100644 --- a/test/unit/Elsa.Workflows.Runtime.UnitTests/HealthChecks/ElsaDistributedLockHealthCheckTests.cs +++ b/test/unit/Elsa.Workflows.Runtime.UnitTests/HealthChecks/ElsaDistributedLockHealthCheckTests.cs @@ -1,5 +1,6 @@ using Elsa.Workflows.Runtime.HealthChecks; using Medallion.Threading; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NSubstitute; @@ -17,7 +18,7 @@ public class ElsaDistributedLockHealthCheckTests _distributedLockProvider.CreateLock(Arg.Any()).Returns(_distributedLock); _distributedLock.TryAcquireAsync(Arg.Any(), Arg.Any()) .Returns(new ValueTask(Substitute.For())); - _sut = new ElsaDistributedLockHealthCheck(_distributedLockProvider); + _sut = new ElsaDistributedLockHealthCheck(CreateServiceProvider(_distributedLockProvider)); } [Fact] @@ -53,4 +54,25 @@ public class ElsaDistributedLockHealthCheckTests Assert.Equal(HealthStatus.Unhealthy, result.Status); Assert.Equal("distributed-locks", result.Data["category"]); } + + [Fact] + public async Task ReturnsDegradedWhenProviderIsNotRegistered() + { + var sut = new ElsaDistributedLockHealthCheck(CreateServiceProvider()); + + var result = await sut.CheckHealthAsync(new HealthCheckContext()); + + Assert.Equal(HealthStatus.Degraded, result.Status); + Assert.Equal("distributed-locks", result.Data["category"]); + } + + private static IServiceProvider CreateServiceProvider(IDistributedLockProvider? distributedLockProvider = null) + { + var services = new ServiceCollection(); + + if (distributedLockProvider != null) + services.AddSingleton(distributedLockProvider); + + return services.BuildServiceProvider(); + } }