using Elsa.Common;
using Elsa.Common.Multitenancy;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Runtime.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Elsa.Workflows.Runtime.Services;
///
/// Default implementation of .
///
public sealed class InterruptedRecoveryScanner : IInterruptedRecoveryScanner
{
private readonly IWorkflowRestarter _restarter;
private readonly IWorkflowInstanceStore _instanceStore;
private readonly IOptions _runtimeOptions;
private readonly ILogger _logger;
private readonly ITenantService? _tenantService;
private readonly ITenantAccessor? _tenantAccessor;
public InterruptedRecoveryScanner(
IWorkflowRestarter restarter,
IWorkflowInstanceStore instanceStore,
IOptions runtimeOptions,
ILogger logger,
ITenantService? tenantService = null,
ITenantAccessor? tenantAccessor = null)
{
_restarter = restarter;
_instanceStore = instanceStore;
_runtimeOptions = runtimeOptions;
_logger = logger;
_tenantService = tenantService;
_tenantAccessor = tenantAccessor;
}
///
public async ValueTask ScanAndRequeueAsync(CancellationToken cancellationToken)
{
var filter = new WorkflowInstanceFilter { WorkflowSubStatus = WorkflowSubStatus.Interrupted };
var batchSize = _runtimeOptions.Value.RestartInterruptedWorkflowsBatchSize;
var instances = _instanceStore.EnumerateSummariesAsync(filter, batchSize, cancellationToken);
var requeued = 0;
_logger.LogInformation("Scanning for workflows interrupted by graceful drain.");
await foreach (var summary in instances.WithCancellation(cancellationToken))
{
try
{
var tenantId = summary.TenantId ?? string.Empty;
if (_tenantService is not null && _tenantAccessor is not null && !string.IsNullOrWhiteSpace(tenantId) && tenantId != Tenant.AgnosticTenantId)
{
var tenant = await _tenantService.FindAsync(tenantId, cancellationToken) ?? new Tenant { Id = tenantId, Name = tenantId };
using (_tenantAccessor.PushContext(tenant))
await _restarter.RestartWorkflowAsync(summary.Id, cancellationToken);
}
else
{
await _restarter.RestartWorkflowAsync(summary.Id, cancellationToken);
}
requeued++;
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to requeue interrupted workflow {WorkflowInstanceId}; will be retried by the timeout-based recovery task.", summary.Id);
}
}
_logger.LogInformation("Interrupted-workflow scan complete; requeued {Count} instance(s).", requeued);
return requeued;
}
}