Skip to content

Commit

Permalink
Fix WorkflowExecutionContext bug (elsa-workflows#4919)
Browse files Browse the repository at this point in the history
* Rename Elsa.IntegrationTests to Elsa.Workflows.IntegrationTests

* Include missed files

* Update WorkflowExecutionContext to assign Activities

This commit updates the constructor of WorkflowExecutionContext. It adds a loop that iterates through each activityExecutionContext and associates its Activity with a corresponding one in the NodeIdLookup by using its NodeId as the key.

* Add versionOptions parameter to RunWorkflowUntilEndAsync method

This update introduces the versionOptions parameter to the RunWorkflowUntilEndAsync method, giving the user control over the version of the workflow they want to run. If no options are provided, the method defaults to running the published version.

* Add new integration tests and workflow samples

Created new integration test projects, Elsa.Alterations.IntegrationTests and Elsa.Workflows.IntegrationTests, for added functionality verification. Also added new workflow sample files "alteration-test.json" and "alteration-test.-v2.json" to "samples/aspnet/ElsaAlterationMigrateBug/Workflows" for testing purposes.

* Remove ElsaAlterationMigrateBug sample

This commit removes the ElsaAlterationMigrateBug sample from the project. The sample is no longer needed and its related files including workflows and project references have been deleted from the solution. This removal helps streamline the solution and remove unnecessary clutter.

* Update comment in MigrationTests.cs

The comments in the MigrationTests.cs file have been revised to provide a more accurate description of what happens in the code. The migration process described does not involve resetting execution, so any reference to it was removed.

* Refactor MigrationTests class summary description

The summary description of the MigrationTests class has been revised. The verb 'Represents' was removed to make the comment more concise and straightforward, focusing solely on its functional aspect.

* Update MigrationTests class constructor comments

The summary comments for the MigrationTests class constructor were corrected. Previously, it indicated that it represented a class containing tests for migration, which was inaccurate. It now properly describes that it initializes a new instance of the MigrationTests class.
  • Loading branch information
sfmskywalker committed Feb 9, 2024
1 parent 4fe0446 commit 60a349c
Show file tree
Hide file tree
Showing 152 changed files with 375 additions and 159 deletions.
9 changes: 8 additions & 1 deletion Elsa.sln
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "component", "component", "{
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.JavaScript.UnitTests", "test\unit\Elsa.JavaScript.UnitTests\Elsa.JavaScript.UnitTests.csproj", "{0BB927FB-8C12-49B6-9150-64B733B2EBFD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.IntegrationTests", "test\integration\Elsa.IntegrationTests\Elsa.IntegrationTests.csproj", "{E9652738-2B3D-4357-B84B-54F0EA161382}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.IntegrationTests", "test\integration\Elsa.Workflows.IntegrationTests\Elsa.Workflows.IntegrationTests.csproj", "{E9652738-2B3D-4357-B84B-54F0EA161382}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "clients", "clients", "{89608AA5-5ADE-4832-AC7B-871C4AE64210}"
EndProject
Expand Down Expand Up @@ -313,6 +313,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "migrations", "migrations",
migrations\efcore-3.0.sh = migrations\efcore-3.0.sh
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Alterations.IntegrationTests", "test\integration\Elsa.Alterations.IntegrationTests\Elsa.Alterations.IntegrationTests.csproj", "{F50336DA-42D1-4DD1-A107-67AFEB8A33EE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -759,6 +761,10 @@ Global
{AFEB799E-82C3-4D02-9D5C-766BB8DEF004}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AFEB799E-82C3-4D02-9D5C-766BB8DEF004}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AFEB799E-82C3-4D02-9D5C-766BB8DEF004}.Release|Any CPU.Build.0 = Release|Any CPU
{F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -893,6 +899,7 @@ Global
{E4BF9791-4086-41EB-8EF0-02686A5F3F65} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
{73852FEC-9847-4C6C-B1F5-1BB014C50A79} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
{AFEB799E-82C3-4D02-9D5C-766BB8DEF004} = {DD089B8B-DA73-492A-9010-F772D1C178DA}
{F50336DA-42D1-4DD1-A107-67AFEB8A33EE} = {1B8D5897-902E-4632-8698-E89CAF3DDF54}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}
Expand Down
8 changes: 6 additions & 2 deletions src/common/Elsa.Testing.Shared/ServiceProviderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,17 @@ public static async Task<WorkflowDefinition> ImportWorkflowDefinitionAsync(this
/// <param name="services">The services.</param>
/// <param name="workflowDefinitionId">The ID of the workflow definition.</param>
/// <param name="input">An optional dictionary of input values.</param>
/// <param name="versionOptions">An optional set of options to specify the version of the workflow definition to retrieve.</param>
/// <returns>The workflow state.</returns>
public static async Task<WorkflowState> RunWorkflowUntilEndAsync(this IServiceProvider services, string workflowDefinitionId, IDictionary<string, object>? input = default)
public static async Task<WorkflowState> RunWorkflowUntilEndAsync(this IServiceProvider services,
string workflowDefinitionId,
IDictionary<string, object>? input = default,
VersionOptions? versionOptions = default)
{
var startWorkflowOptions = new StartWorkflowRuntimeOptions
{
Input = input,
VersionOptions = VersionOptions.Published
VersionOptions = versionOptions ?? VersionOptions.Published
};
var workflowRuntime = services.GetRequiredService<IWorkflowRuntime>();
var result = await workflowRuntime.StartWorkflowAsync(workflowDefinitionId, startWorkflowOptions);
Expand Down
2 changes: 1 addition & 1 deletion src/modules/Elsa.Workflows.Api/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Elsa.IntegrationTests")]
[assembly: InternalsVisibleTo("Elsa.Workflows.IntegrationTests")]
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ public async Task SetWorkflowAsync(Workflow workflow)
NodeIdLookup = nodes.ToDictionary(x => x.NodeId);
NodeHashLookup = nodes.ToDictionary(x => Hash(x.NodeId));
NodeActivityLookup = nodes.ToDictionary(x => x.Activity);

foreach (var activityExecutionContext in ActivityExecutionContexts)
activityExecutionContext.Activity = NodeIdLookup[activityExecutionContext.Activity.NodeId].Activity;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public async Task<CanStartWorkflowResult> CanStartWorkflowAsync(string definitio
/// <inheritdoc />
public async Task<WorkflowExecutionResult> StartWorkflowAsync(string definitionId, StartWorkflowRuntimeOptions? options = default)
{
var workflowHost = await CreateWorkflowHostAsync(definitionId, options, options?.CancellationTokens.SystemCancellationToken ?? default);
var workflowHost = await CreateWorkflowHostAsync(definitionId, options, options?.CancellationTokens.SystemCancellationToken ?? default);
return await StartWorkflowAsync(workflowHost, options);
}

Expand All @@ -100,12 +100,12 @@ public async Task<WorkflowExecutionResult> StartWorkflowAsync(string definitionI
{
return await StartWorkflowAsync(definitionId, options);
}

/// <inheritdoc />
public async Task CancelWorkflowAsync(string workflowInstanceId, CancellationToken cancellationToken)
{
var workflowExecutionContext = await _workflowExecutionContextStore.FindAsync(workflowInstanceId);

if (workflowExecutionContext is null)
{
// The execution context is not running on this instance.
Expand All @@ -114,7 +114,7 @@ public async Task CancelWorkflowAsync(string workflowInstanceId, CancellationTok
await using var cancelLock = await _distributedLockProvider.TryAcquireLockAsync($"{workflowInstanceId}-cancel");
if (cancelLock == null)
return;

var workflowInstance = await _workflowInstanceStore.FindAsync(workflowInstanceId, cancellationToken);
if (workflowInstance is null
|| workflowInstance.SubStatus == WorkflowSubStatus.Cancelled
Expand All @@ -133,23 +133,23 @@ public async Task CancelWorkflowAsync(string workflowInstanceId, CancellationTok

var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(workflowDefinition, cancellationToken);
workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(_serviceProvider, workflow, workflowState, cancellationTokens: cancellationToken);

if (!cancellationToken.IsCancellationRequested)
await CancelWorkflowExecutionContextAsync();

return;
}

await using var mainCancelLock = await _distributedLockProvider.AcquireLockAsync($"{workflowInstanceId}-cancel", TimeSpan.FromMinutes(1));

await CancelWorkflowExecutionContextAsync();

async Task CancelWorkflowExecutionContextAsync()
{
var originalBookmarks = workflowExecutionContext.Bookmarks.ToList();

workflowExecutionContext.Cancel();

var newBookmarks = workflowExecutionContext.Bookmarks.ToList();
var diff = Diff.For(originalBookmarks, newBookmarks);
var bookmarkRequest = new UpdateBookmarksRequest(workflowExecutionContext.Id,
Expand All @@ -174,7 +174,10 @@ public async Task<ICollection<WorkflowExecutionResult>> StartWorkflowsAsync(stri

await using (await AcquireLockAsync(sharedResource, systemCancellationToken))
{
var filter = new TriggerFilter { Hash = hash };
var filter = new TriggerFilter
{
Hash = hash
};
var triggers = await _triggerStore.FindManyAsync(filter, systemCancellationToken);

foreach (var trigger in triggers)
Expand All @@ -199,22 +202,28 @@ public async Task<ICollection<WorkflowExecutionResult>> StartWorkflowsAsync(stri
continue;

var startResult = await StartWorkflowAsync(definitionId, startOptions);
results.Add(startResult with { TriggeredActivityId = trigger.ActivityId });
results.Add(startResult with
{
TriggeredActivityId = trigger.ActivityId
});
}
}

return results;
}

/// <inheritdoc />
public async Task<WorkflowExecutionResult?> ResumeWorkflowAsync(string workflowInstanceId, ResumeWorkflowRuntimeOptions options)
public async Task<WorkflowExecutionResult?> ResumeWorkflowAsync(string workflowInstanceId, ResumeWorkflowRuntimeOptions? options = default)
{
var applicationCancellationToken = options.CancellationTokens.ApplicationCancellationToken;
var systemCancellationToken = options.CancellationTokens.SystemCancellationToken;
var applicationCancellationToken = options?.CancellationTokens.ApplicationCancellationToken ?? default;
var systemCancellationToken = options?.CancellationTokens.SystemCancellationToken ?? default;

await using (await AcquireLockAsync(workflowInstanceId, systemCancellationToken))
{
var workflowInstance = await _workflowInstanceStore.FindAsync(new WorkflowInstanceFilter { Id = workflowInstanceId }, systemCancellationToken);
var workflowInstance = await _workflowInstanceStore.FindAsync(new WorkflowInstanceFilter
{
Id = workflowInstanceId
}, systemCancellationToken);

if (workflowInstance == null)
return null;
Expand All @@ -238,15 +247,15 @@ public async Task<ICollection<WorkflowExecutionResult>> StartWorkflowsAsync(stri

var resumeWorkflowOptions = new ResumeWorkflowHostOptions
{
CorrelationId = options.CorrelationId,
BookmarkId = options.BookmarkId,
ActivityId = options.ActivityId,
ActivityNodeId = options.ActivityNodeId,
ActivityInstanceId = options.ActivityInstanceId,
ActivityHash = options.ActivityHash,
Input = options.Input,
Properties = options.Properties,
CancellationTokens = options.CancellationTokens
CorrelationId = options?.CorrelationId,
BookmarkId = options?.BookmarkId,
ActivityId = options?.ActivityId,
ActivityNodeId = options?.ActivityNodeId,
ActivityInstanceId = options?.ActivityInstanceId,
ActivityHash = options?.ActivityHash,
Input = options?.Input,
Properties = options?.Properties,
CancellationTokens = options?.CancellationTokens ?? default
};

await workflowHost.ResumeWorkflowAsync(resumeWorkflowOptions, applicationCancellationToken);
Expand All @@ -266,7 +275,13 @@ public async Task<ICollection<WorkflowExecutionResult>> ResumeWorkflowsAsync(str
var correlationId = options.CorrelationId;
var workflowInstanceId = options.WorkflowInstanceId;
var activityInstanceId = options.ActivityInstanceId;
var filter = new BookmarkFilter { Hash = hash, CorrelationId = correlationId, WorkflowInstanceId = workflowInstanceId, ActivityInstanceId = activityInstanceId };
var filter = new BookmarkFilter
{
Hash = hash,
CorrelationId = correlationId,
WorkflowInstanceId = workflowInstanceId,
ActivityInstanceId = activityInstanceId
};
var bookmarks = await _bookmarkStore.FindManyAsync(filter, options.CancellationTokens.SystemCancellationToken);

return await ResumeWorkflowsAsync(
Expand Down Expand Up @@ -305,7 +320,10 @@ public async Task<WorkflowExecutionResult> ExecuteWorkflowAsync(WorkflowMatch ma
};

var startResult = await StartWorkflowAsync(collectedStartableWorkflow.DefinitionId!, startOptions);
return startResult with { TriggeredActivityId = collectedStartableWorkflow.ActivityId };
return startResult with
{
TriggeredActivityId = collectedStartableWorkflow.ActivityId
};
}

var collectedResumableWorkflow = (match as ResumableWorkflowMatch)!;
Expand Down Expand Up @@ -363,22 +381,22 @@ public async Task<long> CountRunningWorkflowsAsync(CountRunningWorkflowsRequest
return await _workflowInstanceStore.CountAsync(filter, cancellationToken);
}

private async Task<WorkflowExecutionResult> StartWorkflowAsync(IWorkflowHost workflowHost, StartWorkflowRuntimeOptions options)
private async Task<WorkflowExecutionResult> StartWorkflowAsync(IWorkflowHost workflowHost, StartWorkflowRuntimeOptions? options = default)
{
var workflowInstanceId = string.IsNullOrEmpty(options.InstanceId) ? _identityGenerator.GenerateId() : options.InstanceId;
var cancellationTokens = options.CancellationTokens;
var workflowInstanceId = string.IsNullOrEmpty(options?.InstanceId) ? _identityGenerator.GenerateId() : options?.InstanceId;
var cancellationTokens = options?.CancellationTokens ?? default;

await using (await AcquireLockAsync(workflowInstanceId, cancellationTokens.SystemCancellationToken))
await using (await AcquireLockAsync(workflowInstanceId!, cancellationTokens.SystemCancellationToken))
{
var input = options.Input;
var correlationId = options.CorrelationId;
var input = options?.Input;
var correlationId = options?.CorrelationId;
var startWorkflowOptions = new StartWorkflowHostOptions
{
InstanceId = workflowInstanceId,
CorrelationId = correlationId,
Input = input,
Properties = options.Properties,
TriggerActivityId = options.TriggerActivityId,
Properties = options?.Properties,
TriggerActivityId = options?.TriggerActivityId,
CancellationTokens = cancellationTokens
};
await workflowHost.StartWorkflowAsync(startWorkflowOptions, cancellationTokens.ApplicationCancellationToken);
Expand Down Expand Up @@ -448,7 +466,10 @@ private async Task<IEnumerable<WorkflowMatch>> FindStartableWorkflowsAsync(Workf
var sharedResource = $"{nameof(DefaultWorkflowRuntime)}__StartTriggeredWorkflows__{hash}";
await using (await AcquireLockAsync(sharedResource, cancellationToken))
{
var filter = new TriggerFilter { Hash = hash };
var filter = new TriggerFilter
{
Hash = hash
};
var triggers = await _triggerStore.FindManyAsync(filter, cancellationToken);

foreach (var trigger in triggers)
Expand Down Expand Up @@ -480,7 +501,12 @@ private async Task<IEnumerable<WorkflowMatch>> FindResumableWorkflowsAsync(Workf
var hash = _hasher.Hash(workflowsFilter.ActivityTypeName, workflowsFilter.BookmarkPayload);
var correlationId = workflowsFilter.Options.CorrelationId;
var workflowInstanceId = workflowsFilter.Options.WorkflowInstanceId;
var filter = new BookmarkFilter { Hash = hash, CorrelationId = correlationId, WorkflowInstanceId = workflowInstanceId };
var filter = new BookmarkFilter
{
Hash = hash,
CorrelationId = correlationId,
WorkflowInstanceId = workflowInstanceId
};
var bookmarks = await _bookmarkStore.FindManyAsync(filter, cancellationToken);
var collectedWorkflows = bookmarks.Select(b => new ResumableWorkflowMatch(b.WorkflowInstanceId, default, correlationId, b.BookmarkId, b.Payload)).ToList();
return collectedWorkflows;
Expand Down
3 changes: 3 additions & 0 deletions test/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Alterations\Elsa.Alterations.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
</ItemGroup>

<ItemGroup>
<Folder Include="Workflows\" />
</ItemGroup>

<ItemGroup>
<None Update="Workflows\version-1.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Workflows\version-2.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
Loading

0 comments on commit 60a349c

Please sign in to comment.