Track workflow execution in AppComponentTest to prevent premature disposal.
This commit is contained in:
parent
d5efd38dcc
commit
081e4ccac0
|
|
@ -1,8 +1,8 @@
|
|||
using System.Collections.Concurrent;
|
||||
using Elsa.Common.Multitenancy;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Testing.Shared.Services;
|
||||
using Elsa.Workflows.ComponentTests.Fixtures;
|
||||
using Elsa.Workflows.Management;
|
||||
using Elsa.Workflows.Management.Filters;
|
||||
using Elsa.Workflows.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Workflows.ComponentTests.Abstractions;
|
||||
|
|
@ -15,6 +15,9 @@ public abstract class AppComponentTest : IDisposable
|
|||
protected Infrastructure Infrastructure { get; }
|
||||
protected IServiceScope Scope { get; }
|
||||
private readonly IDisposable _tenantScope;
|
||||
private readonly WorkflowEvents _workflowEvents;
|
||||
private readonly ManualResetEventSlim _allWorkflowsIdle = new(true); // starts signaled (no workflows running)
|
||||
private readonly ConcurrentDictionary<string, byte> _runningWorkflowIds = new();
|
||||
|
||||
protected AppComponentTest(App app)
|
||||
{
|
||||
|
|
@ -25,14 +28,20 @@ public abstract class AppComponentTest : IDisposable
|
|||
|
||||
var tenantAccessor = Scope.ServiceProvider.GetRequiredService<ITenantAccessor>();
|
||||
_tenantScope = tenantAccessor.PushContext(new Tenant { Id = string.Empty, Name = "Default" });
|
||||
|
||||
// Subscribe to workflow lifecycle events to track in-flight workflows.
|
||||
_workflowEvents = app.WorkflowServer.Services.GetRequiredService<WorkflowEvents>();
|
||||
_workflowEvents.WorkflowStateCommitted += OnWorkflowStateCommitted;
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
// Wait for all workflows to reach terminal state before disposing scope
|
||||
// This prevents TaskCanceledException when workflows are still executing
|
||||
// Wait for all workflows to reach terminal state before disposing scope.
|
||||
// This prevents TaskCanceledException when workflows are still executing.
|
||||
WaitForWorkflowsToComplete();
|
||||
|
||||
_workflowEvents.WorkflowStateCommitted -= OnWorkflowStateCommitted;
|
||||
_allWorkflowsIdle.Dispose();
|
||||
_tenantScope.Dispose();
|
||||
Scope.Dispose();
|
||||
OnDispose();
|
||||
|
|
@ -42,39 +51,38 @@ public abstract class AppComponentTest : IDisposable
|
|||
{
|
||||
}
|
||||
|
||||
private void OnWorkflowStateCommitted(object? sender, WorkflowStateCommittedEventArgs e)
|
||||
{
|
||||
var instanceId = e.WorkflowExecutionContext.Id;
|
||||
var status = e.WorkflowExecutionContext.Status;
|
||||
|
||||
if (status == WorkflowStatus.Running)
|
||||
{
|
||||
// Track this instance as in-flight.
|
||||
if (_runningWorkflowIds.TryAdd(instanceId, 0) && _runningWorkflowIds.Count == 1)
|
||||
_allWorkflowsIdle.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Workflow reached a terminal state; remove it.
|
||||
_runningWorkflowIds.TryRemove(instanceId, out _);
|
||||
|
||||
if (_runningWorkflowIds.IsEmpty)
|
||||
_allWorkflowsIdle.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitForWorkflowsToComplete()
|
||||
{
|
||||
try
|
||||
{
|
||||
var workflowInstanceStore = Scope.ServiceProvider.GetRequiredService<IWorkflowInstanceStore>();
|
||||
var timeout = TimeSpan.FromSeconds(10);
|
||||
var pollInterval = TimeSpan.FromMilliseconds(50);
|
||||
var deadline = DateTime.UtcNow.Add(timeout);
|
||||
|
||||
while (DateTime.UtcNow < deadline)
|
||||
{
|
||||
var filter = new WorkflowInstanceFilter
|
||||
{
|
||||
WorkflowStatus = WorkflowStatus.Running
|
||||
};
|
||||
|
||||
// Use async method synchronously - acceptable in cleanup/dispose
|
||||
var runningWorkflows = workflowInstanceStore.FindManyAsync(filter, CancellationToken.None)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
if (!runningWorkflows.Any())
|
||||
return; // All workflows completed
|
||||
|
||||
Thread.Sleep(pollInterval);
|
||||
}
|
||||
|
||||
// If we reach here, workflows didn't complete in time
|
||||
// Log but don't throw to avoid masking actual test failures
|
||||
// Wait up to 10 seconds for all tracked workflows to finish.
|
||||
// If no workflows were started, the event is already signaled and this returns immediately.
|
||||
_allWorkflowsIdle.Wait(TimeSpan.FromSeconds(10));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow exceptions during cleanup to avoid masking test failures
|
||||
// Swallow exceptions during cleanup to avoid masking test failures.
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue