diff --git a/Directory.Packages.props b/Directory.Packages.props index b2b1ff2ed..5745a27d3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,9 +15,9 @@ - - - + + + @@ -29,8 +29,8 @@ - - + + @@ -68,7 +68,7 @@ - + diff --git a/src/WebJobs.Extensions.DurableTask/Grpc/Protos/orchestrator_service.proto b/src/WebJobs.Extensions.DurableTask/Grpc/Protos/orchestrator_service.proto index df5143bc9..c8b55430d 100644 --- a/src/WebJobs.Extensions.DurableTask/Grpc/Protos/orchestrator_service.proto +++ b/src/WebJobs.Extensions.DurableTask/Grpc/Protos/orchestrator_service.proto @@ -1,821 +1,828 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -syntax = "proto3"; - -option csharp_namespace = "Microsoft.DurableTask.Protobuf"; -option java_package = "com.microsoft.durabletask.implementation.protobuf"; -option go_package = "/internal/protos"; - -import "google/protobuf/timestamp.proto"; -import "google/protobuf/duration.proto"; -import "google/protobuf/wrappers.proto"; -import "google/protobuf/empty.proto"; -import "google/protobuf/struct.proto"; - -message OrchestrationInstance { - string instanceId = 1; - google.protobuf.StringValue executionId = 2; -} - -message ActivityRequest { - string name = 1; - google.protobuf.StringValue version = 2; - google.protobuf.StringValue input = 3; - OrchestrationInstance orchestrationInstance = 4; - int32 taskId = 5; - TraceContext parentTraceContext = 6; -} - -message ActivityResponse { - string instanceId = 1; - int32 taskId = 2; - google.protobuf.StringValue result = 3; - TaskFailureDetails failureDetails = 4; - string completionToken = 5; -} - -message TaskFailureDetails { - string errorType = 1; - string errorMessage = 2; - google.protobuf.StringValue stackTrace = 3; - TaskFailureDetails innerFailure = 4; - bool isNonRetriable = 5; -} - -enum OrchestrationStatus { - ORCHESTRATION_STATUS_RUNNING = 0; - ORCHESTRATION_STATUS_COMPLETED = 1; - ORCHESTRATION_STATUS_CONTINUED_AS_NEW = 2; - ORCHESTRATION_STATUS_FAILED = 3; - ORCHESTRATION_STATUS_CANCELED = 4; - ORCHESTRATION_STATUS_TERMINATED = 5; - ORCHESTRATION_STATUS_PENDING = 6; - ORCHESTRATION_STATUS_SUSPENDED = 7; -} - -message ParentInstanceInfo { - int32 taskScheduledId = 1; - google.protobuf.StringValue name = 2; - google.protobuf.StringValue version = 3; - OrchestrationInstance orchestrationInstance = 4; -} - -message TraceContext { - string traceParent = 1; - string spanID = 2 [deprecated=true]; - google.protobuf.StringValue traceState = 3; -} - -message ExecutionStartedEvent { - string name = 1; - google.protobuf.StringValue version = 2; - google.protobuf.StringValue input = 3; - OrchestrationInstance orchestrationInstance = 4; - ParentInstanceInfo parentInstance = 5; - google.protobuf.Timestamp scheduledStartTimestamp = 6; - TraceContext parentTraceContext = 7; - google.protobuf.StringValue orchestrationSpanID = 8; - map tags = 9; -} - -message ExecutionCompletedEvent { - OrchestrationStatus orchestrationStatus = 1; - google.protobuf.StringValue result = 2; - TaskFailureDetails failureDetails = 3; -} - -message ExecutionTerminatedEvent { - google.protobuf.StringValue input = 1; - bool recurse = 2; -} - -message TaskScheduledEvent { - string name = 1; - google.protobuf.StringValue version = 2; - google.protobuf.StringValue input = 3; - TraceContext parentTraceContext = 4; - map tags = 5; -} - -message TaskCompletedEvent { - int32 taskScheduledId = 1; - google.protobuf.StringValue result = 2; -} - -message TaskFailedEvent { - int32 taskScheduledId = 1; - TaskFailureDetails failureDetails = 2; -} - -message SubOrchestrationInstanceCreatedEvent { - string instanceId = 1; - string name = 2; - google.protobuf.StringValue version = 3; - google.protobuf.StringValue input = 4; - TraceContext parentTraceContext = 5; -} - -message SubOrchestrationInstanceCompletedEvent { - int32 taskScheduledId = 1; - google.protobuf.StringValue result = 2; -} - -message SubOrchestrationInstanceFailedEvent { - int32 taskScheduledId = 1; - TaskFailureDetails failureDetails = 2; -} - -message TimerCreatedEvent { - google.protobuf.Timestamp fireAt = 1; -} - -message TimerFiredEvent { - google.protobuf.Timestamp fireAt = 1; - int32 timerId = 2; -} - -message OrchestratorStartedEvent { - // No payload data -} - -message OrchestratorCompletedEvent { - // No payload data -} - -message EventSentEvent { - string instanceId = 1; - string name = 2; - google.protobuf.StringValue input = 3; -} - -message EventRaisedEvent { - string name = 1; - google.protobuf.StringValue input = 2; -} - -message GenericEvent { - google.protobuf.StringValue data = 1; -} - -message HistoryStateEvent { - OrchestrationState orchestrationState = 1; -} - -message ContinueAsNewEvent { - google.protobuf.StringValue input = 1; -} - -message ExecutionSuspendedEvent { - google.protobuf.StringValue input = 1; -} - -message ExecutionResumedEvent { - google.protobuf.StringValue input = 1; -} - -message EntityOperationSignaledEvent { - string requestId = 1; - string operation = 2; - google.protobuf.Timestamp scheduledTime = 3; - google.protobuf.StringValue input = 4; - google.protobuf.StringValue targetInstanceId = 5; // used only within histories, null in messages -} - -message EntityOperationCalledEvent { - string requestId = 1; - string operation = 2; - google.protobuf.Timestamp scheduledTime = 3; - google.protobuf.StringValue input = 4; - google.protobuf.StringValue parentInstanceId = 5; // used only within messages, null in histories - google.protobuf.StringValue parentExecutionId = 6; // used only within messages, null in histories - google.protobuf.StringValue targetInstanceId = 7; // used only within histories, null in messages -} - -message EntityLockRequestedEvent { - string criticalSectionId = 1; - repeated string lockSet = 2; - int32 position = 3; - google.protobuf.StringValue parentInstanceId = 4; // used only within messages, null in histories -} - -message EntityOperationCompletedEvent { - string requestId = 1; - google.protobuf.StringValue output = 2; -} - -message EntityOperationFailedEvent { - string requestId = 1; - TaskFailureDetails failureDetails = 2; -} - -message EntityUnlockSentEvent { - string criticalSectionId = 1; - google.protobuf.StringValue parentInstanceId = 2; // used only within messages, null in histories - google.protobuf.StringValue targetInstanceId = 3; // used only within histories, null in messages -} - -message EntityLockGrantedEvent { - string criticalSectionId = 1; -} - -message HistoryEvent { - int32 eventId = 1; - google.protobuf.Timestamp timestamp = 2; - oneof eventType { - ExecutionStartedEvent executionStarted = 3; - ExecutionCompletedEvent executionCompleted = 4; - ExecutionTerminatedEvent executionTerminated = 5; - TaskScheduledEvent taskScheduled = 6; - TaskCompletedEvent taskCompleted = 7; - TaskFailedEvent taskFailed = 8; - SubOrchestrationInstanceCreatedEvent subOrchestrationInstanceCreated = 9; - SubOrchestrationInstanceCompletedEvent subOrchestrationInstanceCompleted = 10; - SubOrchestrationInstanceFailedEvent subOrchestrationInstanceFailed = 11; - TimerCreatedEvent timerCreated = 12; - TimerFiredEvent timerFired = 13; - OrchestratorStartedEvent orchestratorStarted = 14; - OrchestratorCompletedEvent orchestratorCompleted = 15; - EventSentEvent eventSent = 16; - EventRaisedEvent eventRaised = 17; - GenericEvent genericEvent = 18; - HistoryStateEvent historyState = 19; - ContinueAsNewEvent continueAsNew = 20; - ExecutionSuspendedEvent executionSuspended = 21; - ExecutionResumedEvent executionResumed = 22; - EntityOperationSignaledEvent entityOperationSignaled = 23; - EntityOperationCalledEvent entityOperationCalled = 24; - EntityOperationCompletedEvent entityOperationCompleted = 25; - EntityOperationFailedEvent entityOperationFailed = 26; - EntityLockRequestedEvent entityLockRequested = 27; - EntityLockGrantedEvent entityLockGranted = 28; - EntityUnlockSentEvent entityUnlockSent = 29; - } -} - -message ScheduleTaskAction { - string name = 1; - google.protobuf.StringValue version = 2; - google.protobuf.StringValue input = 3; - map tags = 4; - TraceContext parentTraceContext = 5; -} - -message CreateSubOrchestrationAction { - string instanceId = 1; - string name = 2; - google.protobuf.StringValue version = 3; - google.protobuf.StringValue input = 4; - TraceContext parentTraceContext = 5; -} - -message CreateTimerAction { - google.protobuf.Timestamp fireAt = 1; -} - -message SendEventAction { - OrchestrationInstance instance = 1; - string name = 2; - google.protobuf.StringValue data = 3; -} - -message CompleteOrchestrationAction { - OrchestrationStatus orchestrationStatus = 1; - google.protobuf.StringValue result = 2; - google.protobuf.StringValue details = 3; - google.protobuf.StringValue newVersion = 4; - repeated HistoryEvent carryoverEvents = 5; - TaskFailureDetails failureDetails = 6; -} - -message TerminateOrchestrationAction { - string instanceId = 1; - google.protobuf.StringValue reason = 2; - bool recurse = 3; -} - -message SendEntityMessageAction { - oneof EntityMessageType { - EntityOperationSignaledEvent entityOperationSignaled = 1; - EntityOperationCalledEvent entityOperationCalled = 2; - EntityLockRequestedEvent entityLockRequested = 3; - EntityUnlockSentEvent entityUnlockSent = 4; - } -} - -message OrchestratorAction { - int32 id = 1; - oneof orchestratorActionType { - ScheduleTaskAction scheduleTask = 2; - CreateSubOrchestrationAction createSubOrchestration = 3; - CreateTimerAction createTimer = 4; - SendEventAction sendEvent = 5; - CompleteOrchestrationAction completeOrchestration = 6; - TerminateOrchestrationAction terminateOrchestration = 7; - SendEntityMessageAction sendEntityMessage = 8; - } -} - -message OrchestrationTraceContext { - google.protobuf.StringValue spanID = 1; - google.protobuf.Timestamp spanStartTime = 2; -} - -message OrchestratorRequest { - string instanceId = 1; - google.protobuf.StringValue executionId = 2; - repeated HistoryEvent pastEvents = 3; - repeated HistoryEvent newEvents = 4; - OrchestratorEntityParameters entityParameters = 5; - bool requiresHistoryStreaming = 6; - map properties = 7; - - OrchestrationTraceContext orchestrationTraceContext = 8; -} - -message OrchestratorResponse { - string instanceId = 1; - repeated OrchestratorAction actions = 2; - google.protobuf.StringValue customStatus = 3; - string completionToken = 4; - - // The number of work item events that were processed by the orchestrator. - // This field is optional. If not set, the service should assume that the orchestrator processed all events. - google.protobuf.Int32Value numEventsProcessed = 5; - OrchestrationTraceContext orchestrationTraceContext = 6; - - // Whether or not a history is required to complete the original OrchestratorRequest and none was provided. - bool requiresHistory = 7; -} - -message CreateInstanceRequest { - string instanceId = 1; - string name = 2; - google.protobuf.StringValue version = 3; - google.protobuf.StringValue input = 4; - google.protobuf.Timestamp scheduledStartTimestamp = 5; - OrchestrationIdReusePolicy orchestrationIdReusePolicy = 6; - google.protobuf.StringValue executionId = 7; - map tags = 8; - TraceContext parentTraceContext = 9; - google.protobuf.Timestamp requestTime = 10; -} - -message OrchestrationIdReusePolicy { - repeated OrchestrationStatus replaceableStatus = 1; - reserved 2; -} - -message CreateInstanceResponse { - string instanceId = 1; -} - -message GetInstanceRequest { - string instanceId = 1; - bool getInputsAndOutputs = 2; -} - -message GetInstanceResponse { - bool exists = 1; - OrchestrationState orchestrationState = 2; -} - -message RewindInstanceRequest { - string instanceId = 1; - google.protobuf.StringValue reason = 2; -} - -message RewindInstanceResponse { - // Empty for now. Using explicit type incase we want to add content later. -} - -message OrchestrationState { - string instanceId = 1; - string name = 2; - google.protobuf.StringValue version = 3; - OrchestrationStatus orchestrationStatus = 4; - google.protobuf.Timestamp scheduledStartTimestamp = 5; - google.protobuf.Timestamp createdTimestamp = 6; - google.protobuf.Timestamp lastUpdatedTimestamp = 7; - google.protobuf.StringValue input = 8; - google.protobuf.StringValue output = 9; - google.protobuf.StringValue customStatus = 10; - TaskFailureDetails failureDetails = 11; - google.protobuf.StringValue executionId = 12; - google.protobuf.Timestamp completedTimestamp = 13; - google.protobuf.StringValue parentInstanceId = 14; - map tags = 15; -} - -message RaiseEventRequest { - string instanceId = 1; - string name = 2; - google.protobuf.StringValue input = 3; -} - -message RaiseEventResponse { - // No payload -} - -message TerminateRequest { - string instanceId = 1; - google.protobuf.StringValue output = 2; - bool recursive = 3; -} - -message TerminateResponse { - // No payload -} - -message SuspendRequest { - string instanceId = 1; - google.protobuf.StringValue reason = 2; -} - -message SuspendResponse { - // No payload -} - -message ResumeRequest { - string instanceId = 1; - google.protobuf.StringValue reason = 2; -} - -message ResumeResponse { - // No payload -} - -message QueryInstancesRequest { - InstanceQuery query = 1; -} - -message InstanceQuery{ - repeated OrchestrationStatus runtimeStatus = 1; - google.protobuf.Timestamp createdTimeFrom = 2; - google.protobuf.Timestamp createdTimeTo = 3; - repeated google.protobuf.StringValue taskHubNames = 4; - int32 maxInstanceCount = 5; - google.protobuf.StringValue continuationToken = 6; - google.protobuf.StringValue instanceIdPrefix = 7; - bool fetchInputsAndOutputs = 8; -} - -message QueryInstancesResponse { - repeated OrchestrationState orchestrationState = 1; - google.protobuf.StringValue continuationToken = 2; -} - -message PurgeInstancesRequest { - oneof request { - string instanceId = 1; - PurgeInstanceFilter purgeInstanceFilter = 2; - } - bool recursive = 3; -} - -message PurgeInstanceFilter { - google.protobuf.Timestamp createdTimeFrom = 1; - google.protobuf.Timestamp createdTimeTo = 2; - repeated OrchestrationStatus runtimeStatus = 3; -} - -message PurgeInstancesResponse { - int32 deletedInstanceCount = 1; - google.protobuf.BoolValue isComplete = 2; -} - -message RestartInstanceRequest { - string instanceId = 1; - bool restartWithNewInstanceId = 2; -} - -message RestartInstanceResponse { - string instanceId = 1; -} - -message CreateTaskHubRequest { - bool recreateIfExists = 1; -} - -message CreateTaskHubResponse { - //no playload -} - -message DeleteTaskHubRequest { - //no playload -} - -message DeleteTaskHubResponse { - //no playload -} - -message SignalEntityRequest { - string instanceId = 1; - string name = 2; - google.protobuf.StringValue input = 3; - string requestId = 4; - google.protobuf.Timestamp scheduledTime = 5; - TraceContext parentTraceContext = 6; - google.protobuf.Timestamp requestTime = 7; -} - -message SignalEntityResponse { - // no payload -} - -message GetEntityRequest { - string instanceId = 1; - bool includeState = 2; -} - -message GetEntityResponse { - bool exists = 1; - EntityMetadata entity = 2; -} - -message EntityQuery { - google.protobuf.StringValue instanceIdStartsWith = 1; - google.protobuf.Timestamp lastModifiedFrom = 2; - google.protobuf.Timestamp lastModifiedTo = 3; - bool includeState = 4; - bool includeTransient = 5; - google.protobuf.Int32Value pageSize = 6; - google.protobuf.StringValue continuationToken = 7; -} - -message QueryEntitiesRequest { - EntityQuery query = 1; -} - -message QueryEntitiesResponse { - repeated EntityMetadata entities = 1; - google.protobuf.StringValue continuationToken = 2; -} - -message EntityMetadata { - string instanceId = 1; - google.protobuf.Timestamp lastModifiedTime = 2; - int32 backlogQueueSize = 3; - google.protobuf.StringValue lockedBy = 4; - google.protobuf.StringValue serializedState = 5; -} - -message CleanEntityStorageRequest { - google.protobuf.StringValue continuationToken = 1; - bool removeEmptyEntities = 2; - bool releaseOrphanedLocks = 3; -} - -message CleanEntityStorageResponse { - google.protobuf.StringValue continuationToken = 1; - int32 emptyEntitiesRemoved = 2; - int32 orphanedLocksReleased = 3; -} - -message OrchestratorEntityParameters { - google.protobuf.Duration entityMessageReorderWindow = 1; -} - -message EntityBatchRequest { - string instanceId = 1; - google.protobuf.StringValue entityState = 2; - repeated OperationRequest operations = 3; -} - -message EntityBatchResult { - repeated OperationResult results = 1; - repeated OperationAction actions = 2; - google.protobuf.StringValue entityState = 3; - TaskFailureDetails failureDetails = 4; - string completionToken = 5; - repeated OperationInfo operationInfos = 6; // used only with DTS -} - -message EntityRequest { - string instanceId = 1; - string executionId = 2; - google.protobuf.StringValue entityState = 3; // null if entity does not exist - repeated HistoryEvent operationRequests = 4; -} - -message OperationRequest { - string operation = 1; - string requestId = 2; - google.protobuf.StringValue input = 3; - TraceContext traceContext = 4; -} - -message OperationResult { - oneof resultType { - OperationResultSuccess success = 1; - OperationResultFailure failure = 2; - } -} - -message OperationInfo { - string requestId = 1; - OrchestrationInstance responseDestination = 2; // null for signals -} - -message OperationResultSuccess { - google.protobuf.StringValue result = 1; - google.protobuf.Timestamp startTimeUtc = 2; - google.protobuf.Timestamp endTimeUtc = 3; -} - -message OperationResultFailure { - TaskFailureDetails failureDetails = 1; - google.protobuf.Timestamp startTimeUtc = 2; - google.protobuf.Timestamp endTimeUtc = 3; -} - -message OperationAction { - int32 id = 1; - oneof operationActionType { - SendSignalAction sendSignal = 2; - StartNewOrchestrationAction startNewOrchestration = 3; - } -} - -message SendSignalAction { - string instanceId = 1; - string name = 2; - google.protobuf.StringValue input = 3; - google.protobuf.Timestamp scheduledTime = 4; - google.protobuf.Timestamp requestTime = 5; - TraceContext parentTraceContext = 6; -} - -message StartNewOrchestrationAction { - string instanceId = 1; - string name = 2; - google.protobuf.StringValue version = 3; - google.protobuf.StringValue input = 4; - google.protobuf.Timestamp scheduledTime = 5; - google.protobuf.Timestamp requestTime = 6; - TraceContext parentTraceContext = 7; -} - -message AbandonActivityTaskRequest { - string completionToken = 1; -} - -message AbandonActivityTaskResponse { - // Empty. -} - -message AbandonOrchestrationTaskRequest { - string completionToken = 1; -} - -message AbandonOrchestrationTaskResponse { - // Empty. -} - -message AbandonEntityTaskRequest { - string completionToken = 1; -} - -message AbandonEntityTaskResponse { - // Empty. -} - -message SkipGracefulOrchestrationTerminationsRequest { - // A maximum of 500 instance IDs can be provided in this list. - repeated string instanceIds = 1; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +syntax = "proto3"; + +option csharp_namespace = "Microsoft.DurableTask.Protobuf"; +option java_package = "com.microsoft.durabletask.implementation.protobuf"; +option go_package = "/internal/protos"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/struct.proto"; + +message OrchestrationInstance { + string instanceId = 1; + google.protobuf.StringValue executionId = 2; +} + +message ActivityRequest { + string name = 1; + google.protobuf.StringValue version = 2; + google.protobuf.StringValue input = 3; + OrchestrationInstance orchestrationInstance = 4; + int32 taskId = 5; + TraceContext parentTraceContext = 6; +} + +message ActivityResponse { + string instanceId = 1; + int32 taskId = 2; + google.protobuf.StringValue result = 3; + TaskFailureDetails failureDetails = 4; + string completionToken = 5; +} + +message TaskFailureDetails { + string errorType = 1; + string errorMessage = 2; + google.protobuf.StringValue stackTrace = 3; + TaskFailureDetails innerFailure = 4; + bool isNonRetriable = 5; + map properties = 6; +} + +enum OrchestrationStatus { + ORCHESTRATION_STATUS_RUNNING = 0; + ORCHESTRATION_STATUS_COMPLETED = 1; + ORCHESTRATION_STATUS_CONTINUED_AS_NEW = 2; + ORCHESTRATION_STATUS_FAILED = 3; + ORCHESTRATION_STATUS_CANCELED = 4; + ORCHESTRATION_STATUS_TERMINATED = 5; + ORCHESTRATION_STATUS_PENDING = 6; + ORCHESTRATION_STATUS_SUSPENDED = 7; +} + +message ParentInstanceInfo { + int32 taskScheduledId = 1; + google.protobuf.StringValue name = 2; + google.protobuf.StringValue version = 3; + OrchestrationInstance orchestrationInstance = 4; +} + +message TraceContext { + string traceParent = 1; + string spanID = 2 [deprecated=true]; + google.protobuf.StringValue traceState = 3; +} + +message ExecutionStartedEvent { + string name = 1; + google.protobuf.StringValue version = 2; + google.protobuf.StringValue input = 3; + OrchestrationInstance orchestrationInstance = 4; + ParentInstanceInfo parentInstance = 5; + google.protobuf.Timestamp scheduledStartTimestamp = 6; + TraceContext parentTraceContext = 7; + google.protobuf.StringValue orchestrationSpanID = 8; + map tags = 9; +} + +message ExecutionCompletedEvent { + OrchestrationStatus orchestrationStatus = 1; + google.protobuf.StringValue result = 2; + TaskFailureDetails failureDetails = 3; +} + +message ExecutionTerminatedEvent { + google.protobuf.StringValue input = 1; + bool recurse = 2; +} + +message TaskScheduledEvent { + string name = 1; + google.protobuf.StringValue version = 2; + google.protobuf.StringValue input = 3; + TraceContext parentTraceContext = 4; + map tags = 5; +} + +message TaskCompletedEvent { + int32 taskScheduledId = 1; + google.protobuf.StringValue result = 2; +} + +message TaskFailedEvent { + int32 taskScheduledId = 1; + TaskFailureDetails failureDetails = 2; +} + +message SubOrchestrationInstanceCreatedEvent { + string instanceId = 1; + string name = 2; + google.protobuf.StringValue version = 3; + google.protobuf.StringValue input = 4; + TraceContext parentTraceContext = 5; +} + +message SubOrchestrationInstanceCompletedEvent { + int32 taskScheduledId = 1; + google.protobuf.StringValue result = 2; +} + +message SubOrchestrationInstanceFailedEvent { + int32 taskScheduledId = 1; + TaskFailureDetails failureDetails = 2; +} + +message TimerCreatedEvent { + google.protobuf.Timestamp fireAt = 1; +} + +message TimerFiredEvent { + google.protobuf.Timestamp fireAt = 1; + int32 timerId = 2; +} + +message OrchestratorStartedEvent { + // No payload data +} + +message OrchestratorCompletedEvent { + // No payload data +} + +message EventSentEvent { + string instanceId = 1; + string name = 2; + google.protobuf.StringValue input = 3; +} + +message EventRaisedEvent { + string name = 1; + google.protobuf.StringValue input = 2; +} + +message GenericEvent { + google.protobuf.StringValue data = 1; +} + +message HistoryStateEvent { + OrchestrationState orchestrationState = 1; +} + +message ContinueAsNewEvent { + google.protobuf.StringValue input = 1; +} + +message ExecutionSuspendedEvent { + google.protobuf.StringValue input = 1; +} + +message ExecutionResumedEvent { + google.protobuf.StringValue input = 1; +} + +message EntityOperationSignaledEvent { + string requestId = 1; + string operation = 2; + google.protobuf.Timestamp scheduledTime = 3; + google.protobuf.StringValue input = 4; + google.protobuf.StringValue targetInstanceId = 5; // used only within histories, null in messages +} + +message EntityOperationCalledEvent { + string requestId = 1; + string operation = 2; + google.protobuf.Timestamp scheduledTime = 3; + google.protobuf.StringValue input = 4; + google.protobuf.StringValue parentInstanceId = 5; // used only within messages, null in histories + google.protobuf.StringValue parentExecutionId = 6; // used only within messages, null in histories + google.protobuf.StringValue targetInstanceId = 7; // used only within histories, null in messages +} + +message EntityLockRequestedEvent { + string criticalSectionId = 1; + repeated string lockSet = 2; + int32 position = 3; + google.protobuf.StringValue parentInstanceId = 4; // used only within messages, null in histories +} + +message EntityOperationCompletedEvent { + string requestId = 1; + google.protobuf.StringValue output = 2; +} + +message EntityOperationFailedEvent { + string requestId = 1; + TaskFailureDetails failureDetails = 2; +} + +message EntityUnlockSentEvent { + string criticalSectionId = 1; + google.protobuf.StringValue parentInstanceId = 2; // used only within messages, null in histories + google.protobuf.StringValue targetInstanceId = 3; // used only within histories, null in messages +} + +message EntityLockGrantedEvent { + string criticalSectionId = 1; +} + +message HistoryEvent { + int32 eventId = 1; + google.protobuf.Timestamp timestamp = 2; + oneof eventType { + ExecutionStartedEvent executionStarted = 3; + ExecutionCompletedEvent executionCompleted = 4; + ExecutionTerminatedEvent executionTerminated = 5; + TaskScheduledEvent taskScheduled = 6; + TaskCompletedEvent taskCompleted = 7; + TaskFailedEvent taskFailed = 8; + SubOrchestrationInstanceCreatedEvent subOrchestrationInstanceCreated = 9; + SubOrchestrationInstanceCompletedEvent subOrchestrationInstanceCompleted = 10; + SubOrchestrationInstanceFailedEvent subOrchestrationInstanceFailed = 11; + TimerCreatedEvent timerCreated = 12; + TimerFiredEvent timerFired = 13; + OrchestratorStartedEvent orchestratorStarted = 14; + OrchestratorCompletedEvent orchestratorCompleted = 15; + EventSentEvent eventSent = 16; + EventRaisedEvent eventRaised = 17; + GenericEvent genericEvent = 18; + HistoryStateEvent historyState = 19; + ContinueAsNewEvent continueAsNew = 20; + ExecutionSuspendedEvent executionSuspended = 21; + ExecutionResumedEvent executionResumed = 22; + EntityOperationSignaledEvent entityOperationSignaled = 23; + EntityOperationCalledEvent entityOperationCalled = 24; + EntityOperationCompletedEvent entityOperationCompleted = 25; + EntityOperationFailedEvent entityOperationFailed = 26; + EntityLockRequestedEvent entityLockRequested = 27; + EntityLockGrantedEvent entityLockGranted = 28; + EntityUnlockSentEvent entityUnlockSent = 29; + } +} + +message ScheduleTaskAction { + string name = 1; + google.protobuf.StringValue version = 2; + google.protobuf.StringValue input = 3; + map tags = 4; + TraceContext parentTraceContext = 5; +} + +message CreateSubOrchestrationAction { + string instanceId = 1; + string name = 2; + google.protobuf.StringValue version = 3; + google.protobuf.StringValue input = 4; + TraceContext parentTraceContext = 5; +} + +message CreateTimerAction { + google.protobuf.Timestamp fireAt = 1; +} + +message SendEventAction { + OrchestrationInstance instance = 1; + string name = 2; + google.protobuf.StringValue data = 3; +} + +message CompleteOrchestrationAction { + OrchestrationStatus orchestrationStatus = 1; + google.protobuf.StringValue result = 2; + google.protobuf.StringValue details = 3; + google.protobuf.StringValue newVersion = 4; + repeated HistoryEvent carryoverEvents = 5; + TaskFailureDetails failureDetails = 6; +} + +message TerminateOrchestrationAction { + string instanceId = 1; + google.protobuf.StringValue reason = 2; + bool recurse = 3; +} + +message SendEntityMessageAction { + oneof EntityMessageType { + EntityOperationSignaledEvent entityOperationSignaled = 1; + EntityOperationCalledEvent entityOperationCalled = 2; + EntityLockRequestedEvent entityLockRequested = 3; + EntityUnlockSentEvent entityUnlockSent = 4; + } +} + +message OrchestratorAction { + int32 id = 1; + oneof orchestratorActionType { + ScheduleTaskAction scheduleTask = 2; + CreateSubOrchestrationAction createSubOrchestration = 3; + CreateTimerAction createTimer = 4; + SendEventAction sendEvent = 5; + CompleteOrchestrationAction completeOrchestration = 6; + TerminateOrchestrationAction terminateOrchestration = 7; + SendEntityMessageAction sendEntityMessage = 8; + } +} + +message OrchestrationTraceContext { + google.protobuf.StringValue spanID = 1; + google.protobuf.Timestamp spanStartTime = 2; +} + +message OrchestratorRequest { + string instanceId = 1; + google.protobuf.StringValue executionId = 2; + repeated HistoryEvent pastEvents = 3; + repeated HistoryEvent newEvents = 4; + OrchestratorEntityParameters entityParameters = 5; + bool requiresHistoryStreaming = 6; + map properties = 7; + + OrchestrationTraceContext orchestrationTraceContext = 8; +} + +message OrchestratorResponse { + string instanceId = 1; + repeated OrchestratorAction actions = 2; + google.protobuf.StringValue customStatus = 3; + string completionToken = 4; + + // The number of work item events that were processed by the orchestrator. + // This field is optional. If not set, the service should assume that the orchestrator processed all events. + google.protobuf.Int32Value numEventsProcessed = 5; + OrchestrationTraceContext orchestrationTraceContext = 6; + + // Whether or not a history is required to complete the original OrchestratorRequest and none was provided. + bool requiresHistory = 7; +} + +message CreateInstanceRequest { + string instanceId = 1; + string name = 2; + google.protobuf.StringValue version = 3; + google.protobuf.StringValue input = 4; + google.protobuf.Timestamp scheduledStartTimestamp = 5; + OrchestrationIdReusePolicy orchestrationIdReusePolicy = 6; + google.protobuf.StringValue executionId = 7; + map tags = 8; + TraceContext parentTraceContext = 9; + google.protobuf.Timestamp requestTime = 10; +} + +message OrchestrationIdReusePolicy { + repeated OrchestrationStatus replaceableStatus = 1; + reserved 2; +} + +message CreateInstanceResponse { + string instanceId = 1; +} + +message GetInstanceRequest { + string instanceId = 1; + bool getInputsAndOutputs = 2; +} + +message GetInstanceResponse { + bool exists = 1; + OrchestrationState orchestrationState = 2; +} + +message RewindInstanceRequest { + string instanceId = 1; + google.protobuf.StringValue reason = 2; +} + +message RewindInstanceResponse { + // Empty for now. Using explicit type incase we want to add content later. +} + +message OrchestrationState { + string instanceId = 1; + string name = 2; + google.protobuf.StringValue version = 3; + OrchestrationStatus orchestrationStatus = 4; + google.protobuf.Timestamp scheduledStartTimestamp = 5; + google.protobuf.Timestamp createdTimestamp = 6; + google.protobuf.Timestamp lastUpdatedTimestamp = 7; + google.protobuf.StringValue input = 8; + google.protobuf.StringValue output = 9; + google.protobuf.StringValue customStatus = 10; + TaskFailureDetails failureDetails = 11; + google.protobuf.StringValue executionId = 12; + google.protobuf.Timestamp completedTimestamp = 13; + google.protobuf.StringValue parentInstanceId = 14; + map tags = 15; +} + +message RaiseEventRequest { + string instanceId = 1; + string name = 2; + google.protobuf.StringValue input = 3; +} + +message RaiseEventResponse { + // No payload +} + +message TerminateRequest { + string instanceId = 1; + google.protobuf.StringValue output = 2; + bool recursive = 3; +} + +message TerminateResponse { + // No payload +} + +message SuspendRequest { + string instanceId = 1; google.protobuf.StringValue reason = 2; -} - -message SkipGracefulOrchestrationTerminationsResponse { - // Those instances which could not be terminated because they had locked entities at the time of this termination call, - // are already in a terminal state (completed, failed, terminated, etc.), are not orchestrations, or do not exist (i.e. have been purged) +} + +message SuspendResponse { + // No payload +} + +message ResumeRequest { + string instanceId = 1; + google.protobuf.StringValue reason = 2; +} + +message ResumeResponse { + // No payload +} + +message QueryInstancesRequest { + InstanceQuery query = 1; +} + +message InstanceQuery{ + repeated OrchestrationStatus runtimeStatus = 1; + google.protobuf.Timestamp createdTimeFrom = 2; + google.protobuf.Timestamp createdTimeTo = 3; + repeated google.protobuf.StringValue taskHubNames = 4; + int32 maxInstanceCount = 5; + google.protobuf.StringValue continuationToken = 6; + google.protobuf.StringValue instanceIdPrefix = 7; + bool fetchInputsAndOutputs = 8; +} + +message QueryInstancesResponse { + repeated OrchestrationState orchestrationState = 1; + google.protobuf.StringValue continuationToken = 2; +} + +message PurgeInstancesRequest { + oneof request { + string instanceId = 1; + PurgeInstanceFilter purgeInstanceFilter = 2; + InstanceBatch instanceBatch = 4; + } + bool recursive = 3; +} + +message PurgeInstanceFilter { + google.protobuf.Timestamp createdTimeFrom = 1; + google.protobuf.Timestamp createdTimeTo = 2; + repeated OrchestrationStatus runtimeStatus = 3; +} + +message PurgeInstancesResponse { + int32 deletedInstanceCount = 1; + google.protobuf.BoolValue isComplete = 2; +} + +message RestartInstanceRequest { + string instanceId = 1; + bool restartWithNewInstanceId = 2; +} + +message RestartInstanceResponse { + string instanceId = 1; +} + +message CreateTaskHubRequest { + bool recreateIfExists = 1; +} + +message CreateTaskHubResponse { + //no playload +} + +message DeleteTaskHubRequest { + //no playload +} + +message DeleteTaskHubResponse { + //no playload +} + +message SignalEntityRequest { + string instanceId = 1; + string name = 2; + google.protobuf.StringValue input = 3; + string requestId = 4; + google.protobuf.Timestamp scheduledTime = 5; + TraceContext parentTraceContext = 6; + google.protobuf.Timestamp requestTime = 7; +} + +message SignalEntityResponse { + // no payload +} + +message GetEntityRequest { + string instanceId = 1; + bool includeState = 2; +} + +message GetEntityResponse { + bool exists = 1; + EntityMetadata entity = 2; +} + +message EntityQuery { + google.protobuf.StringValue instanceIdStartsWith = 1; + google.protobuf.Timestamp lastModifiedFrom = 2; + google.protobuf.Timestamp lastModifiedTo = 3; + bool includeState = 4; + bool includeTransient = 5; + google.protobuf.Int32Value pageSize = 6; + google.protobuf.StringValue continuationToken = 7; +} + +message QueryEntitiesRequest { + EntityQuery query = 1; +} + +message QueryEntitiesResponse { + repeated EntityMetadata entities = 1; + google.protobuf.StringValue continuationToken = 2; +} + +message EntityMetadata { + string instanceId = 1; + google.protobuf.Timestamp lastModifiedTime = 2; + int32 backlogQueueSize = 3; + google.protobuf.StringValue lockedBy = 4; + google.protobuf.StringValue serializedState = 5; +} + +message CleanEntityStorageRequest { + google.protobuf.StringValue continuationToken = 1; + bool removeEmptyEntities = 2; + bool releaseOrphanedLocks = 3; +} + +message CleanEntityStorageResponse { + google.protobuf.StringValue continuationToken = 1; + int32 emptyEntitiesRemoved = 2; + int32 orphanedLocksReleased = 3; +} + +message OrchestratorEntityParameters { + google.protobuf.Duration entityMessageReorderWindow = 1; +} + +message EntityBatchRequest { + string instanceId = 1; + google.protobuf.StringValue entityState = 2; + repeated OperationRequest operations = 3; +} + +message EntityBatchResult { + repeated OperationResult results = 1; + repeated OperationAction actions = 2; + google.protobuf.StringValue entityState = 3; + TaskFailureDetails failureDetails = 4; + string completionToken = 5; + repeated OperationInfo operationInfos = 6; // used only with DTS +} + +message EntityRequest { + string instanceId = 1; + string executionId = 2; + google.protobuf.StringValue entityState = 3; // null if entity does not exist + repeated HistoryEvent operationRequests = 4; +} + +message OperationRequest { + string operation = 1; + string requestId = 2; + google.protobuf.StringValue input = 3; + TraceContext traceContext = 4; +} + +message OperationResult { + oneof resultType { + OperationResultSuccess success = 1; + OperationResultFailure failure = 2; + } +} + +message OperationInfo { + string requestId = 1; + OrchestrationInstance responseDestination = 2; // null for signals +} + +message OperationResultSuccess { + google.protobuf.StringValue result = 1; + google.protobuf.Timestamp startTimeUtc = 2; + google.protobuf.Timestamp endTimeUtc = 3; +} + +message OperationResultFailure { + TaskFailureDetails failureDetails = 1; + google.protobuf.Timestamp startTimeUtc = 2; + google.protobuf.Timestamp endTimeUtc = 3; +} + +message OperationAction { + int32 id = 1; + oneof operationActionType { + SendSignalAction sendSignal = 2; + StartNewOrchestrationAction startNewOrchestration = 3; + } +} + +message SendSignalAction { + string instanceId = 1; + string name = 2; + google.protobuf.StringValue input = 3; + google.protobuf.Timestamp scheduledTime = 4; + google.protobuf.Timestamp requestTime = 5; + TraceContext parentTraceContext = 6; +} + +message StartNewOrchestrationAction { + string instanceId = 1; + string name = 2; + google.protobuf.StringValue version = 3; + google.protobuf.StringValue input = 4; + google.protobuf.Timestamp scheduledTime = 5; + google.protobuf.Timestamp requestTime = 6; + TraceContext parentTraceContext = 7; +} + +message AbandonActivityTaskRequest { + string completionToken = 1; +} + +message AbandonActivityTaskResponse { + // Empty. +} + +message AbandonOrchestrationTaskRequest { + string completionToken = 1; +} + +message AbandonOrchestrationTaskResponse { + // Empty. +} + +message AbandonEntityTaskRequest { + string completionToken = 1; +} + +message AbandonEntityTaskResponse { + // Empty. +} + +message SkipGracefulOrchestrationTerminationsRequest { + InstanceBatch instanceBatch = 1; + google.protobuf.StringValue reason = 2; +} + +message SkipGracefulOrchestrationTerminationsResponse { + // Those instances which could not be terminated because they had locked entities at the time of this termination call, + // are already in a terminal state (completed, failed, terminated, etc.), are not orchestrations, or do not exist (i.e. have been purged) repeated string unterminatedInstanceIds = 1; -} - -service TaskHubSidecarService { - // Sends a hello request to the sidecar service. - rpc Hello(google.protobuf.Empty) returns (google.protobuf.Empty); - - // Starts a new orchestration instance. - rpc StartInstance(CreateInstanceRequest) returns (CreateInstanceResponse); - - // Gets the status of an existing orchestration instance. - rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse); - - // Rewinds an orchestration instance to last known good state and replays from there. - rpc RewindInstance(RewindInstanceRequest) returns (RewindInstanceResponse); - - // Restarts an orchestration instance. - rpc RestartInstance(RestartInstanceRequest) returns (RestartInstanceResponse); - - // Waits for an orchestration instance to reach a running or completion state. - rpc WaitForInstanceStart(GetInstanceRequest) returns (GetInstanceResponse); - - // Waits for an orchestration instance to reach a completion state (completed, failed, terminated, etc.). - rpc WaitForInstanceCompletion(GetInstanceRequest) returns (GetInstanceResponse); - - // Raises an event to a running orchestration instance. - rpc RaiseEvent(RaiseEventRequest) returns (RaiseEventResponse); - - // Terminates a running orchestration instance. - rpc TerminateInstance(TerminateRequest) returns (TerminateResponse); - - // Suspends a running orchestration instance. - rpc SuspendInstance(SuspendRequest) returns (SuspendResponse); - - // Resumes a suspended orchestration instance. - rpc ResumeInstance(ResumeRequest) returns (ResumeResponse); - - // rpc DeleteInstance(DeleteInstanceRequest) returns (DeleteInstanceResponse); - - rpc QueryInstances(QueryInstancesRequest) returns (QueryInstancesResponse); - rpc PurgeInstances(PurgeInstancesRequest) returns (PurgeInstancesResponse); - - rpc GetWorkItems(GetWorkItemsRequest) returns (stream WorkItem); - rpc CompleteActivityTask(ActivityResponse) returns (CompleteTaskResponse); - rpc CompleteOrchestratorTask(OrchestratorResponse) returns (CompleteTaskResponse); - rpc CompleteEntityTask(EntityBatchResult) returns (CompleteTaskResponse); - - // Gets the history of an orchestration instance as a stream of events. - rpc StreamInstanceHistory(StreamInstanceHistoryRequest) returns (stream HistoryChunk); - - // Deletes and Creates the necessary resources for the orchestration service and the instance store - rpc CreateTaskHub(CreateTaskHubRequest) returns (CreateTaskHubResponse); - - // Deletes the resources for the orchestration service and optionally the instance store - rpc DeleteTaskHub(DeleteTaskHubRequest) returns (DeleteTaskHubResponse); - - // sends a signal to an entity - rpc SignalEntity(SignalEntityRequest) returns (SignalEntityResponse); - - // get information about a specific entity - rpc GetEntity(GetEntityRequest) returns (GetEntityResponse); - - // query entities - rpc QueryEntities(QueryEntitiesRequest) returns (QueryEntitiesResponse); - - // clean entity storage - rpc CleanEntityStorage(CleanEntityStorageRequest) returns (CleanEntityStorageResponse); - - // Abandons a single work item - rpc AbandonTaskActivityWorkItem(AbandonActivityTaskRequest) returns (AbandonActivityTaskResponse); - - // Abandon an orchestration work item - rpc AbandonTaskOrchestratorWorkItem(AbandonOrchestrationTaskRequest) returns (AbandonOrchestrationTaskResponse); - - // Abandon an entity work item - rpc AbandonTaskEntityWorkItem(AbandonEntityTaskRequest) returns (AbandonEntityTaskResponse); - - // "Skip" graceful termination of orchestrations by immediately changing their status in storage to "terminated". - // Note that a maximum of 500 orchestrations can be terminated at a time using this method. - rpc SkipGracefulOrchestrationTerminations(SkipGracefulOrchestrationTerminationsRequest) returns (SkipGracefulOrchestrationTerminationsResponse); -} - -message GetWorkItemsRequest { - int32 maxConcurrentOrchestrationWorkItems = 1; - int32 maxConcurrentActivityWorkItems = 2; - int32 maxConcurrentEntityWorkItems = 3; - - repeated WorkerCapability capabilities = 10; -} - -enum WorkerCapability { - WORKER_CAPABILITY_UNSPECIFIED = 0; - - // Indicates that the worker is capable of streaming instance history as a more optimized - // alternative to receiving the full history embedded in the orchestrator work-item. - // When set, the service may return work items without any history events as an optimization. - // It is strongly recommended that all SDKs support this capability. - WORKER_CAPABILITY_HISTORY_STREAMING = 1; -} - -message WorkItem { - oneof request { - OrchestratorRequest orchestratorRequest = 1; - ActivityRequest activityRequest = 2; - EntityBatchRequest entityRequest = 3; // (older) used by orchestration services implementations - HealthPing healthPing = 4; - EntityRequest entityRequestV2 = 5; // (newer) used by backend service implementations - } - string completionToken = 10; -} - -message CompleteTaskResponse { - // No payload -} - -message HealthPing { - // No payload -} - -message StreamInstanceHistoryRequest { - string instanceId = 1; - google.protobuf.StringValue executionId = 2; - - // When set to true, the service may return a more optimized response suitable for workers. - bool forWorkItemProcessing = 3; -} - -message HistoryChunk { - repeated HistoryEvent events = 1; -} \ No newline at end of file + +} + +service TaskHubSidecarService { + // Sends a hello request to the sidecar service. + rpc Hello(google.protobuf.Empty) returns (google.protobuf.Empty); + + // Starts a new orchestration instance. + rpc StartInstance(CreateInstanceRequest) returns (CreateInstanceResponse); + + // Gets the status of an existing orchestration instance. + rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse); + + // Rewinds an orchestration instance to last known good state and replays from there. + rpc RewindInstance(RewindInstanceRequest) returns (RewindInstanceResponse); + + // Restarts an orchestration instance. + rpc RestartInstance(RestartInstanceRequest) returns (RestartInstanceResponse); + + // Waits for an orchestration instance to reach a running or completion state. + rpc WaitForInstanceStart(GetInstanceRequest) returns (GetInstanceResponse); + + // Waits for an orchestration instance to reach a completion state (completed, failed, terminated, etc.). + rpc WaitForInstanceCompletion(GetInstanceRequest) returns (GetInstanceResponse); + + // Raises an event to a running orchestration instance. + rpc RaiseEvent(RaiseEventRequest) returns (RaiseEventResponse); + + // Terminates a running orchestration instance. + rpc TerminateInstance(TerminateRequest) returns (TerminateResponse); + + // Suspends a running orchestration instance. + rpc SuspendInstance(SuspendRequest) returns (SuspendResponse); + + // Resumes a suspended orchestration instance. + rpc ResumeInstance(ResumeRequest) returns (ResumeResponse); + + // rpc DeleteInstance(DeleteInstanceRequest) returns (DeleteInstanceResponse); + + rpc QueryInstances(QueryInstancesRequest) returns (QueryInstancesResponse); + rpc PurgeInstances(PurgeInstancesRequest) returns (PurgeInstancesResponse); + + rpc GetWorkItems(GetWorkItemsRequest) returns (stream WorkItem); + rpc CompleteActivityTask(ActivityResponse) returns (CompleteTaskResponse); + rpc CompleteOrchestratorTask(OrchestratorResponse) returns (CompleteTaskResponse); + rpc CompleteEntityTask(EntityBatchResult) returns (CompleteTaskResponse); + + // Gets the history of an orchestration instance as a stream of events. + rpc StreamInstanceHistory(StreamInstanceHistoryRequest) returns (stream HistoryChunk); + + // Deletes and Creates the necessary resources for the orchestration service and the instance store + rpc CreateTaskHub(CreateTaskHubRequest) returns (CreateTaskHubResponse); + + // Deletes the resources for the orchestration service and optionally the instance store + rpc DeleteTaskHub(DeleteTaskHubRequest) returns (DeleteTaskHubResponse); + + // sends a signal to an entity + rpc SignalEntity(SignalEntityRequest) returns (SignalEntityResponse); + + // get information about a specific entity + rpc GetEntity(GetEntityRequest) returns (GetEntityResponse); + + // query entities + rpc QueryEntities(QueryEntitiesRequest) returns (QueryEntitiesResponse); + + // clean entity storage + rpc CleanEntityStorage(CleanEntityStorageRequest) returns (CleanEntityStorageResponse); + + // Abandons a single work item + rpc AbandonTaskActivityWorkItem(AbandonActivityTaskRequest) returns (AbandonActivityTaskResponse); + + // Abandon an orchestration work item + rpc AbandonTaskOrchestratorWorkItem(AbandonOrchestrationTaskRequest) returns (AbandonOrchestrationTaskResponse); + + // Abandon an entity work item + rpc AbandonTaskEntityWorkItem(AbandonEntityTaskRequest) returns (AbandonEntityTaskResponse); + + // "Skip" graceful termination of orchestrations by immediately changing their status in storage to "terminated". + // Note that a maximum of 500 orchestrations can be terminated at a time using this method. + rpc SkipGracefulOrchestrationTerminations(SkipGracefulOrchestrationTerminationsRequest) returns (SkipGracefulOrchestrationTerminationsResponse); +} + +message GetWorkItemsRequest { + int32 maxConcurrentOrchestrationWorkItems = 1; + int32 maxConcurrentActivityWorkItems = 2; + int32 maxConcurrentEntityWorkItems = 3; + + repeated WorkerCapability capabilities = 10; +} + +enum WorkerCapability { + WORKER_CAPABILITY_UNSPECIFIED = 0; + + // Indicates that the worker is capable of streaming instance history as a more optimized + // alternative to receiving the full history embedded in the orchestrator work-item. + // When set, the service may return work items without any history events as an optimization. + // It is strongly recommended that all SDKs support this capability. + WORKER_CAPABILITY_HISTORY_STREAMING = 1; +} + +message WorkItem { + oneof request { + OrchestratorRequest orchestratorRequest = 1; + ActivityRequest activityRequest = 2; + EntityBatchRequest entityRequest = 3; // (older) used by orchestration services implementations + HealthPing healthPing = 4; + EntityRequest entityRequestV2 = 5; // (newer) used by backend service implementations + } + string completionToken = 10; +} + +message CompleteTaskResponse { + // No payload +} + +message HealthPing { + // No payload +} + +message StreamInstanceHistoryRequest { + string instanceId = 1; + google.protobuf.StringValue executionId = 2; + + // When set to true, the service may return a more optimized response suitable for workers. + bool forWorkItemProcessing = 3; +} + +message HistoryChunk { + repeated HistoryEvent events = 1; +} + +message InstanceBatch { + // A maximum of 500 instance IDs can be provided in this list. + repeated string instanceIds = 1; +} diff --git a/src/WebJobs.Extensions.DurableTask/Grpc/Protos/versions.txt b/src/WebJobs.Extensions.DurableTask/Grpc/Protos/versions.txt index b4a20b9e3..178b7c3fe 100644 --- a/src/WebJobs.Extensions.DurableTask/Grpc/Protos/versions.txt +++ b/src/WebJobs.Extensions.DurableTask/Grpc/Protos/versions.txt @@ -1,2 +1,2 @@ -# The following files were downloaded from branch main at 2025-09-17 01:59:32 UTC -https://raw.githubusercontent.com/microsoft/durabletask-protobuf/f5745e0d83f608d77871c1894d9260ceaae08967/protos/orchestrator_service.proto +# The following files were downloaded from branch nytian/failure-details at 2025-10-06 02:42:27 UTC +https://raw.githubusercontent.com/microsoft/durabletask-protobuf/362f886f1ef7c4dd90cbdfdb2f661f48eeeec4fa/protos/orchestrator_service.proto diff --git a/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs b/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs index 0aeaa9440..090bac187 100644 --- a/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs +++ b/src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text.Json.Nodes; using System.Threading.Tasks; using DurableTask.Core; using DurableTask.Core.Entities; @@ -12,6 +13,7 @@ using DurableTask.Core.Exceptions; using DurableTask.Core.History; using DurableTask.Core.Middleware; +using Google.Protobuf.WellKnownTypes; using Microsoft.Azure.WebJobs.Host.Executors; using Newtonsoft.Json; using P = Microsoft.DurableTask.Protobuf; @@ -205,9 +207,9 @@ await this.LifeCycleNotificationHelper.OrchestratorStartingAsync( OrchestratorExecutionResult orchestratorResult; if (functionResult.Succeeded) { - if (workerRequiresHistory) - { - throw new SessionAbortedException("The worker has since ended the extended session and needs an orchestration history to execute the orchestration request."); + if (workerRequiresHistory) + { + throw new SessionAbortedException("The worker has since ended the extended session and needs an orchestration history to execute the orchestration request."); } orchestratorResult = context.GetResult(); @@ -637,12 +639,15 @@ private static FailureDetails GetFailureDetails(Exception e, out bool fromSerial return details; } + // Try to extract properties from the serialized exception JSON + IDictionary? properties = TryExtractPropertiesFromExceptionJson(exception); + if (TrySplitExceptionTypeFromMessage(exception, out string? exceptionType, out string? exceptionMessage)) { - return new FailureDetails(exceptionType, exceptionMessage, stackTrace, innerFailure: null, isNonRetriable: false); + return new FailureDetails(exceptionType, exceptionMessage, stackTrace, innerFailure: null, isNonRetriable: false, properties: properties); } - return new FailureDetails("(unknown)", exception, stackTrace, innerFailure: null, isNonRetriable: false); + return new FailureDetails("(unknown)", exception, stackTrace, innerFailure: null, isNonRetriable: false, properties: properties); } else { @@ -662,12 +667,44 @@ private static FailureDetails GetFailureDetails(Exception e, out bool fromSerial return null; } + IDictionary? properties = null; + if (taskFailureDetails.Properties != null && taskFailureDetails.Properties.Count > 0) + { + properties = new Dictionary(); + foreach (var kvp in taskFailureDetails.Properties) + { + properties[kvp.Key] = ConvertValueToObject(kvp.Value); + } + } + return new FailureDetails( taskFailureDetails.ErrorType ?? string.Empty, taskFailureDetails.ErrorMessage ?? string.Empty, taskFailureDetails.StackTrace, GetFailureDetails(taskFailureDetails.InnerFailure), - taskFailureDetails.IsNonRetriable); + taskFailureDetails.IsNonRetriable, + properties); + } + + private static object ConvertValueToObject(Value value) + { + switch (value.KindCase) + { + case Value.KindOneofCase.StringValue: + return value.StringValue; + case Value.KindOneofCase.NumberValue: + return value.NumberValue; + case Value.KindOneofCase.BoolValue: + return value.BoolValue; + case Value.KindOneofCase.StructValue: + return value.StructValue.Fields.ToDictionary(f => f.Key, f => ConvertValueToObject(f.Value)); + case Value.KindOneofCase.ListValue: + return value.ListValue.Values.Select(ConvertValueToObject).ToList(); + case Value.KindOneofCase.NullValue: + return null!; + default: + return value; // fallback + } } private static bool TryGetRpcExceptionFields( @@ -739,6 +776,69 @@ private static bool TryExtractSerializedFailureDetailsFromException(string excep details = null; return false; } + + public static IDictionary TryExtractPropertiesFromExceptionJson(string json) + { + var result = new Dictionary(); + var root = JsonNode.Parse(json)?["Properties"]?.AsObject(); + if (root == null) + { + return result; + } + + foreach (var kvp in root) + { + var valueNode = kvp.Value?.AsObject(); + if (valueNode == null) + { + continue; + } + + result[kvp.Key] = ExtractValue(valueNode); + } + + return result; + } + + private static object? ExtractValue(JsonObject valueNode) + { + // Look at KindCase to determine which field is active + if (valueNode.TryGetPropertyValue("HasStringValue", out var hasStr) + && hasStr?.GetValue() == true) + { + return valueNode["StringValue"]?.GetValue(); + } + + if (valueNode.TryGetPropertyValue("HasNumberValue", out var hasNum) + && hasNum?.GetValue() == true) + { + return valueNode["NumberValue"]?.GetValue(); + } + + if (valueNode.TryGetPropertyValue("HasBoolValue", out var hasBool) + && hasBool?.GetValue() == true) + { + return valueNode["BoolValue"]?.GetValue(); + } + + if (valueNode.TryGetPropertyValue("HasNullValue", out var hasNull) + && hasNull?.GetValue() == true) + { + return null; + } + + if (valueNode["StructValue"] is JsonObject structObj) + { + return structObj; + } + + if (valueNode["ListValue"] is JsonArray listArr) + { + return listArr.Select(x => x?.ToJsonString()).ToList(); + } + + return null; + } private static bool TrySplitExceptionTypeFromMessage( string exception, diff --git a/src/WebJobs.Extensions.DurableTask/WebJobs.Extensions.DurableTask.csproj b/src/WebJobs.Extensions.DurableTask/WebJobs.Extensions.DurableTask.csproj index 3499faa64..278fea87e 100644 --- a/src/WebJobs.Extensions.DurableTask/WebJobs.Extensions.DurableTask.csproj +++ b/src/WebJobs.Extensions.DurableTask/WebJobs.Extensions.DurableTask.csproj @@ -5,7 +5,7 @@ Microsoft.Azure.WebJobs.Extensions.DurableTask Microsoft.Azure.WebJobs.Extensions.DurableTask 3 - 5 + 6 0 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(MajorVersion).$(MinorVersion).$(PatchVersion) diff --git a/src/Worker.Extensions.DurableTask/AssemblyInfo.cs b/src/Worker.Extensions.DurableTask/AssemblyInfo.cs index 061ba9228..6463fa77e 100644 --- a/src/Worker.Extensions.DurableTask/AssemblyInfo.cs +++ b/src/Worker.Extensions.DurableTask/AssemblyInfo.cs @@ -5,5 +5,5 @@ using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; // TODO: Find a way to generate this dynamically at build-time -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.DurableTask", "3.5.0")] +[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.DurableTask", "3.6.0")] [assembly: InternalsVisibleTo("Worker.Extensions.DurableTask.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100cd1dabd5a893b40e75dc901fe7293db4a3caf9cd4d3e3ed6178d49cd476969abe74a9e0b7f4a0bb15edca48758155d35a4f05e6e852fff1b319d103b39ba04acbadd278c2753627c95e1f6f6582425374b92f51cca3deb0d2aab9de3ecda7753900a31f70a236f163006beefffe282888f85e3c76d1205ec7dfef7fa472a17b1")] diff --git a/src/Worker.Extensions.DurableTask/DefaultExceptionPropertiesProvider.cs b/src/Worker.Extensions.DurableTask/DefaultExceptionPropertiesProvider.cs new file mode 100644 index 000000000..b1db243b5 --- /dev/null +++ b/src/Worker.Extensions.DurableTask/DefaultExceptionPropertiesProvider.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.DurableTask.Worker; + +namespace Microsoft.Azure.Functions.Worker.Extensions.DurableTask; + +/// +/// Default implementation of IExceptionPropertiesProvider that returns no custom properties. +/// +internal class DefaultExceptionPropertiesProvider : IExceptionPropertiesProvider +{ + /// + public IReadOnlyDictionary? GetExceptionProperties(Exception exception) + { + // Default implementation returns null, indicating no custom properties should be added + return null; + } + + IDictionary? IExceptionPropertiesProvider.GetExceptionProperties(Exception exception) + { + throw new NotImplementedException(); + } +} diff --git a/src/Worker.Extensions.DurableTask/DurableTaskFunctionsMiddleware.cs b/src/Worker.Extensions.DurableTask/DurableTaskFunctionsMiddleware.cs index 1ef56ff7b..efc5fa6cf 100644 --- a/src/Worker.Extensions.DurableTask/DurableTaskFunctionsMiddleware.cs +++ b/src/Worker.Extensions.DurableTask/DurableTaskFunctionsMiddleware.cs @@ -16,8 +16,8 @@ namespace Microsoft.Azure.Functions.Worker.Extensions.DurableTask; /// internal class DurableTaskFunctionsMiddleware(ExtendedSessionsCache extendedSessionsCache) : IFunctionsWorkerMiddleware { - private readonly ExtendedSessionsCache extendedSessionsCache = extendedSessionsCache; - + private readonly ExtendedSessionsCache extendedSessionsCache = extendedSessionsCache; + /// public Task Invoke(FunctionContext functionContext, FunctionExecutionDelegate next) { @@ -129,7 +129,9 @@ private static async Task RunActivityAsync(FunctionContext functionContext, Bind } catch (Exception ex) { - throw new DurableSerializationException(ex); + // Get the exception properties provider from the service provider if available + IExceptionPropertiesProvider? exceptionPropertiesProvider = functionContext.InstanceServices.GetService(typeof(IExceptionPropertiesProvider)) as IExceptionPropertiesProvider; + throw new DurableSerializationException(ex, exceptionPropertiesProvider); } } } diff --git a/src/Worker.Extensions.DurableTask/Exceptions/DurableSerializationException.cs b/src/Worker.Extensions.DurableTask/Exceptions/DurableSerializationException.cs index d0c664946..b09665b9a 100644 --- a/src/Worker.Extensions.DurableTask/Exceptions/DurableSerializationException.cs +++ b/src/Worker.Extensions.DurableTask/Exceptions/DurableSerializationException.cs @@ -1,5 +1,6 @@ using System; using Microsoft.DurableTask.Protobuf; +using Microsoft.DurableTask.Worker; using Newtonsoft.Json; namespace Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Exceptions; @@ -11,7 +12,13 @@ internal class DurableSerializationException : Exception // We set the base class properties of this exception to the same as the parent, // so that methods in the worker after this can still (typically) access the same information vs w/o // this exception type. - internal DurableSerializationException(Exception fromException) : base(CreateExceptionMessage(fromException), fromException.InnerException) + internal DurableSerializationException(Exception fromException) : + this(fromException, null) + { + } + + internal DurableSerializationException(Exception fromException, IExceptionPropertiesProvider? exceptionPropertiesProvider) + : base(CreateExceptionMessage(fromException, exceptionPropertiesProvider), fromException.InnerException) { this.fromException = fromException; } @@ -21,10 +28,9 @@ public override string ToString() return this.Message; } - // Serilize FailureDetails to JSON - private static string CreateExceptionMessage(Exception ex) + private static string CreateExceptionMessage(Exception ex, IExceptionPropertiesProvider? exceptionPropertiesProvider) { - TaskFailureDetails? failureDetails = TaskFailureDetailsConverter.TaskFailureFromException(ex); + TaskFailureDetails? failureDetails = TaskFailureDetailsConverter.TaskFailureFromException(ex, exceptionPropertiesProvider); return JsonConvert.SerializeObject(failureDetails); } diff --git a/src/Worker.Extensions.DurableTask/TaskFailureDetailsConverter.cs b/src/Worker.Extensions.DurableTask/TaskFailureDetailsConverter.cs index 4bfbf7b02..1c1f2118d 100644 --- a/src/Worker.Extensions.DurableTask/TaskFailureDetailsConverter.cs +++ b/src/Worker.Extensions.DurableTask/TaskFailureDetailsConverter.cs @@ -3,25 +3,72 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Linq; +using Google.Protobuf.WellKnownTypes; +using Microsoft.DurableTask.Worker; using P = Microsoft.DurableTask.Protobuf; namespace Microsoft.Azure.Functions.Worker.Extensions.DurableTask; internal class TaskFailureDetailsConverter { internal static P.TaskFailureDetails? TaskFailureFromException(Exception? fromException) + { + return TaskFailureFromException(fromException, null); + } + + internal static P.TaskFailureDetails? TaskFailureFromException(Exception? fromException, IExceptionPropertiesProvider? exceptionPropertiesProvider) { if (fromException is null) { return null; } - return new P.TaskFailureDetails() + + var failureDetails = new P.TaskFailureDetails() { ErrorType = fromException.GetType().FullName, ErrorMessage = fromException.Message, StackTrace = fromException.StackTrace, - InnerFailure = TaskFailureFromException(fromException.InnerException), + InnerFailure = TaskFailureFromException(fromException.InnerException, exceptionPropertiesProvider), IsNonRetriable = false }; + + // Add custom properties if provider is available + if (exceptionPropertiesProvider != null) + { + var customProperties = exceptionPropertiesProvider.GetExceptionProperties(fromException); + if (customProperties != null && customProperties.Count > 0) + { + foreach (var property in customProperties) + { + failureDetails.Properties[property.Key] = ConvertObjectToValue(property.Value); + } + } + } + + return failureDetails; + } + + private static Value ConvertObjectToValue(object? value) + { + return value switch + { + null => Value.ForNull(), + string str => Value.ForString(str), + bool b => Value.ForBool(b), + int i => Value.ForNumber(i), + long l => Value.ForNumber(l), + float f => Value.ForNumber(f), + double d => Value.ForNumber(d), + decimal dec => Value.ForNumber((double)dec), + DateTime dt => Value.ForString(dt.ToString("O")), + DateTimeOffset dto => Value.ForString(dto.ToString("O")), + Guid guid => Value.ForString(guid.ToString()), + IDictionary dict => Value.ForStruct(new Struct + { + Fields = { dict.ToDictionary(kvp => kvp.Key, kvp => ConvertObjectToValue(kvp.Value)) }, + }), + IEnumerable list => Value.ForList(list.Select(ConvertObjectToValue).ToArray()), + _ => Value.ForString(value.ToString() ?? string.Empty), // Fallback to string representation + }; } } diff --git a/src/Worker.Extensions.DurableTask/Worker.Extensions.DurableTask.csproj b/src/Worker.Extensions.DurableTask/Worker.Extensions.DurableTask.csproj index 5c5cedd50..e1b3bdb40 100644 --- a/src/Worker.Extensions.DurableTask/Worker.Extensions.DurableTask.csproj +++ b/src/Worker.Extensions.DurableTask/Worker.Extensions.DurableTask.csproj @@ -29,8 +29,8 @@ ..\..\sign.snk - 1.8.0 - + 1.9.0 + test0 $(VersionPrefix).0 $(VersionPrefix).$(FileVersionRevision) diff --git a/test/e2e/Apps/BasicDotNetIsolated/CustomExceptionPropertiesOrchestration.cs b/test/e2e/Apps/BasicDotNetIsolated/CustomExceptionPropertiesOrchestration.cs new file mode 100644 index 000000000..2c274a0b5 --- /dev/null +++ b/test/e2e/Apps/BasicDotNetIsolated/CustomExceptionPropertiesOrchestration.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.DurableTask; +using Microsoft.DurableTask.Client; +using Microsoft.Extensions.Logging; +using System.Net; + +namespace Microsoft.Azure.Durable.Tests.E2E; + +public class CustomExceptionPropertiesOrchestration +{ + [Function(nameof(OrchestrationWithCustomException))] + public async Task OrchestrationWithCustomException([OrchestrationTrigger] TaskOrchestrationContext context) + { + // Call the activity that will throw an exception + await context.CallActivityAsync(nameof(BusinessActivity)); + return "Success"; + } + + [Function(nameof(BusinessActivity))] + public void BusinessActivity([ActivityTrigger] TaskActivityContext context) + { + // Throw an exception with custom properties that should be captured + throw new ArgumentOutOfRangeException( + paramName: "age", + actualValue: 150, + message: "Age must be less than 120"); + } +} diff --git a/test/e2e/Apps/BasicDotNetIsolated/Program.cs b/test/e2e/Apps/BasicDotNetIsolated/Program.cs index 462990f02..46926f7f2 100644 --- a/test/e2e/Apps/BasicDotNetIsolated/Program.cs +++ b/test/e2e/Apps/BasicDotNetIsolated/Program.cs @@ -4,6 +4,7 @@ using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.DurableTask.Worker; using System.Diagnostics; var host = new HostBuilder() @@ -14,6 +15,9 @@ // Register a custom service for testing dependency injection in entities services.AddSingleton(); + + // Register the custom exception properties provider + services.AddSingleton(); }) .Build(); @@ -26,4 +30,21 @@ host.Run(); // This empty class is used to demonstrate dependency injection in entities. -internal class MyInjectedService { } \ No newline at end of file +internal class MyInjectedService { } + +// Custom exception properties provider for testing +public class TestExceptionPropertiesProvider : IExceptionPropertiesProvider +{ + public IDictionary? GetExceptionProperties(Exception exception) + { + return exception switch + { + ArgumentOutOfRangeException e => new Dictionary + { + ["Name"] = e.ParamName ?? string.Empty, + ["Value"] = e.ActualValue ?? string.Empty, + }, + _ => null // No custom properties for other exceptions + }; + } +} \ No newline at end of file diff --git a/test/e2e/Tests/Tests/ErrorHandlingTests.cs b/test/e2e/Tests/Tests/ErrorHandlingTests.cs index 31d38b82b..fdd5cc9ab 100644 --- a/test/e2e/Tests/Tests/ErrorHandlingTests.cs +++ b/test/e2e/Tests/Tests/ErrorHandlingTests.cs @@ -223,4 +223,41 @@ public async Task OrchestratorWithCustomRetriedActivityException_ShouldSucceed() Assert.Contains(this.fixture.TestLogs.CoreToolsLogs, x => x.Contains(nameof(OverflowException)) && x.Contains("Inner exception message")); } + + [Fact] + [Trait("PowerShell", "Skip")] // FailureDetails is a dotnet-isolated implementation detail + [Trait("Python", "Skip")] // FailureDetails is a dotnet-isolated implementation detail + [Trait("Node", "Skip")] // FailureDetails is a dotnet-isolated implementation detail + [Trait("Java", "Skip")] // FailureDetails is a dotnet-isolated implementation detail + public async Task CustomExceptionPropertiesInFailureDetails() + { + using HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger("StartOrchestration", "?orchestrationName=OrchestrationWithCustomException"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + string statusQueryGetUri = await DurableHelpers.ParseStatusQueryGetUriAsync(response); + + await DurableHelpers.WaitForOrchestrationStateAsync(statusQueryGetUri, "Failed", 30); + + var orchestrationDetails = await DurableHelpers.GetRunningOrchestrationDetailsAsync(statusQueryGetUri); + + // Deserialize the output to FailureDetails + var failureDetails = JsonConvert.DeserializeObject(orchestrationDetails.Output); + + // Check FailureDetails contains the right error type and error message + Assert.NotNull(failureDetails); + Assert.Equal(typeof(TaskFailedException).FullName, failureDetails.ErrorType); + + // Check that the activity failure is in the inner failure + Assert.NotNull(failureDetails.InnerFailure); + TaskFailureDetails innerFailure = failureDetails.InnerFailure!; + Assert.Equal(typeof(ArgumentOutOfRangeException).FullName, innerFailure.ErrorType); + + // Check that custom properties are included + Assert.NotNull(innerFailure.Properties); + Assert.True(innerFailure.Properties.ContainsKey("Name")); + Assert.True(innerFailure.Properties.ContainsKey("Value")); + + Assert.Equal("age", innerFailure.Properties["Name"]); + Assert.Equal((double)150, innerFailure.Properties["Value"]); + } }