diff --git a/Directory.Packages.props b/Directory.Packages.props index d5300050..a3e580b8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -30,7 +30,7 @@ - + diff --git a/eng/targets/Release.props b/eng/targets/Release.props index d4adbf24..41429536 100644 --- a/eng/targets/Release.props +++ b/eng/targets/Release.props @@ -17,7 +17,7 @@ - 1.16.2 + 1.17.0 diff --git a/src/Client/Core/DurableTaskClient.cs b/src/Client/Core/DurableTaskClient.cs index 8ad6181e..f2d658d8 100644 --- a/src/Client/Core/DurableTaskClient.cs +++ b/src/Client/Core/DurableTaskClient.cs @@ -436,7 +436,35 @@ public virtual Task RestartAsync( string instanceId, bool restartWithNewInstanceId = false, CancellationToken cancellation = default) - => throw new NotSupportedException($"{this.GetType()} does not support orchestration restart."); + => throw new NotSupportedException($"{this.GetType()} does not support orchestration restart."); + + /// + /// Rewinds the specified orchestration instance by re-executing any failed Activities, and recursively rewinding + /// any failed suborchestrations with failed Activities. + /// + /// + /// The orchestration's history will be replaced with a new history that excludes the failed Activities and suborchestrations, + /// and a new execution ID will be generated for the rewound orchestration instance. As the failed Activities and suborchestrations + /// re-execute, the history will be appended with new TaskScheduled, TaskCompleted, and SubOrchestrationInstanceCompleted events. + /// Note that only orchestrations in a "Failed" state can be rewound. + /// + /// The instance ID of the orchestration to rewind. + /// The reason for the rewind. + /// The cancellation token. This only cancels enqueueing the rewind request to the backend. + /// It does not abort rewinding the orchestration once the request has been enqueued. + /// A task that represents the enqueueing of the rewind operation. + /// Thrown if this implementation of does not + /// support rewinding orchestrations. + /// Thrown if the backend storage provider does not support rewinding orchestrations. + /// Thrown if an orchestration with the specified does not exist. + /// Thrown if a precondition of the operation fails, for example if the specified + /// orchestration is not in a "Failed" state. + /// Thrown if the operation is canceled via the token. + public virtual Task RewindInstanceAsync( + string instanceId, + string reason, + CancellationToken cancellation = default) + => throw new NotSupportedException($"{this.GetType()} does not support orchestration rewind."); // TODO: Create task hub diff --git a/src/Client/Grpc/GrpcDurableTaskClient.cs b/src/Client/Grpc/GrpcDurableTaskClient.cs index c57b22f9..efc6d765 100644 --- a/src/Client/Grpc/GrpcDurableTaskClient.cs +++ b/src/Client/Grpc/GrpcDurableTaskClient.cs @@ -431,6 +431,43 @@ public override async Task RestartAsync( } } + /// + public override async Task RewindInstanceAsync( + string instanceId, + string reason, + CancellationToken cancellation = default) + { + Check.NotNullOrEmpty(instanceId); + Check.NotEntity(this.options.EnableEntitySupport, instanceId); + + var request = new P.RewindInstanceRequest + { + InstanceId = instanceId, + Reason = reason, + }; + try + { + await this.sidecarClient.RewindInstanceAsync(request, cancellationToken: cancellation); + } + catch (RpcException e) when (e.StatusCode == StatusCode.NotFound) + { + throw new ArgumentException($"An orchestration with the instanceId {instanceId} was not found.", e); + } + catch (RpcException e) when (e.StatusCode == StatusCode.FailedPrecondition) + { + throw new InvalidOperationException(e.Status.Detail); + } + catch (RpcException e) when (e.StatusCode == StatusCode.Unimplemented) + { + throw new NotImplementedException(e.Status.Detail); + } + catch (RpcException e) when (e.StatusCode == StatusCode.Cancelled) + { + throw new OperationCanceledException( + $"The {nameof(this.RewindInstanceAsync)} operation was canceled.", e, cancellation); + } + } + static AsyncDisposable GetCallInvoker(GrpcDurableTaskClientOptions options, out CallInvoker callInvoker) { if (options.Channel is GrpcChannel c) diff --git a/src/Grpc/orchestrator_service.proto b/src/Grpc/orchestrator_service.proto index b2def087..7e38f67e 100644 --- a/src/Grpc/orchestrator_service.proto +++ b/src/Grpc/orchestrator_service.proto @@ -220,6 +220,13 @@ message EntityLockGrantedEvent { string criticalSectionId = 1; } +message ExecutionRewoundEvent { + google.protobuf.StringValue reason = 1; + google.protobuf.StringValue parentExecutionId = 2; // used only for rewinding suborchestrations, null otherwise + google.protobuf.StringValue instanceId = 3; // used only for rewinding suborchestrations, null otherwise + TraceContext parentTraceContext = 4; // used only for rewinding suborchestrations, null otherwise +} + message HistoryEvent { int32 eventId = 1; google.protobuf.Timestamp timestamp = 2; @@ -251,6 +258,7 @@ message HistoryEvent { EntityLockRequestedEvent entityLockRequested = 27; EntityLockGrantedEvent entityLockGranted = 28; EntityUnlockSentEvent entityUnlockSent = 29; + ExecutionRewoundEvent executionRewound = 30; } } diff --git a/src/Grpc/versions.txt b/src/Grpc/versions.txt index 709a8007..9f3e2a16 100644 --- a/src/Grpc/versions.txt +++ b/src/Grpc/versions.txt @@ -1,2 +1,2 @@ -# The following files were downloaded from branch main at 2025-10-13 18:06:55 UTC -https://raw.githubusercontent.com/microsoft/durabletask-protobuf/97cf9cf6ac44107b883b0f4ab1dd62ee2332cfd9/protos/orchestrator_service.proto +# The following files were downloaded from branch main at 2025-11-04 03:02:50 UTC +https://raw.githubusercontent.com/microsoft/durabletask-protobuf/73592a81dac1b5dd8653bb5ed1099aee2c89e3a2/protos/orchestrator_service.proto diff --git a/src/Shared/Grpc/ProtoUtils.cs b/src/Shared/Grpc/ProtoUtils.cs index 13b64fe8..ac31aea4 100644 --- a/src/Shared/Grpc/ProtoUtils.cs +++ b/src/Shared/Grpc/ProtoUtils.cs @@ -219,6 +219,9 @@ internal static HistoryEvent ConvertHistoryEvent(P.HistoryEvent proto, EntityCon Tags = proto.HistoryState.OrchestrationState.Tags, }); break; + case P.HistoryEvent.EventTypeOneofCase.ExecutionRewound: + historyEvent = new ExecutionRewoundEvent(proto.EventId); + break; default: throw new NotSupportedException($"Deserialization of {proto.EventTypeCase} is not supported."); }