Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public async Task<DispatchWorkflowResponse> DispatchAsync(DispatchWorkflowDefini
ParentWorkflowInstanceId = request.ParentWorkflowInstanceId,
};

await commandSender.SendAsync(command, CommandStrategy.Background, CreateHeaders(), cancellationToken);
// Background commands run independently of caller's lifecycle.
await commandSender.SendAsync(command, CommandStrategy.Background, CreateHeaders(), CancellationToken.None);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider applying this same pattern to BackgroundStimulusDispatcher and BackgroundWorkflowCancellationDispatcher for consistency, as they also dispatch background commands that should run independently of caller lifecycle.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

var response = DispatchWorkflowResponse.Success();

// Emit dispatched notification
Expand All @@ -52,7 +53,8 @@ public async Task<DispatchWorkflowResponse> DispatchAsync(DispatchWorkflowInstan
Properties = request.Properties,
CorrelationId = request.CorrelationId};

await commandSender.SendAsync(command, CommandStrategy.Background, CreateHeaders(), cancellationToken);
// Background commands run independently of caller's lifecycle.
await commandSender.SendAsync(command, CommandStrategy.Background, CreateHeaders(), CancellationToken.None);
var response = DispatchWorkflowResponse.Success();

// Emit dispatched notification
Expand All @@ -72,7 +74,9 @@ public async Task<DispatchWorkflowResponse> DispatchAsync(DispatchTriggerWorkflo
Input = request.Input,
Properties = request.Properties
};
await commandSender.SendAsync(command, CommandStrategy.Background, CreateHeaders(), cancellationToken);

// Background commands run independently of caller's lifecycle.
await commandSender.SendAsync(command, CommandStrategy.Background, CreateHeaders(), CancellationToken.None);
return DispatchWorkflowResponse.Success();
}

Expand All @@ -86,7 +90,9 @@ public async Task<DispatchWorkflowResponse> DispatchAsync(DispatchResumeWorkflow
ActivityInstanceId = request.ActivityInstanceId,
Input = request.Input
};
await commandSender.SendAsync(command, CommandStrategy.Background, CreateHeaders(), cancellationToken);

// Background commands run independently of caller's lifecycle.
await commandSender.SendAsync(command, CommandStrategy.Background, CreateHeaders(), CancellationToken.None);
return DispatchWorkflowResponse.Success();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using Elsa.Common.Multitenancy;
using Elsa.Mediator;
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Runtime.Commands;
using Elsa.Workflows.Runtime.Requests;
using NSubstitute;

namespace Elsa.Workflows.Runtime.UnitTests.Services;

public class BackgroundWorkflowDispatcherTests
{
private readonly ICommandSender _commandSender = Substitute.For<ICommandSender>();
private readonly INotificationSender _notificationSender = Substitute.For<INotificationSender>();
private readonly ITenantAccessor _tenantAccessor = Substitute.For<ITenantAccessor>();

[Fact]
public async Task DispatchWorkflowDefinition_DoesNotPropagateCallerToken()
{
// Arrange
var dispatcher = CreateDispatcher();
var request = new DispatchWorkflowDefinitionRequest("test-definition-id");
using var callerCts = new CancellationTokenSource();

// Act
await dispatcher.DispatchAsync(request, cancellationToken: callerCts.Token);

// Assert
await _commandSender.Received(1).SendAsync(
Arg.Any<DispatchWorkflowDefinitionCommand>(),
CommandStrategy.Background,
Arg.Any<IDictionary<object, object>>(),
CancellationToken.None);
}

[Fact]
public async Task DispatchWorkflowInstance_DoesNotPropagateCallerToken()
{
// Arrange
var dispatcher = CreateDispatcher();
var request = new DispatchWorkflowInstanceRequest("test-instance-id");
using var callerCts = new CancellationTokenSource();

// Act
await dispatcher.DispatchAsync(request, cancellationToken: callerCts.Token);

// Assert
await _commandSender.Received(1).SendAsync(
Arg.Any<DispatchWorkflowInstanceCommand>(),
CommandStrategy.Background,
Arg.Any<IDictionary<object, object>>(),
CancellationToken.None);
}

[Fact]
public async Task DispatchTriggerWorkflows_DoesNotPropagateCallerToken()
{
// Arrange
var dispatcher = CreateDispatcher();
var request = new DispatchTriggerWorkflowsRequest("TestActivityType", new { });
using var callerCts = new CancellationTokenSource();

// Act
await dispatcher.DispatchAsync(request, cancellationToken: callerCts.Token);

// Assert
await _commandSender.Received(1).SendAsync(
Arg.Any<DispatchTriggerWorkflowsCommand>(),
CommandStrategy.Background,
Arg.Any<IDictionary<object, object>>(),
CancellationToken.None);
}

[Fact]
public async Task DispatchResumeWorkflows_DoesNotPropagateCallerToken()
{
// Arrange
var dispatcher = CreateDispatcher();
var request = new DispatchResumeWorkflowsRequest("TestActivityType", new { });
using var callerCts = new CancellationTokenSource();

// Act
await dispatcher.DispatchAsync(request, cancellationToken: callerCts.Token);

// Assert
await _commandSender.Received(1).SendAsync(
Arg.Any<DispatchResumeWorkflowsCommand>(),
CommandStrategy.Background,
Arg.Any<IDictionary<object, object>>(),
CancellationToken.None);
}

private BackgroundWorkflowDispatcher CreateDispatcher()
{
return new(_commandSender, _notificationSender, _tenantAccessor);
}
}
Loading