Skip to content

Commit 1ae6b28

Browse files
committed
Add test to reproduce race condition
If the host is stopped as soon as WorkflowCompleted LifeCycleEvent is raised, some persistence providers cannot persist the 'Completed' state in time.
1 parent 16661ef commit 1ae6b28

File tree

11 files changed

+185
-11
lines changed

11 files changed

+185
-11
lines changed

src/WorkflowCore.Testing/WorkflowTest.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public abstract class WorkflowTest<TWorkflow, TData> : IDisposable
1717
protected IWorkflowHost Host;
1818
protected IPersistenceProvider PersistenceProvider;
1919
protected List<StepError> UnhandledStepErrors = new List<StepError>();
20+
private bool isDisposed;
2021

2122
protected virtual void Setup()
2223
{
@@ -116,9 +117,22 @@ protected TData GetData(string workflowId)
116117
return (TData)instance.Data;
117118
}
118119

120+
protected virtual void Dispose(bool disposing)
121+
{
122+
if (!isDisposed)
123+
{
124+
if (disposing)
125+
{
126+
Host.Stop();
127+
}
128+
isDisposed = true;
129+
}
130+
}
131+
119132
public void Dispose()
120133
{
121-
Host.Stop();
134+
Dispose(disposing: true);
135+
GC.SuppressFinalize(this);
122136
}
123137
}
124138

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using WorkflowCore.Interface;
2+
using WorkflowCore.Models;
3+
using Xunit;
4+
using FluentAssertions;
5+
using WorkflowCore.Testing;
6+
using WorkflowCore.Models.LifeCycleEvents;
7+
using System.Threading.Tasks;
8+
using System.Threading;
9+
using Moq;
10+
11+
namespace WorkflowCore.IntegrationTests.Scenarios
12+
{
13+
public class StopScenario : WorkflowTest<StopScenario.StopWorkflow, object>
14+
{
15+
public class StopWorkflow : IWorkflow
16+
{
17+
public string Id => "StopWorkflow";
18+
public int Version => 1;
19+
public void Build(IWorkflowBuilder<object> builder)
20+
{
21+
builder.StartWith(context => ExecutionResult.Next());
22+
}
23+
}
24+
25+
public StopScenario()
26+
{
27+
Setup();
28+
}
29+
30+
[Fact]
31+
public async Task Scenario()
32+
{
33+
var tcs = new TaskCompletionSource<object>();
34+
Host.OnLifeCycleEvent += (evt) => OnLifeCycleEvent(evt, tcs);
35+
var workflowId = StartWorkflow(null);
36+
37+
await tcs.Task;
38+
GetStatus(workflowId).Should().Be(WorkflowStatus.Complete);
39+
}
40+
41+
private async void OnLifeCycleEvent(LifeCycleEvent evt, TaskCompletionSource<object> tcs)
42+
{
43+
if (evt is WorkflowCompleted)
44+
{
45+
await Host.StopAsync(CancellationToken.None);
46+
tcs.SetResult(new());
47+
}
48+
}
49+
50+
protected override void Dispose(bool disposing) { }
51+
}
52+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using Amazon.DynamoDBv2;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using WorkflowCore.IntegrationTests.Scenarios;
5+
using Xunit;
6+
7+
namespace WorkflowCore.Tests.DynamoDB.Scenarios
8+
{
9+
[Collection("DynamoDb collection")]
10+
public class DynamoStopScenario : StopScenario
11+
{
12+
protected override void ConfigureServices(IServiceCollection services)
13+
{
14+
var cfg = new AmazonDynamoDBConfig {ServiceURL = DynamoDbDockerSetup.ConnectionString};
15+
services.AddWorkflow(x => x.UseAwsDynamoPersistence(DynamoDbDockerSetup.Credentials, cfg, "tests-"));
16+
}
17+
}
18+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using WorkflowCore.IntegrationTests.Scenarios;
4+
using Xunit;
5+
6+
namespace WorkflowCore.Tests.MongoDB.Scenarios
7+
{
8+
[Collection("Mongo collection")]
9+
public class MongoStopScenario : StopScenario
10+
{
11+
protected override void ConfigureServices(IServiceCollection services)
12+
{
13+
services.AddWorkflow(x => x.UseMongoDB(MongoDockerSetup.ConnectionString, "integration-tests"));
14+
}
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using WorkflowCore.IntegrationTests.Scenarios;
4+
using Xunit;
5+
6+
namespace WorkflowCore.Tests.MySQL.Scenarios
7+
{
8+
[Collection("Mysql collection")]
9+
public class MysqlStopScenario : StopScenario
10+
{
11+
protected override void ConfigureServices(IServiceCollection services)
12+
{
13+
services.AddWorkflow(x => x.UseMySQL(MysqlDockerSetup.ScenarioConnectionString, true, true));
14+
}
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using WorkflowCore.IntegrationTests.Scenarios;
4+
using Xunit;
5+
6+
namespace WorkflowCore.Tests.PostgreSQL.Scenarios
7+
{
8+
[Collection("Postgres collection")]
9+
public class PostgresStopScenario : StopScenario
10+
{
11+
protected override void ConfigureServices(IServiceCollection services)
12+
{
13+
services.AddWorkflow(x => x.UsePostgreSQL(PostgresDockerSetup.ScenarioConnectionString, true, true));
14+
}
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using WorkflowCore.IntegrationTests.Scenarios;
4+
using Xunit;
5+
6+
namespace WorkflowCore.Tests.Redis.Scenarios
7+
{
8+
[Collection("Redis collection")]
9+
public class RedisStopScenario : StopScenario
10+
{
11+
protected override void ConfigureServices(IServiceCollection services)
12+
{
13+
services.AddWorkflow(x => x.UseRedisPersistence(RedisDockerSetup.ConnectionString, "scenario-"));
14+
}
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using WorkflowCore.IntegrationTests.Scenarios;
4+
using Xunit;
5+
6+
namespace WorkflowCore.Tests.SqlServer.Scenarios
7+
{
8+
[Collection("SqlServer collection")]
9+
public class SqlServerStopScenario : StopScenario
10+
{
11+
protected override void ConfigureServices(IServiceCollection services)
12+
{
13+
services.AddWorkflow(x => x.UseSqlServer(SqlDockerSetup.ScenarioConnectionString, true, true));
14+
}
15+
}
16+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using WorkflowCore.IntegrationTests.Scenarios;
4+
using WorkflowCore.Tests.Sqlite;
5+
using Xunit;
6+
7+
namespace WorkflowCore.Tests.Sqlite.Scenarios
8+
{
9+
[Collection("Sqlite collection")]
10+
public class SqliteStopScenario : StopScenario
11+
{
12+
protected override void ConfigureServices(IServiceCollection services)
13+
{
14+
services.AddWorkflow(x => x.UseSqlite(SqliteSetup.ConnectionString, true));
15+
}
16+
}
17+
}

test/WorkflowCore.Tests.Sqlite/SqliteCollection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class SqliteCollection : ICollectionFixture<SqliteSetup>
1010

1111
public class SqliteSetup : IDisposable
1212
{
13-
public string ConnectionString { get; set; }
13+
public static string ConnectionString { get; set; }
1414

1515
public SqliteSetup()
1616
{

test/WorkflowCore.Tests.Sqlite/SqlitePersistenceProviderFixture.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,11 @@ namespace WorkflowCore.Tests.Sqlite
1010
[Collection("Sqlite collection")]
1111
public class SqlitePersistenceProviderFixture : BasePersistenceFixture
1212
{
13-
string _connectionString;
14-
15-
public SqlitePersistenceProviderFixture(SqliteSetup setup)
16-
{
17-
_connectionString = setup.ConnectionString;
18-
}
19-
2013
protected override IPersistenceProvider Subject
2114
{
2215
get
23-
{
24-
var db = new EntityFrameworkPersistenceProvider(new SqliteContextFactory(_connectionString), true, false);
16+
{
17+
var db = new EntityFrameworkPersistenceProvider(new SqliteContextFactory(SqliteSetup.ConnectionString), true, false);
2518
db.EnsureStoreExists();
2619
return db;
2720
}

0 commit comments

Comments
 (0)