Skip to content

Commit 98bb677

Browse files
authored
add sleep-for-days sample (temporalio#95)
* add sleep-for-days sample * address pr comments * WhenAny -> WhenAnyAsync * fixed test - added check for sleep timer start before time-skipping * ran dotnet format * changed [Fact] -> [TimeSkippingServerFact] * use to check for eventuality of timer history event
1 parent 16f24a9 commit 98bb677

9 files changed

Lines changed: 202 additions & 0 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Prerequisites:
3030
* [Saga](src/Saga) - Demonstrates how to implement a saga pattern.
3131
* [Schedules](src/Schedules) - How to schedule workflows to be run at specific times in the future.
3232
* [SignalsQueries](src/SignalsQueries) - A loyalty program using Signals and Queries.
33+
* [SleepForDays](src/SleepForDays/) - Use a timer to send an email every 30 days.
3334
* [Timer](src/Timer) - Use a timer to implement a monthly subscription; handle workflow cancellation.
3435
* [UpdateWithStartEarlyReturn](src/UpdateWithStartEarlyReturn) - Use update with start to get an early return, letting the rest of the workflow complete in the background.
3536
* [UpdateWithStartLazyInit](src/UpdateWithStartLazyInit) - Use update with start to lazily start a workflow before sending update.

TemporalioSamples.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.UpdateWit
7979
EndProject
8080
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.UpdateWithStartEarlyReturn", "src\UpdateWithStartEarlyReturn\TemporalioSamples.UpdateWithStartEarlyReturn.csproj", "{69C72FEE-D1A1-4F3F-9B1A-462C9FF91B16}"
8181
EndProject
82+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.SleepForDays", "src\SleepForDays\TemporalioSamples.SleepForDays.csproj", "{60EDA2A6-04ED-460F-9EB1-EC397B095E41}"
83+
EndProject
8284
Global
8385
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8486
Debug|Any CPU = Debug|Any CPU
@@ -221,6 +223,10 @@ Global
221223
{69C72FEE-D1A1-4F3F-9B1A-462C9FF91B16}.Debug|Any CPU.Build.0 = Debug|Any CPU
222224
{69C72FEE-D1A1-4F3F-9B1A-462C9FF91B16}.Release|Any CPU.ActiveCfg = Release|Any CPU
223225
{69C72FEE-D1A1-4F3F-9B1A-462C9FF91B16}.Release|Any CPU.Build.0 = Release|Any CPU
226+
{60EDA2A6-04ED-460F-9EB1-EC397B095E41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
227+
{60EDA2A6-04ED-460F-9EB1-EC397B095E41}.Debug|Any CPU.Build.0 = Debug|Any CPU
228+
{60EDA2A6-04ED-460F-9EB1-EC397B095E41}.Release|Any CPU.ActiveCfg = Release|Any CPU
229+
{60EDA2A6-04ED-460F-9EB1-EC397B095E41}.Release|Any CPU.Build.0 = Release|Any CPU
224230
EndGlobalSection
225231
GlobalSection(SolutionProperties) = preSolution
226232
HideSolutionNode = FALSE
@@ -260,5 +266,6 @@ Global
260266
{AE18875F-B7D2-4A3C-8784-15B76277D508} = {5339989C-3791-4D75-A9F1-42620C443D4A}
261267
{B99AA6E8-4224-4BAB-84BC-0A6D6095B9BA} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC}
262268
{69C72FEE-D1A1-4F3F-9B1A-462C9FF91B16} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC}
269+
{60EDA2A6-04ED-460F-9EB1-EC397B095E41} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC}
263270
EndGlobalSection
264271
EndGlobal

src/SleepForDays/Activities.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace TemporalioSamples.SleepForDays;
2+
3+
using Microsoft.Extensions.Logging;
4+
using Temporalio.Activities;
5+
6+
public class Activities
7+
{
8+
// Stub for an actual implementation for sending emails.
9+
[Activity]
10+
public void SendEmail(string msg)
11+
{
12+
ActivityExecutionContext.Current.Logger.LogInformation("{Msg}", msg);
13+
}
14+
}

src/SleepForDays/Program.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Microsoft.Extensions.Logging;
2+
using Temporalio.Client;
3+
using Temporalio.Worker;
4+
using TemporalioSamples.SleepForDays;
5+
6+
// Create a client to localhost on default namespace
7+
var client = await TemporalClient.ConnectAsync(new("localhost:7233")
8+
{
9+
LoggerFactory = LoggerFactory.Create(builder =>
10+
builder.
11+
AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] ").
12+
SetMinimumLevel(LogLevel.Information)),
13+
});
14+
15+
async Task RunWorkerAsync()
16+
{
17+
// Cancellation token cancelled on ctrl+c
18+
using var tokenSource = new CancellationTokenSource();
19+
Console.CancelKeyPress += (_, eventArgs) =>
20+
{
21+
tokenSource.Cancel();
22+
eventArgs.Cancel = true;
23+
};
24+
25+
// Create an activity instance with some state
26+
var activities = new Activities();
27+
28+
// Run worker until cancelled
29+
Console.WriteLine("Running worker");
30+
using var worker = new TemporalWorker(
31+
client,
32+
new TemporalWorkerOptions(taskQueue: "sleep-for-days").
33+
AddActivity(activities.SendEmail).
34+
AddWorkflow<SleepForDaysWorkflow>());
35+
try
36+
{
37+
await worker.ExecuteAsync(tokenSource.Token);
38+
}
39+
catch (OperationCanceledException)
40+
{
41+
Console.WriteLine("Worker cancelled");
42+
}
43+
}
44+
45+
async Task ExecuteWorkflowAsync()
46+
{
47+
Console.WriteLine("Executing workflow");
48+
await client.ExecuteWorkflowAsync(
49+
(SleepForDaysWorkflow wf) => wf.RunAsync(),
50+
new(id: "sleep-for-days-workflow-id", taskQueue: "sleep-for-days"));
51+
}
52+
53+
switch (args.ElementAtOrDefault(0))
54+
{
55+
case "worker":
56+
await RunWorkerAsync();
57+
break;
58+
case "workflow":
59+
await ExecuteWorkflowAsync();
60+
break;
61+
default:
62+
throw new ArgumentException("Must pass 'worker' or 'workflow' as the single argument");
63+
}

src/SleepForDays/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Sleep for days
2+
3+
This sample demonstrates how to create a Temporal workflow that runs forever, sending an email every 30 days.
4+
5+
To run, first see [README.md](../../README.md) for prerequisites. Then, run the following from this directory in a separate terminal to start the worker:
6+
7+
dotnet run worker
8+
9+
Then in another terminal, run the workflow from this directory:
10+
11+
dotnet run workflow
12+
13+
The worker terminal will show logs from running the workflow.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace TemporalioSamples.SleepForDays;
2+
3+
using Microsoft.Extensions.Logging;
4+
using Temporalio.Workflows;
5+
6+
[Workflow]
7+
public class SleepForDaysWorkflow
8+
{
9+
private bool complete;
10+
11+
[WorkflowRun]
12+
public async Task RunAsync()
13+
{
14+
while (!complete)
15+
{
16+
await Workflow.ExecuteActivityAsync(
17+
(Activities act) => act.SendEmail("Sleeping for 30 days"),
18+
new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) });
19+
await Workflow.WhenAnyAsync(
20+
Workflow.DelayAsync(TimeSpan.FromDays(30)),
21+
Workflow.WaitConditionAsync(() => complete));
22+
}
23+
24+
Workflow.Logger.LogInformation("done!");
25+
}
26+
27+
[WorkflowSignal]
28+
public async Task CompleteAsync() => complete = true;
29+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
</PropertyGroup>
6+
7+
</Project>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
namespace TemporalioSamples.Tests.SleepForDays;
2+
3+
using Temporalio.Activities;
4+
using Temporalio.Testing;
5+
using Temporalio.Worker;
6+
using TemporalioSamples.SleepForDays;
7+
using Xunit;
8+
using Xunit.Abstractions;
9+
10+
public class SleepForDaysWorkflowTests : TestBase
11+
{
12+
public SleepForDaysWorkflowTests(ITestOutputHelper output)
13+
: base(output)
14+
{
15+
}
16+
17+
[TimeSkippingServerFact]
18+
public async Task RunAsync_SleepForDays_Succeeds()
19+
{
20+
await using var env = await WorkflowEnvironment.StartTimeSkippingAsync();
21+
var activities = new Activities();
22+
23+
var activityExecutions = 0;
24+
// Mock out the activity to assert number of executions
25+
[Activity]
26+
void SendEmail(string msg)
27+
{
28+
activityExecutions++;
29+
}
30+
31+
using var worker = new TemporalWorker(
32+
env.Client,
33+
new TemporalWorkerOptions("sleep-for-days-task-queue").
34+
AddActivity(SendEmail).
35+
AddWorkflow<SleepForDaysWorkflow>());
36+
37+
var startTime = await env.GetCurrentTimeAsync();
38+
await worker.ExecuteAsync(async () =>
39+
{
40+
var handle = await env.Client.StartWorkflowAsync(
41+
(SleepForDaysWorkflow wf) => wf.RunAsync(),
42+
new(id: $"wf-{Guid.NewGuid()}", taskQueue: worker.Options.TaskQueue!));
43+
44+
// Continuously check history to see if timer has started.
45+
await AssertMore.EventuallyAsync(async () =>
46+
{
47+
var history = await handle.FetchHistoryAsync();
48+
Assert.Contains(history.Events, e =>
49+
e.TimerStartedEventAttributes != null &&
50+
(TimeSpan.FromDays(30) == e.TimerStartedEventAttributes.StartToFireTimeout.ToTimeSpan()));
51+
});
52+
53+
// The sleep timer has started, we should expect an activity execution.
54+
Assert.Equal(1, activityExecutions);
55+
// Sleep for 90 days
56+
await env.DelayAsync(TimeSpan.FromDays(90));
57+
// Expect 3 activity executions
58+
Assert.Equal(4, activityExecutions);
59+
// Signal the workflow to complete
60+
await handle.SignalAsync(wf => wf.CompleteAsync());
61+
// Expect the same number of activity executions
62+
Assert.Equal(4, activityExecutions);
63+
// Assert at least 90 days of time have passed
64+
Assert.True((await env.GetCurrentTimeAsync() - startTime) >= TimeSpan.FromDays(90));
65+
});
66+
}
67+
}

tests/TemporalioSamples.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<ProjectReference Include="..\src\UpdateWithStartEarlyReturn\TemporalioSamples.UpdateWithStartEarlyReturn.csproj" />
3131
<ProjectReference Include="..\src\UpdateWithStartLazyInit\TemporalioSamples.UpdateWithStartLazyInit.csproj" />
3232
<ProjectReference Include="..\src\WorkflowUpdate\TemporalioSamples.WorkflowUpdate.csproj" />
33+
<ProjectReference Include="..\src\SleepForDays\TemporalioSamples.SleepForDays.csproj" />
3334
</ItemGroup>
3435

3536
</Project>

0 commit comments

Comments
 (0)