Skip to content

add sleep-for-days sample#95

Merged
THardy98 merged 7 commits intomainfrom
sleep-for-days
Feb 5, 2025
Merged

add sleep-for-days sample#95
THardy98 merged 7 commits intomainfrom
sleep-for-days

Conversation

@THardy98
Copy link
Contributor

@THardy98 THardy98 commented Feb 1, 2025

added sleep-for-days sample

@THardy98 THardy98 requested a review from cretz February 1, 2025 06:22
@THardy98
Copy link
Contributor Author

THardy98 commented Feb 1, 2025

This is pretty much done - I'm just having trouble with the:
await env.DelayAsync(TimeSpan.FromDays(90));

it's not actually time-skipping and I'm not sure why given that it's essentially following what's in the docs

I've made the assertion following that statement to fail, just to make sure that this doesn't pass CI.

Copy link
Member

Choose a reason for hiding this comment

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

Class name should match file name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed class to Activities

using Temporalio.Workflows;

[Workflow]
public class SleepForDaysWorkflow
Copy link
Member

Choose a reason for hiding this comment

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

Class name should match pre-extension part of file name (should probably change file name in this case)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed file to SleepForDaysWorkflow.workflow.cs, looks a bit odd.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, though that same seemingly-odd name is present on similar files/workflows in this repo

Copy link
Member

@cretz cretz Feb 3, 2025

Choose a reason for hiding this comment

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

Suggested change
isComplete = false;

This is dangerous because it will overwrite a signal that comes before the workflow starts (and bool fields default to false anyways)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, didn't consider this. Don't think this is a real concern with this sample, but I get that this might not be a pattern we want to demonstrate to new users. Removed.

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private bool isComplete;
private bool complete;

"is" prefix not usually needed in .NET

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed prefix.

Copy link
Member

Choose a reason for hiding this comment

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

Can be a bit confusing to users for the send-email subject to be what the workflow is doing next, but not a big deal

Comment on lines 20 to 22
Copy link
Member

Choose a reason for hiding this comment

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

There is an overload of WaitConditionAsync that accepts a timeout

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We considered this, but figured having a direct sleep more clearly showed the ability to wait for long periods of time.

Copy link
Member

@cretz cretz Feb 3, 2025

Choose a reason for hiding this comment

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

The concern here is that we usually encourage samples to look like a user should write them. It also depends on if we're making a simple sample like for the website or not. If so and we want a clear "sleep" for this sample, we should consider using workflow cancellation as the interrupt mechanism because, even though we may not recommend cancellation as control flow, it makes the sample much more legible to a first-time viewer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah that's true. The intention is for this to be a reference for the web sample and the discussion on Slack leaned towards using the workflow function and stripping out the signal stuff if it's too complicated.

But imo I agree. I err on the side of no interrupt mechanism at all, would be the simplest to read for a new viewer. But we have some consensus on this already and some samples already merged. I don't think it's worth re-opening the discussion or being inconsistent with the other samples, especially given the scope.

Copy link
Member

Choose a reason for hiding this comment

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

Won't hold up this PR on this FWIW, but when we get to the public site content, I think it may not look like a sample

Copy link
Contributor Author

@THardy98 THardy98 Feb 4, 2025

Choose a reason for hiding this comment

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

Yeah I think that's fair. Marketing team asked for PRs yesterday which I shared, with a little addendum that if they want simpler examples I can send them code snippets with the signal handling stuff removed. I don't think it's a bad thing that the actual sample has the signal handling, it's useful.

Comment on lines 29 to 32
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public async Task CompleteAsync()
{
isComplete = true;
}
public async Task CompleteAsync() => isComplete = true;

Can shorten some of these one-liners if you want

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Shortened

Comment on lines +44 to +45
// Sleep for 90 days
await env.DelayAsync(TimeSpan.FromDays(90));
Copy link
Member

Choose a reason for hiding this comment

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

Are you sure you're not time skipping before the workflow is even waiting for the number of days? You may be time skipping before the workflow even reaches the wait condition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I hadn't considered that. Is there a way to test for this? Maybe with a query?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, it's a bit rough, but that's what we've done in other situations is waited for a workflow to reach a certain state. One way is to have the activity you're mocking let you know at least that part has been reached. But otherwise, yeah a loop until history has a timer event may be best here.

Copy link
Contributor Author

@THardy98 THardy98 Feb 4, 2025

Choose a reason for hiding this comment

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

Added a check that the timer exists before time-skipping. That fixed the time-skipping issue I mentioned at the beginning, seems like that was indeed the cause of the issue. Thanks!

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
await Task.WhenAny(
await Workflow.WhenAnyAsync(

Even though Task.WhenAny( technically works here in most cases, we encourage the safer wrapper

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Didn't realize there was a workflow wrapper, switched.

Copy link
Member

Choose a reason for hiding this comment

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

Make sure it's WhenAnyAsync

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated

Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

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

Also, make sure that this .csproj is added to the primary solution (sln) file. Can see what's there with dotnet sln list and can add with dotnet sln add. Also don't forget to add this sample to README list.

Copy link
Member

Choose a reason for hiding this comment

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

This is failing compilation

Copy link
Contributor Author

@THardy98 THardy98 Feb 4, 2025

Choose a reason for hiding this comment

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

Forgot to push that change, whoops.

Added to sln via dotnet sln add src/SleepForDays, seems to look right.
Added to README as well

@THardy98 THardy98 requested a review from cretz February 4, 2025 16:43
@THardy98
Copy link
Contributor Author

THardy98 commented Feb 4, 2025

Am I correct in thinking that [TimeSkippingServerFact] is used to avoid time skipping on platforms outside of x86/x64?

[xUnit.net 00:00:01.25]       Time-skipping test server only works on x86/x64 platforms

Saw this failure in CI when I had the [Fact] annotation and noticed the [TimeSkippingServerFact] annotation used in ActivitySimple's workflow test.

Comment on lines 47 to 62
Copy link
Member

Choose a reason for hiding this comment

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

The final assert can never be false. Would recommend only looping a certain number of times and sleeping a short bit between each fetch.

Actually, I just realized even in samples we have a AssertMore.EventuallyAsync helper, so you could probably just have:

await AssertMore.EventuallyAsync(async () =>
{
    var history = await handle.FetchHistoryAsync();
    Assert.Contains(history.Events, e => e.TimerStartedEventAttributes != null);
});

(untested but the idea is there)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh much nicer. I've also updated the activity execution count assertions. By the time the timer event for the sleep/delay is in history, we should have already executed the activity once. Skipping 90 more days should then give us 4 total executions not 3, which was erroneous. I wonder why that passed, if the timer existed in history before, then we still would have had to execute the activity, we still should have had 4 executions not 3.

@cretz
Copy link
Member

cretz commented Feb 4, 2025

Am I correct in thinking that [TimeSkippingServerFact] is used to avoid time skipping on platforms outside of x86/x64?

Yes, though we should probably relax it one day for macOS since its translation layer can run x64 binaries

Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

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

Looks great, feel free to merge if/when CI passes

@THardy98 THardy98 merged commit 98bb677 into main Feb 5, 2025
7 checks passed
@THardy98
Copy link
Contributor Author

THardy98 commented Feb 5, 2025

TYFR!

@cretz cretz deleted the sleep-for-days branch February 5, 2025 14:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments