Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add sample for workflow update #40

Merged
merged 14 commits into from
Mar 6, 2024

Conversation

antmendoza
Copy link
Member

What was changed

Why?

Checklist

  1. Closes

  2. How was this tested:

  1. Any docs updates needed?

@antmendoza antmendoza requested a review from cretz November 1, 2023 18:50
@antmendoza antmendoza marked this pull request as draft November 1, 2023 18:52
@antmendoza
Copy link
Member Author

@cretz I am trying to implement an example for workflow update, in test environment the [WorkflowUpdate] method never gets executed.

this is the stacktrace

[xUnit.net 00:00:00.35]     TemporalioSamples.Tests.Encryption.CodecServer.CodecServerTests.CodecServer_WithEncryptionCodec_WorksProperly [SKIP]
  Skipped TemporalioSamples.Tests.Encryption.CodecServer.CodecServerTests.CodecServer_WithEncryptionCodec_WorksProperly [1 ms]
[xUnit.net 00:00:30.61]     TemporalioSamples.Tests.WorkflowUpdate.MyWorkflowTests.RunAsync_SimpleRun_Succeeds [FAIL]
  Failed TemporalioSamples.Tests.WorkflowUpdate.MyWorkflowTests.RunAsync_SimpleRun_Succeeds [30 s]
  Error Message:
   Temporalio.Exceptions.RpcException : Timeout expired
  Stack Trace:
     at Temporalio.Bridge.Client.CallAsync[T](RpcService service, String rpc, IMessage req, MessageParser`1 resp, Boolean retry, IEnumerable`1 metadata, Nullable`1 timeout, Nullable`1 cancellationToken)
   at Temporalio.Client.TemporalConnection.InvokeRpcAsync[T](RpcService service, String rpc, IMessage req, MessageParser`1 resp, RpcOptions options)
   at Temporalio.Client.WorkflowService.UpdateWorkflowExecutionAsync(UpdateWorkflowExecutionRequest req, RpcOptions options)
   at Temporalio.Client.TemporalClient.Impl.StartWorkflowUpdateAsync[TResult](StartWorkflowUpdateInput input)
   at Temporalio.Client.WorkflowHandle`1.ExecuteUpdateAsync[TUpdateResult](Expression`1 updateCall, WorkflowUpdateOptions options)
   at TemporalioSamples.Tests.WorkflowUpdate.MyWorkflowTests.<>c__DisplayClass1_0.<<RunAsync_SimpleRun_Succeeds>b__0>d.MoveNext() in /Users/antmendoza/dev/temporal/_dotnet/samples-dotnet/tests/WorkflowUpdate/MyWorkflowTests.cs:line 36
--- End of stack trace from previous location ---
   at Temporalio.Worker.TemporalWorker.ExecuteInternalAsync(Func`1 untilComplete, CancellationToken stoppingToken)
   at Temporalio.Worker.TemporalWorker.ExecuteInternalAsync(Func`1 untilComplete, CancellationToken stoppingToken)
   at TemporalioSamples.Tests.WorkflowUpdate.MyWorkflowTests.RunAsync_SimpleRun_Succeeds() in /Users/antmendoza/dev/temporal/_dotnet/samples-dotnet/tests/WorkflowUpdate/MyWorkflowTests.cs:line 25
   at TemporalioSamples.Tests.WorkflowUpdate.MyWorkflowTests.RunAsync_SimpleRun_Succeeds() in /Users/antmendoza/dev/temporal/_dotnet/samples-dotnet/tests/WorkflowUpdate/MyWorkflowTests.cs:line 25
--- End of stack trace from previous location ---

I might be doing something wrong, any idea?

thank you

@antmendoza antmendoza removed the request for review from cretz November 1, 2023 19:03
@cretz
Copy link
Member

cretz commented Nov 1, 2023

I wonder if this is an issue with the time skipping test server. I will set aside some time to debug that, but see if it works with WorkflowEnvironment.StartLocalAsync in the meantime.

@antmendoza
Copy link
Member Author

now that you mention skipping test server , my laptop is not x86/x64 , but the rest of the test are working for me

image

@antmendoza
Copy link
Member Author

await using var env = await WorkflowEnvironment.StartLocalAsync();
works

@cretz
Copy link
Member

cretz commented Nov 2, 2023

Can you check whether all uses of StartTimeSkippingAsync fail for you or just update? I haven't gotten around to investigating this issue yet.

@antmendoza
Copy link
Member Author

@cretz sorry I didn't come back before,

Can you check whether all uses of StartTimeSkippingAsync fail for you or just update?

I am not really sure what you mean with "all uses". Tested

  • query
  • signal
  • DelayAsync
  • WaitConditionAsync

and it works.

Is there anything else I can test?

@cretz
Copy link
Member

cretz commented Nov 7, 2023

Is there anything else I can test?

No, that confirms that the time-skipping test server does not currently work with update but does with other things. Use StartLocalAsync for now so your PR will go through. If you'd like you can open an issue on the .NET repo (though it is likely a problem w/ the Java test server).

@antmendoza
Copy link
Member Author

antmendoza commented Feb 25, 2024

@cretz sorry forgot about this.. I am working on it again.

For this example, I am implementing a UI wizard. The user submits a screen, the workflow processes the screen (maybe execute some activities) and returns the id of the next screen.

I have a small issue, implementation issue, nothing to do with dotnet,

If I remove this line https://github.com/temporalio/samples-dotnet/pull/40/files#diff-6717af88ce41823890a56df6172522c7503b0829944f308df6cf29549e1404c1R25 the workflow completes before the update handler returns

Event WorkflowExecutionUpdateCompleted is missing

Screenshot 2024-02-25 at 18 49 24

Am I doing something wrong?

@cretz
Copy link
Member

cretz commented Feb 26, 2024

@antmendoza - hrmm, I would expect instant update completion (as failure since workflow completed) upon workflow completion. Can you bring this up internally and tag me (e.g. start discussion with server team)?

@antmendoza antmendoza changed the title [WIP] add sample for workflow update add sample for workflow update Mar 5, 2024
Copy link
Member Author

@antmendoza antmendoza left a comment

Choose a reason for hiding this comment

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

the PR is ready @cretz ,

thank you

/// </remarks>
public sealed class TimeSkippingServerFactAttribute : FactAttribute
{
public TimeSkippingServerFactAttribute()
{
if (RuntimeInformation.ProcessArchitecture != Architecture.X86 &&
RuntimeInformation.ProcessArchitecture != Architecture.X64)
var processArchitecture = RuntimeInformation.ProcessArchitecture;
Copy link
Member Author

Choose a reason for hiding this comment

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

I had to change this file to have some tests working on my laptop, let me know if it makes sense @cretz or you want me to revert it back

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this will fail CI. The problem is your macOS ARM does run fine, but Linux ARM does not. You should revert it back and not use [TimeSkippingServerFact] on your tests since you're not using the time-skipping test server.

@antmendoza antmendoza marked this pull request as ready for review March 5, 2024 12:12
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.

Sorry, added several more comments, but the only two that really matter are the time skipping fact attribute update and the sln file conflict.

/// </remarks>
public sealed class TimeSkippingServerFactAttribute : FactAttribute
{
public TimeSkippingServerFactAttribute()
{
if (RuntimeInformation.ProcessArchitecture != Architecture.X86 &&
RuntimeInformation.ProcessArchitecture != Architecture.X64)
var processArchitecture = RuntimeInformation.ProcessArchitecture;
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this will fail CI. The problem is your macOS ARM does run fine, but Linux ARM does not. You should revert it back and not use [TimeSkippingServerFact] on your tests since you're not using the time-skipping test server.

Copy link
Member

Choose a reason for hiding this comment

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

This file is conflicting with main. Since it's kinda an opaque file, you may have to merge, accept main's version, and then dotnet add this project again

@@ -0,0 +1,3 @@
namespace TemporalioSamples.WorkflowUpdate;

public record UiRequest(string RequestId, ScreenId ScreenId);
Copy link
Member

Choose a reason for hiding this comment

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

Could have also just made this and ScreenId as inner classes of the workflow itself, but this is fine too

Comment on lines 52 to 56
[WorkflowQuery]
public ScreenId GetCurrentScreen()
{
return screen;
}
Copy link
Member

Choose a reason for hiding this comment

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

May be clearer to make this a property and remove the screen field (queries as property getters is a neat .NET feature).

Suggested change
[WorkflowQuery]
public ScreenId GetCurrentScreen()
{
return screen;
}
[WorkflowQuery]
public ScreenId CurrentScreen { get; private set; } = ScreenId.Screen1;

Comment on lines 41 to 42
await Workflow.WaitConditionAsync(() => !requests.Any());
updateInProgress = true;
Copy link
Member

Choose a reason for hiding this comment

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

If you're counting on one in-flight update at a time, might as well not use a queue, but instead just have a private UiRequest? pendingRequest field that you set.

Comment on lines 22 to 25
}

// Ensure update method has completed
await Workflow.WaitConditionAsync(() => !updateInProgress);
Copy link
Member

Choose a reason for hiding this comment

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

Might as well do the per-update wait in the loop for each update

Suggested change
}
// Ensure update method has completed
await Workflow.WaitConditionAsync(() => !updateInProgress);
// Ensure update method has completed
await Workflow.WaitConditionAsync(() => !updateInProgress);
}

return screen;
}

private bool IsLastScreen()
Copy link
Member

Choose a reason for hiding this comment

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

Can probably inline this single-use one-liner into the while loop

private bool updateInProgress;

[WorkflowRun]
public async Task RunAsync()
Copy link
Member

Choose a reason for hiding this comment

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

Technically it may be clearer to have the update handler have its own logic and if it needs to have a homemade lock to prevent other updates from running using a bool field like you have, so be it, and the run function just be public Task RunAsync() => Workflow.WaitConditionAsync(() => CurrentScreen == ScreenId.End), but what you have here is fine too.

@antmendoza antmendoza marked this pull request as draft March 6, 2024 05:59
@antmendoza
Copy link
Member Author

antmendoza commented Mar 6, 2024

@cretz thanks, I've implemented the suggested changes. The code looks much clearer now

I've implemented the suggested change

I think, feel free to add more comments if something can be improved

@antmendoza antmendoza self-assigned this Mar 6, 2024
@antmendoza antmendoza requested a review from cretz March 6, 2024 09:10
@antmendoza antmendoza marked this pull request as ready for review March 6, 2024 09:10
@antmendoza antmendoza removed their assignment Mar 6, 2024
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.

LGTM, just minor suggestions, not required

Comment on lines 12 to 15
public async Task RunAsync()
{
await Workflow.WaitConditionAsync(() => currentScreen == ScreenId.End);
}
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 RunAsync()
{
await Workflow.WaitConditionAsync(() => currentScreen == ScreenId.End);
}
public Task RunAsync() => Workflow.WaitConditionAsync(() => currentScreen == ScreenId.End);

Can shorten to this if you want, but don't have to

return currentScreen;
}

private void SetNextScreen(UiRequest currentRequest)
Copy link
Member

Choose a reason for hiding this comment

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

Why make this a separate method? It's just a one-liner

Copy link
Member Author

Choose a reason for hiding this comment

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

It makes the code easier (for me) to read than having...

        updateInProgress = true;
        // Activities can be scheduled here
        currentScreen = request.ScreenId switch
        {
            ScreenId.Screen1 => ScreenId.Screen2,
            ScreenId.Screen2 => ScreenId.End,
            _ => currentScreen,
        };
        updateInProgress = false;

wdyt?

{
}

[TimeSkippingServerFact]
Copy link
Member

Choose a reason for hiding this comment

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

I think you can remove this attribute since you're not using time-skipping

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried but it does not work? what should I put instead ?

Copy link
Member Author

Choose a reason for hiding this comment

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

ok [Fact] seems to work

Copy link
Member

@cretz cretz Mar 6, 2024

Choose a reason for hiding this comment

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

Yes, sorry, [Fact] required for all tests (the [TimeSkippingServerFact] is just a special kind)

});
}

[TimeSkippingServerFact]
Copy link
Member

Choose a reason for hiding this comment

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

I think you can remove this attribute since you're not using time-skipping

private ScreenId currentScreen = ScreenId.Screen1;

[WorkflowRun]
public async Task RunAsync() => await Workflow.WaitConditionAsync(() => currentScreen == ScreenId.End);
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 RunAsync() => await Workflow.WaitConditionAsync(() => currentScreen == ScreenId.End);
public Task RunAsync() => Workflow.WaitConditionAsync(() => currentScreen == ScreenId.End);

Probably could be this, but what you have is good too

@cretz
Copy link
Member

cretz commented Mar 6, 2024

Looks great, merge whenever.

@antmendoza antmendoza merged commit 49c0c06 into temporalio:main Mar 6, 2024
6 checks passed
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