Repository navigation
[DurableTask.ServiceBus] Fixed executionId being ignored in ServiceBusOrchestrationService.WaitForOrchestrationAsync #1403
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
Merged
Bernd Verst (berndverst)
merged 23 commits into
Azure:main
from
davidemontanari:davidemontanari/dtfx-sb-orchestration-wait-executionid
Sep 30, 2026
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
ca5f18d
Fixed executionId usage in WaitForOrchestrationAsync in ServiceBusOrc…
davidemontanari 5a232c6
Marked ContinuedAsNew and Suspended as non-terminal states
davidemontanari 556b30b
Added new tests
davidemontanari b417710
Fixed doc
davidemontanari d9567bb
Merge branch 'main' into davidemontanari/dtfx-sb-orchestration-wait-e…
davidemontanari 1765143
Fixed check when execution is ContinuedAsNew and timeout is TimeSpan.…
davidemontanari a31d3ba
Merge branch 'davidemontanari/dtfx-sb-orchestration-wait-executionid'…
davidemontanari e0a2e2c
Fixed timeout check
davidemontanari 6f416b4
- Fixed timeout validation error message to include TimeSpan.Zero
davidemontanari 4625987
Renamed test file path
davidemontanari 0578772
Fixed test
davidemontanari c698660
Added check on LastUpdatedTime to ignore previous runs
davidemontanari d8a1397
Fixed stale-result race when no executionId is passed
davidemontanari e16cb28
Reverted escalation code
davidemontanari ec1079a
Removed checks
davidemontanari beaa47c
Added check on state from previous run
davidemontanari c7667c3
Preserved the existing store retry policy for execution-specific polling
davidemontanari 62a96d2
Added tests for transient failures when passing executionId
davidemontanari 09442fa
Merge branch 'main' into davidemontanari/dtfx-sb-orchestration-wait-e…
davidemontanari 98ce63b
Renamed test file
davidemontanari 1c8f6bf
Removed check on CreatedTime and short circuit the check on LastUpdat…
davidemontanari a2c61e6
Removed wall-clock dependent code
davidemontanari e82b40a
Merge branch 'main' into davidemontanari/dtfx-sb-orchestration-wait-e…
davidemontanari File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
151 changes: 151 additions & 0 deletions
151
test/DurableTask.ServiceBus.Tests/AzureTableInstanceStoreRetryTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| // ---------------------------------------------------------------------------------- | ||
| // Copyright Microsoft Corporation | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
| // ---------------------------------------------------------------------------------- | ||
|
|
||
| namespace DurableTask.ServiceBus.Tests | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
| using global::Azure; | ||
| using DurableTask.Core; | ||
| using DurableTask.Core.Tracking; | ||
| using DurableTask.ServiceBus.Tracking; | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
|
||
| /// <summary> | ||
| /// Verifies that the execution-id lookup on <see cref="AzureTableInstanceStore"/> retries transient | ||
| /// table failures, matching the instance-id overload. Polling by execution id would otherwise fault | ||
| /// on the first transient error that escapes the storage SDK's own retries. | ||
| /// </summary> | ||
| [TestClass] | ||
| public class AzureTableInstanceStoreRetryTests | ||
| { | ||
| const string InstanceId = "instance-1"; | ||
| const string ExecutionId = "generation-1"; | ||
| const string HubName = "testhub"; | ||
|
|
||
| // Parsed only; constructing a TableServiceClient performs no I/O. | ||
| const string FakeStorageConnectionString = "UseDevelopmentStorage=true"; | ||
|
|
||
| static readonly DateTime BaseTime = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc); | ||
|
|
||
| static AzureTableOrchestrationStateEntity CreateEntity() | ||
| { | ||
| return new AzureTableOrchestrationStateEntity(new OrchestrationState | ||
| { | ||
| OrchestrationInstance = new OrchestrationInstance | ||
| { | ||
| InstanceId = InstanceId, | ||
| ExecutionId = ExecutionId | ||
| }, | ||
| OrchestrationStatus = OrchestrationStatus.Completed, | ||
| CreatedTime = BaseTime, | ||
| LastUpdatedTime = BaseTime, | ||
| Output = "final output" | ||
| }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// A transient failure on the state table must be retried rather than surfaced to the caller. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public async Task GetOrchestrationStateAsync_ByExecutionId_RetriesTransientStateTableFailure() | ||
| { | ||
| var tableClient = new FailOnceTableClient { FailuresBeforeSuccess = 1 }; | ||
| var store = new AzureTableInstanceStore(tableClient); | ||
|
|
||
| OrchestrationStateInstanceEntity state = await store.GetOrchestrationStateAsync(InstanceId, ExecutionId); | ||
|
|
||
| Assert.IsNotNull(state, "A single transient failure must not fault the execution-id lookup."); | ||
| Assert.AreEqual(ExecutionId, state.State.OrchestrationInstance.ExecutionId); | ||
| Assert.AreEqual("final output", state.State.Output); | ||
| Assert.AreEqual(2, tableClient.StateQueryCount, "Expected one failed attempt followed by a successful retry."); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// The JumpStart fallback is a separate query and needs the same protection: without it a | ||
| /// transient failure there faults the lookup even though the state table responded fine. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public async Task GetOrchestrationStateAsync_ByExecutionId_RetriesTransientJumpStartTableFailure() | ||
| { | ||
| var tableClient = new FailOnceTableClient | ||
| { | ||
| // Force the JumpStart fallback by leaving the state table empty. | ||
| StateTableIsEmpty = true, | ||
| JumpStartFailuresBeforeSuccess = 1 | ||
| }; | ||
|
|
||
| var store = new AzureTableInstanceStore(tableClient); | ||
|
|
||
| OrchestrationStateInstanceEntity state = await store.GetOrchestrationStateAsync(InstanceId, ExecutionId); | ||
|
|
||
| Assert.IsNotNull(state, "A single transient failure must not fault the JumpStart fallback."); | ||
| Assert.AreEqual(ExecutionId, state.State.OrchestrationInstance.ExecutionId); | ||
| Assert.AreEqual(2, tableClient.JumpStartQueryCount, "Expected one failed attempt followed by a successful retry."); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Table client that throws a retryable storage error a fixed number of times before returning | ||
| /// a row, so a lookup that lacks retries fails and one that retries succeeds. | ||
| /// </summary> | ||
| sealed class FailOnceTableClient : AzureTableClient | ||
| { | ||
| public FailOnceTableClient() | ||
| : base(HubName, FakeStorageConnectionString) | ||
| { | ||
| } | ||
|
|
||
| public int FailuresBeforeSuccess { get; set; } | ||
|
|
||
| public int JumpStartFailuresBeforeSuccess { get; set; } | ||
|
|
||
| public bool StateTableIsEmpty { get; set; } | ||
|
|
||
| public int StateQueryCount { get; private set; } | ||
|
|
||
| public int JumpStartQueryCount { get; private set; } | ||
|
|
||
| public override Task<IEnumerable<AzureTableOrchestrationStateEntity>> QueryOrchestrationStatesAsync( | ||
| OrchestrationStateQuery stateQuery) | ||
| { | ||
| this.StateQueryCount++; | ||
|
|
||
| if (this.StateQueryCount <= this.FailuresBeforeSuccess) | ||
| { | ||
| throw new RequestFailedException(503, "The server is busy."); | ||
| } | ||
|
|
||
| IEnumerable<AzureTableOrchestrationStateEntity> result = this.StateTableIsEmpty | ||
| ? Enumerable.Empty<AzureTableOrchestrationStateEntity>() | ||
| : new[] { CreateEntity() }; | ||
|
|
||
| return Task.FromResult(result); | ||
| } | ||
|
|
||
| public override Task<IEnumerable<AzureTableOrchestrationStateEntity>> QueryJumpStartOrchestrationsAsync( | ||
| OrchestrationStateQuery stateQuery) | ||
| { | ||
| this.JumpStartQueryCount++; | ||
|
|
||
| if (this.JumpStartQueryCount <= this.JumpStartFailuresBeforeSuccess) | ||
| { | ||
| throw new RequestFailedException(503, "The server is busy."); | ||
| } | ||
|
|
||
| return Task.FromResult<IEnumerable<AzureTableOrchestrationStateEntity>>(new[] { CreateEntity() }); | ||
| } | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.