Skip to content

Commit c703da9

Browse files
authored
Add type-safe SignalWithStart (#193)
1 parent 66abe70 commit c703da9

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,8 @@ Some things to note about the above code:
519519
`StartWorkflowAsync` would be a compile-time failure.
520520
* The `handle` is also typed with the workflow result, so `GetResultAsync()` returns a `string` as expected.
521521
* A shortcut extension `ExecuteWorkflowAsync` is available that is just `StartWorkflowAsync` + `GetResultAsync`.
522+
* `SignalWithStart` method is present on the workflow options to make the workflow call a signal-with-start call which
523+
means it will only start the workflow if it's not running, but send a signal to it regardless.
522524

523525
#### Invoking Activities
524526

src/Temporalio/Client/WorkflowOptions.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq.Expressions;
4+
using System.Threading.Tasks;
35
using Temporalio.Api.Enums.V1;
46
using Temporalio.Common;
57

@@ -105,6 +107,32 @@ public WorkflowOptions(string id, string taskQueue)
105107
/// </summary>
106108
public RpcOptions? Rpc { get; set; }
107109

110+
/// <summary>
111+
/// Perform a signal-with-start which will only start the workflow if it's not already
112+
/// running, but send a signal to it regardless. This is just sugar for manually setting
113+
/// <see cref="StartSignal" /> and <see cref="StartSignalArgs" /> directly.
114+
/// </summary>
115+
/// <typeparam name="TWorkflow">Workflow class type.</typeparam>
116+
/// <param name="signalCall">Invocation or a workflow signal method.</param>
117+
public void SignalWithStart<TWorkflow>(Expression<Func<TWorkflow, Task>> signalCall)
118+
{
119+
var (method, args) = ExpressionUtil.ExtractCall(signalCall);
120+
SignalWithStart(Workflows.WorkflowSignalDefinition.NameFromMethodForCall(method), args);
121+
}
122+
123+
/// <summary>
124+
/// Perform a signal-with-start which will only start the workflow if it's not already
125+
/// running, but send a signal to it regardless. This is just sugar for manually setting
126+
/// <see cref="StartSignal" /> and <see cref="StartSignalArgs" /> directly.
127+
/// </summary>
128+
/// <param name="signal">Signal name.</param>
129+
/// <param name="args">Signal args.</param>
130+
public void SignalWithStart(string signal, IReadOnlyCollection<object?> args)
131+
{
132+
StartSignal = signal;
133+
StartSignalArgs = args;
134+
}
135+
108136
/// <summary>
109137
/// Create a shallow copy of these options.
110138
/// </summary>

tests/Temporalio.Tests/Worker/WorkflowWorkerTests.cs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,43 @@ await handle.SignalAsync(
956956
});
957957
}
958958

959+
[Fact]
960+
public async Task ExecuteWorkflowAsync_Signals_SignalWithStart()
961+
{
962+
await ExecuteWorkerAsync<SignalWorkflow>(async worker =>
963+
{
964+
// Start the workflow with a signal
965+
var options = new WorkflowOptions(
966+
id: $"workflow-{Guid.NewGuid()}",
967+
taskQueue: worker.Options.TaskQueue!);
968+
options.SignalWithStart((SignalWorkflow wf) => wf.Signal1Async("signalval1"));
969+
var handle = await Env.Client.StartWorkflowAsync(
970+
(SignalWorkflow wf) => wf.RunAsync(),
971+
options);
972+
// Confirm signal received
973+
Assert.Equal(
974+
new List<string>
975+
{
976+
"Signal1: signalval1",
977+
},
978+
await handle.QueryAsync(wf => wf.Events()));
979+
// Do it again, confirm signal received on same workflow
980+
options.SignalWithStart((SignalWorkflow wf) => wf.Signal1Async("signalval2"));
981+
var newHandle = await Env.Client.StartWorkflowAsync(
982+
(SignalWorkflow wf) => wf.RunAsync(),
983+
options);
984+
Assert.Equal(handle.ResultRunId, newHandle.ResultRunId);
985+
// Confirm signal received
986+
Assert.Equal(
987+
new List<string>
988+
{
989+
"Signal1: signalval1",
990+
"Signal1: signalval2",
991+
},
992+
await handle.QueryAsync(wf => wf.Events()));
993+
});
994+
}
995+
959996
[Workflow]
960997
public class BadSignalArgsDroppedWorkflow
961998
{
@@ -3939,8 +3976,12 @@ private async Task ExecuteWorkerAsync<TWf>(
39393976
TemporalWorkerOptions? options = null,
39403977
IWorkerClient? client = null)
39413978
{
3942-
await ExecuteWorkerAsyncReturning<TWf, bool>(
3943-
(w) => action(w).ContinueWith(t => true),
3979+
await ExecuteWorkerAsyncReturning<TWf, ValueTuple>(
3980+
async (w) =>
3981+
{
3982+
await action(w);
3983+
return ValueTuple.Create();
3984+
},
39443985
options,
39453986
client);
39463987
}

0 commit comments

Comments
 (0)