diff --git a/src/Grpc/orchestrator_service.proto b/src/Grpc/orchestrator_service.proto index 1a86c0a27..196c88da6 100644 --- a/src/Grpc/orchestrator_service.proto +++ b/src/Grpc/orchestrator_service.proto @@ -276,6 +276,7 @@ message CreateSubOrchestrationAction { google.protobuf.StringValue version = 3; google.protobuf.StringValue input = 4; TraceContext parentTraceContext = 5; + map tags = 6; } message CreateTimerAction { @@ -356,6 +357,13 @@ message OrchestratorResponse { // Whether or not a history is required to complete the original OrchestratorRequest and none was provided. bool requiresHistory = 7; + + // True if this is a partial (chunked) completion. The backend must keep the work item open until the final chunk (isPartial=false). + bool isPartial = 8; + + // Zero-based position of the current chunk within a chunked completion sequence. + // This field is omitted for non-chunked completions. + google.protobuf.Int32Value chunkIndex = 9; } message CreateInstanceRequest { diff --git a/src/Grpc/versions.txt b/src/Grpc/versions.txt index 69b075cd3..3b7d51216 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-11-14 16:36:47 UTC -https://raw.githubusercontent.com/microsoft/durabletask-protobuf/9f762f1301b91e3e7c736b9c5a29c2e09f2a850e/protos/orchestrator_service.proto +# The following files were downloaded from branch main at 2025-12-12 01:49:06 UTC +https://raw.githubusercontent.com/microsoft/durabletask-protobuf/b03c06dea21952dcf2a86551fd761b6b78c64d15/protos/orchestrator_service.proto diff --git a/src/InProcessTestHost/Sidecar/Grpc/ProtobufUtils.cs b/src/InProcessTestHost/Sidecar/Grpc/ProtobufUtils.cs index fe4e093cb..8289574b6 100644 --- a/src/InProcessTestHost/Sidecar/Grpc/ProtobufUtils.cs +++ b/src/InProcessTestHost/Sidecar/Grpc/ProtobufUtils.cs @@ -288,7 +288,7 @@ public static OrchestratorAction ToOrchestratorAction(Proto.OrchestratorAction a ParentTraceContext = a.CreateSubOrchestration.ParentTraceContext is not null ? new DistributedTraceContext(a.CreateSubOrchestration.ParentTraceContext.TraceParent, a.CreateSubOrchestration.ParentTraceContext.TraceState) : null, - Tags = null, // TODO + Tags = a.CreateSubOrchestration.Tags, Version = a.CreateSubOrchestration.Version, }; case Proto.OrchestratorAction.OrchestratorActionTypeOneofCase.CreateTimer: diff --git a/src/Shared/Grpc/ProtoUtils.cs b/src/Shared/Grpc/ProtoUtils.cs index ab5551d45..2412fac37 100644 --- a/src/Shared/Grpc/ProtoUtils.cs +++ b/src/Shared/Grpc/ProtoUtils.cs @@ -373,7 +373,16 @@ internal static P.OrchestratorResponse ConstructOrchestratorResponse( Name = subOrchestrationAction.Name, Version = subOrchestrationAction.Version, ParentTraceContext = CreateTraceContext(), - }; + }; + + if (subOrchestrationAction.Tags != null) + { + foreach (KeyValuePair tag in subOrchestrationAction.Tags) + { + protoAction.CreateSubOrchestration.Tags[tag.Key] = tag.Value; + } + } + break; case OrchestratorActionType.CreateTimer: var createTimerAction = (CreateTimerOrchestratorAction)action; diff --git a/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs b/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs index 44430610e..945d6ac5b 100644 --- a/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs +++ b/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs @@ -235,7 +235,8 @@ public override async Task CallSubOrchestratorAsync( orchestratorName.Name, version, instanceId, - input), + input, + options?.Tags), orchestratorName.Name, handler, default); @@ -246,7 +247,8 @@ public override async Task CallSubOrchestratorAsync( orchestratorName.Name, version, instanceId, - input); + input, + options?.Tags); } } catch (global::DurableTask.Core.Exceptions.SubOrchestrationFailedException e) diff --git a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs index 72c286f38..be7c0d1dc 100644 --- a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs +++ b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs @@ -36,7 +36,7 @@ public async Task EmptyOrchestration() } [Fact] - public async Task ScheduleOrchesrationWithTags() + public async Task ScheduleOrchestrationWithTags() { TaskName orchestratorName = nameof(EmptyOrchestration); await using HostTestLifetime server = await this.StartWorkerAsync(b => @@ -67,6 +67,52 @@ public async Task ScheduleOrchesrationWithTags() Assert.Equal("value2", metadata.Tags["tag2"]); } + [Fact] + public async Task ScheduleSubOrchestrationWithTags() + { + TaskName orchestratorName = nameof(ScheduleSubOrchestrationWithTags); + + // Schedule a new orchestration instance with tags + SubOrchestrationOptions subOrchestrationOptions = new() + { + InstanceId = "instance_id", + Tags = new Dictionary + { + { "tag1", "value1" }, + { "tag2", "value2" } + } + }; + + await using HostTestLifetime server = await this.StartWorkerAsync(b => + { + b.AddTasks(tasks => tasks.AddOrchestratorFunc(orchestratorName, async (ctx, input) => + { + int result = 1; + if (input < 2) + { + // recursively call this same orchestrator + result += await ctx.CallSubOrchestratorAsync(orchestratorName, input: input + 1, subOrchestrationOptions); + } + + return result; + })); + }); + + + await server.Client.ScheduleNewOrchestrationInstanceAsync(orchestratorName, input: 1); + + OrchestrationMetadata metadata = await server.Client.WaitForInstanceCompletionAsync( + subOrchestrationOptions.InstanceId, this.TimeoutToken); + + Assert.NotNull(metadata); + Assert.Equal(subOrchestrationOptions.InstanceId, metadata.InstanceId); + Assert.Equal(OrchestrationRuntimeStatus.Completed, metadata.RuntimeStatus); + Assert.NotNull(metadata.Tags); + Assert.Equal(2, metadata.Tags.Count); + Assert.Equal("value1", metadata.Tags["tag1"]); + Assert.Equal("value2", metadata.Tags["tag2"]); + } + [Fact] public async Task SingleTimer() {