diff --git a/src/Temporalio/Client/TemporalClient.Workflow.cs b/src/Temporalio/Client/TemporalClient.Workflow.cs index 0cfad9ed..ca04427b 100644 --- a/src/Temporalio/Client/TemporalClient.Workflow.cs +++ b/src/Temporalio/Client/TemporalClient.Workflow.cs @@ -439,6 +439,10 @@ private async Task> StartWorkflowInternalAsyn { req.SearchAttributes = input.Options.TypedSearchAttributes.ToProto(); } + if (input.Options.StartDelay is { } startDelay) + { + req.WorkflowStartDelay = Duration.FromTimeSpan(startDelay); + } if (input.Headers != null) { req.Header = new(); @@ -489,6 +493,7 @@ private async Task> StartWorkflowInternalAsyn Memo = req.Memo, SearchAttributes = req.SearchAttributes, Header = req.Header, + WorkflowStartDelay = req.WorkflowStartDelay, SignalName = input.Options.StartSignal, }; if (input.Options.StartSignalArgs != null && input.Options.StartSignalArgs.Count > 0) diff --git a/src/Temporalio/Client/WorkflowOptions.cs b/src/Temporalio/Client/WorkflowOptions.cs index 3b89c6ba..2d10b28f 100644 --- a/src/Temporalio/Client/WorkflowOptions.cs +++ b/src/Temporalio/Client/WorkflowOptions.cs @@ -80,6 +80,13 @@ public WorkflowOptions(string id, string taskQueue) /// public SearchAttributeCollection? TypedSearchAttributes { get; set; } + /// + /// Gets or sets the amount of time to wait before starting the workflow. + /// + /// Start delay does not work with . + /// WARNING: Start delay is experimental. + public TimeSpan? StartDelay { get; set; } + /// /// Gets or sets the start signal for the workflow. If this is non-null, a signal-with-start /// is used instead of a traditional workflow start. This means the workflow will only be diff --git a/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs b/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs index 6bc327b3..36f907de 100644 --- a/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs +++ b/tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs @@ -83,6 +83,45 @@ await Client.StartWorkflowAsync( }); } + [Fact] + public async Task StartWorkflowAsync_StartDelay_WaitsProperly() + { + var arg = new KSWorkflowParams(new KSAction(Result: new(Value: "Some String"))); + var handle = await Client.StartWorkflowAsync( + (IKitchenSinkWorkflowWithUnknownReturn wf) => wf.RunAsync(arg), + new(id: $"workflow-{Guid.NewGuid()}", taskQueue: Env.KitchenSinkWorkerTaskQueue) + { + StartDelay = TimeSpan.FromMinutes(45), + }); + // Check that first event has start delay + await using var enumerator = handle.FetchHistoryEventsAsync().GetAsyncEnumerator(); + Assert.True(await enumerator.MoveNextAsync()); + var attrs = enumerator.Current.WorkflowExecutionStartedEventAttributes; + Assert.Equal( + TimeSpan.FromMinutes(45), + enumerator.Current.WorkflowExecutionStartedEventAttributes.FirstWorkflowTaskBackoff.ToTimeSpan()); + } + + [Fact] + public async Task StartWorkflowAsync_SignalWithStartDelay_WaitsProperly() + { + var arg = new KSWorkflowParams(new KSAction(Result: new(Value: "Some String"))); + var handle = await Client.StartWorkflowAsync( + (IKitchenSinkWorkflowWithUnknownReturn wf) => wf.RunAsync(arg), + new(id: $"workflow-{Guid.NewGuid()}", taskQueue: Env.KitchenSinkWorkerTaskQueue) + { + StartDelay = TimeSpan.FromMinutes(45), + StartSignal = "some-signal", + }); + // Check that first event has start delay + await using var enumerator = handle.FetchHistoryEventsAsync().GetAsyncEnumerator(); + Assert.True(await enumerator.MoveNextAsync()); + var attrs = enumerator.Current.WorkflowExecutionStartedEventAttributes; + Assert.Equal( + TimeSpan.FromMinutes(45), + enumerator.Current.WorkflowExecutionStartedEventAttributes.FirstWorkflowTaskBackoff.ToTimeSpan()); + } + [Fact] public async Task StartWorkflowAsync_StartSignal_Succeeds() { diff --git a/tests/Temporalio.Tests/Testing/WorkflowEnvironmentTests.cs b/tests/Temporalio.Tests/Testing/WorkflowEnvironmentTests.cs index 41873f28..3bb60884 100644 --- a/tests/Temporalio.Tests/Testing/WorkflowEnvironmentTests.cs +++ b/tests/Temporalio.Tests/Testing/WorkflowEnvironmentTests.cs @@ -156,9 +156,8 @@ await worker.ExecuteAsync(async () => new(id: $"workflow-{Guid.NewGuid()}", taskQueue: worker.Options.TaskQueue!))); // Check causes too var actExc = Assert.IsType(exc.InnerException); - var toExc1 = Assert.IsType(actExc.InnerException); - var toExc2 = Assert.IsType(toExc1.InnerException); - Assert.Equal(TimeoutType.Heartbeat, toExc2.TimeoutType); + var toExc = Assert.IsType(actExc.InnerException); + Assert.Equal(TimeoutType.Heartbeat, toExc.TimeoutType); }); }