diff --git a/AGENTS.md b/AGENTS.md index 7dc8cbb5e93..ecce76b6a75 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,11 @@ Before starting the implementation or review of any request, you MUST REVIEW the - Update API proto: `make update-go-api` - Always include `-tags test_dep` when running tests. Include the `integration` tag only for integration tests. +## Code Conventions + +- Keep receiver methods in their existing implementation files and unit tests in the corresponding owner test files. Do not create feature files that mix methods or test suites from unrelated owners. +- Use getter methods for reads whenever available, including protobuf fields. Use direct field access for assignments. + ## Development Workflow 1. Inspect surrounding code, tests, and configuration before making changes. diff --git a/api/historyservice/v1/request_response.pb.go b/api/historyservice/v1/request_response.pb.go index 072c3a57dec..630d747f07b 100644 --- a/api/historyservice/v1/request_response.pb.go +++ b/api/historyservice/v1/request_response.pb.go @@ -588,8 +588,10 @@ type GetMutableStateResponse struct { TransientOrSpeculativeTasks *v19.TransientWorkflowTaskInfo `protobuf:"bytes,26,opt,name=transient_or_speculative_tasks,json=transientOrSpeculativeTasks,proto3" json:"transient_or_speculative_tasks,omitempty"` // Nexus operation context for this workflow, used by workers to select payload serialization. NexusSerializationContext *v113.PropagatedSerializationContext `protobuf:"bytes,27,opt,name=nexus_serialization_context,json=nexusSerializationContext,proto3" json:"nexus_serialization_context,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Session assignment and worker destination for this workflow. + SessionState *v110.SessionState `protobuf:"bytes,28,opt,name=session_state,json=sessionState,proto3" json:"session_state,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetMutableStateResponse) Reset() { @@ -776,6 +778,13 @@ func (x *GetMutableStateResponse) GetNexusSerializationContext() *v113.Propagate return nil } +func (x *GetMutableStateResponse) GetSessionState() *v110.SessionState { + if x != nil { + return x.SessionState + } + return nil +} + type PollMutableStateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` NamespaceId string `protobuf:"bytes,1,opt,name=namespace_id,json=namespaceId,proto3" json:"namespace_id,omitempty"` @@ -10851,7 +10860,7 @@ const file_temporal_server_api_historyservice_v1_request_response_proto_rawDesc "\x16expected_next_event_id\x18\x03 \x01(\x03R\x13expectedNextEventId\x120\n" + "\x14current_branch_token\x18\x04 \x01(\fR\x12currentBranchToken\x12d\n" + "\x14version_history_item\x18\x05 \x01(\v22.temporal.server.api.history.v1.VersionHistoryItemR\x12versionHistoryItem\x12j\n" + - "\x14versioned_transition\x18\x06 \x01(\v27.temporal.server.api.persistence.v1.VersionedTransitionR\x13versionedTransition:\x1b\x92\xc4\x03\x17*\x15execution.workflow_id\"\xea\r\n" + + "\x14versioned_transition\x18\x06 \x01(\v27.temporal.server.api.persistence.v1.VersionedTransitionR\x13versionedTransition:\x1b\x92\xc4\x03\x17*\x15execution.workflow_id\"\xc1\x0e\n" + "\x17GetMutableStateResponse\x12G\n" + "\texecution\x18\x01 \x01(\v2).temporal.api.common.v1.WorkflowExecutionR\texecution\x12I\n" + "\rworkflow_type\x18\x02 \x01(\v2$.temporal.api.common.v1.WorkflowTypeR\fworkflowType\x12\"\n" + @@ -10875,7 +10884,8 @@ const file_temporal_server_api_historyservice_v1_request_response_proto_rawDesc "\x12transition_history\x18\x18 \x03(\v27.temporal.server.api.persistence.v1.VersionedTransitionR\x11transitionHistory\x12b\n" + "\x0fversioning_info\x18\x19 \x01(\v29.temporal.api.workflow.v1.WorkflowExecutionVersioningInfoR\x0eversioningInfo\x12~\n" + "\x1etransient_or_speculative_tasks\x18\x1a \x01(\v29.temporal.server.api.history.v1.TransientWorkflowTaskInfoR\x1btransientOrSpeculativeTasks\x12u\n" + - "\x1bnexus_serialization_context\x18\x1b \x01(\v25.temporal.api.nexus.v1.PropagatedSerializationContextR\x19nexusSerializationContextJ\x04\b\b\x10\tJ\x04\b\t\x10\n" + + "\x1bnexus_serialization_context\x18\x1b \x01(\v25.temporal.api.nexus.v1.PropagatedSerializationContextR\x19nexusSerializationContext\x12U\n" + + "\rsession_state\x18\x1c \x01(\v20.temporal.server.api.persistence.v1.SessionStateR\fsessionStateJ\x04\b\b\x10\tJ\x04\b\t\x10\n" + "J\x04\b\n" + "\x10\vJ\x04\b\f\x10\rJ\x04\b\x0e\x10\x0f\"\xef\x02\n" + "\x17PollMutableStateRequest\x12!\n" + @@ -11805,97 +11815,98 @@ var file_temporal_server_api_historyservice_v1_request_response_proto_goTypes = (*v15.WorkflowExecutionVersioningInfo)(nil), // 199: temporal.api.workflow.v1.WorkflowExecutionVersioningInfo (*v19.TransientWorkflowTaskInfo)(nil), // 200: temporal.server.api.history.v1.TransientWorkflowTaskInfo (*v113.PropagatedSerializationContext)(nil), // 201: temporal.api.nexus.v1.PropagatedSerializationContext - (*v1.PollWorkflowTaskQueueRequest)(nil), // 202: temporal.api.workflowservice.v1.PollWorkflowTaskQueueRequest - (*v114.BuildIdRedirectInfo)(nil), // 203: temporal.server.api.taskqueue.v1.BuildIdRedirectInfo - (*v16.Deployment)(nil), // 204: temporal.api.deployment.v1.Deployment - (*v114.TaskVersionDirective)(nil), // 205: temporal.server.api.taskqueue.v1.TaskVersionDirective - (*v116.Message)(nil), // 206: temporal.api.protocol.v1.Message - (*v17.History)(nil), // 207: temporal.api.history.v1.History - (*v1.PollActivityTaskQueueRequest)(nil), // 208: temporal.api.workflowservice.v1.PollActivityTaskQueueRequest - (*v17.HistoryEvent)(nil), // 209: temporal.api.history.v1.HistoryEvent - (*v14.Priority)(nil), // 210: temporal.api.common.v1.Priority - (*v14.RetryPolicy)(nil), // 211: temporal.api.common.v1.RetryPolicy - (*v1.RespondWorkflowTaskCompletedRequest)(nil), // 212: temporal.api.workflowservice.v1.RespondWorkflowTaskCompletedRequest - (*v1.PollActivityTaskQueueResponse)(nil), // 213: temporal.api.workflowservice.v1.PollActivityTaskQueueResponse - (*v1.RespondWorkflowTaskFailedRequest)(nil), // 214: temporal.api.workflowservice.v1.RespondWorkflowTaskFailedRequest - (*v1.RecordActivityTaskHeartbeatRequest)(nil), // 215: temporal.api.workflowservice.v1.RecordActivityTaskHeartbeatRequest - (*v1.RespondActivityTaskCompletedRequest)(nil), // 216: temporal.api.workflowservice.v1.RespondActivityTaskCompletedRequest - (*v1.RespondActivityTaskFailedRequest)(nil), // 217: temporal.api.workflowservice.v1.RespondActivityTaskFailedRequest - (*v1.RespondActivityTaskCanceledRequest)(nil), // 218: temporal.api.workflowservice.v1.RespondActivityTaskCanceledRequest - (*v1.SignalWorkflowExecutionRequest)(nil), // 219: temporal.api.workflowservice.v1.SignalWorkflowExecutionRequest - (*v1.SignalWithStartWorkflowExecutionRequest)(nil), // 220: temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest - (*v1.TerminateWorkflowExecutionRequest)(nil), // 221: temporal.api.workflowservice.v1.TerminateWorkflowExecutionRequest - (*v1.ResetWorkflowExecutionRequest)(nil), // 222: temporal.api.workflowservice.v1.ResetWorkflowExecutionRequest - (*v1.RequestCancelWorkflowExecutionRequest)(nil), // 223: temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionRequest - (*v1.DescribeWorkflowExecutionRequest)(nil), // 224: temporal.api.workflowservice.v1.DescribeWorkflowExecutionRequest - (*v15.WorkflowExecutionConfig)(nil), // 225: temporal.api.workflow.v1.WorkflowExecutionConfig - (*v15.WorkflowExecutionInfo)(nil), // 226: temporal.api.workflow.v1.WorkflowExecutionInfo - (*v15.PendingActivityInfo)(nil), // 227: temporal.api.workflow.v1.PendingActivityInfo - (*v15.PendingChildExecutionInfo)(nil), // 228: temporal.api.workflow.v1.PendingChildExecutionInfo - (*v15.PendingWorkflowTaskInfo)(nil), // 229: temporal.api.workflow.v1.PendingWorkflowTaskInfo - (*v15.CallbackInfo)(nil), // 230: temporal.api.workflow.v1.CallbackInfo - (*v15.PendingNexusOperationInfo)(nil), // 231: temporal.api.workflow.v1.PendingNexusOperationInfo - (*v15.WorkflowExecutionExtendedInfo)(nil), // 232: temporal.api.workflow.v1.WorkflowExecutionExtendedInfo - (*v14.DataBlob)(nil), // 233: temporal.api.common.v1.DataBlob - (*v11.BaseExecutionInfo)(nil), // 234: temporal.server.api.workflow.v1.BaseExecutionInfo - (*v110.WorkflowMutableState)(nil), // 235: temporal.server.api.persistence.v1.WorkflowMutableState - (*v19.VersionHistory)(nil), // 236: temporal.server.api.history.v1.VersionHistory - (*v117.NamespaceCacheInfo)(nil), // 237: temporal.server.api.namespace.v1.NamespaceCacheInfo - (*v110.ShardInfo)(nil), // 238: temporal.server.api.persistence.v1.ShardInfo - (*v118.ReplicationToken)(nil), // 239: temporal.server.api.replication.v1.ReplicationToken - (*v118.ReplicationTaskInfo)(nil), // 240: temporal.server.api.replication.v1.ReplicationTaskInfo - (*v118.ReplicationTask)(nil), // 241: temporal.server.api.replication.v1.ReplicationTask - (*v1.QueryWorkflowRequest)(nil), // 242: temporal.api.workflowservice.v1.QueryWorkflowRequest - (*v1.QueryWorkflowResponse)(nil), // 243: temporal.api.workflowservice.v1.QueryWorkflowResponse - (*v119.ReapplyEventsRequest)(nil), // 244: temporal.server.api.adminservice.v1.ReapplyEventsRequest - (v112.DeadLetterQueueType)(0), // 245: temporal.server.api.enums.v1.DeadLetterQueueType - (*v119.RefreshWorkflowTasksRequest)(nil), // 246: temporal.server.api.adminservice.v1.RefreshWorkflowTasksRequest - (*v1.UpdateWorkflowExecutionRequest)(nil), // 247: temporal.api.workflowservice.v1.UpdateWorkflowExecutionRequest - (*v1.UpdateWorkflowExecutionResponse)(nil), // 248: temporal.api.workflowservice.v1.UpdateWorkflowExecutionResponse - (*v118.SyncReplicationState)(nil), // 249: temporal.server.api.replication.v1.SyncReplicationState - (*v118.WorkflowReplicationMessages)(nil), // 250: temporal.server.api.replication.v1.WorkflowReplicationMessages - (*v1.PollWorkflowExecutionUpdateRequest)(nil), // 251: temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateRequest - (*v1.PollWorkflowExecutionUpdateResponse)(nil), // 252: temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateResponse - (*v1.GetWorkflowExecutionHistoryRequest)(nil), // 253: temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryRequest - (*v1.GetWorkflowExecutionHistoryResponse)(nil), // 254: temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse - (*v1.GetWorkflowExecutionHistoryReverseRequest)(nil), // 255: temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseRequest - (*v1.GetWorkflowExecutionHistoryReverseResponse)(nil), // 256: temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseResponse - (*v119.GetWorkflowExecutionRawHistoryV2Request)(nil), // 257: temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Request - (*v119.GetWorkflowExecutionRawHistoryV2Response)(nil), // 258: temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Response - (*v119.GetWorkflowExecutionRawHistoryRequest)(nil), // 259: temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryRequest - (*v119.GetWorkflowExecutionRawHistoryResponse)(nil), // 260: temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryResponse - (*v119.DeleteWorkflowExecutionRequest)(nil), // 261: temporal.server.api.adminservice.v1.DeleteWorkflowExecutionRequest - (*v119.DeleteWorkflowExecutionResponse)(nil), // 262: temporal.server.api.adminservice.v1.DeleteWorkflowExecutionResponse - (*v120.HistoryDLQKey)(nil), // 263: temporal.server.api.common.v1.HistoryDLQKey - (*v120.HistoryDLQTask)(nil), // 264: temporal.server.api.common.v1.HistoryDLQTask - (*v120.HistoryDLQTaskMetadata)(nil), // 265: temporal.server.api.common.v1.HistoryDLQTaskMetadata - (*v119.ListHistoryTasksRequest)(nil), // 266: temporal.server.api.adminservice.v1.ListHistoryTasksRequest - (*v119.ListHistoryTasksResponse)(nil), // 267: temporal.server.api.adminservice.v1.ListHistoryTasksResponse - (*v121.NexusOperationCompletion)(nil), // 268: temporal.server.api.token.v1.NexusOperationCompletion - (*v14.Payload)(nil), // 269: temporal.api.common.v1.Payload - (*v113.Failure)(nil), // 270: temporal.api.nexus.v1.Failure - (*v110.StateMachineRef)(nil), // 271: temporal.server.api.persistence.v1.StateMachineRef - (v112.HealthState)(0), // 272: temporal.server.api.enums.v1.HealthState - (*v122.HealthCheck)(nil), // 273: temporal.server.api.health.v1.HealthCheck - (*v118.VersionedTransitionArtifact)(nil), // 274: temporal.server.api.replication.v1.VersionedTransitionArtifact - (*v1.UpdateActivityOptionsRequest)(nil), // 275: temporal.api.workflowservice.v1.UpdateActivityOptionsRequest - (*v123.ActivityOptions)(nil), // 276: temporal.api.activity.v1.ActivityOptions - (*v1.PauseActivityRequest)(nil), // 277: temporal.api.workflowservice.v1.PauseActivityRequest - (*v1.UnpauseActivityRequest)(nil), // 278: temporal.api.workflowservice.v1.UnpauseActivityRequest - (*v1.ResetActivityRequest)(nil), // 279: temporal.api.workflowservice.v1.ResetActivityRequest - (*v1.UpdateWorkflowExecutionOptionsRequest)(nil), // 280: temporal.api.workflowservice.v1.UpdateWorkflowExecutionOptionsRequest - (*v15.WorkflowExecutionOptions)(nil), // 281: temporal.api.workflow.v1.WorkflowExecutionOptions - (*v1.PauseWorkflowExecutionRequest)(nil), // 282: temporal.api.workflowservice.v1.PauseWorkflowExecutionRequest - (*v1.UnpauseWorkflowExecutionRequest)(nil), // 283: temporal.api.workflowservice.v1.UnpauseWorkflowExecutionRequest - (*v113.StartOperationRequest)(nil), // 284: temporal.api.nexus.v1.StartOperationRequest - (*v113.StartOperationResponse)(nil), // 285: temporal.api.nexus.v1.StartOperationResponse - (*v113.CancelOperationRequest)(nil), // 286: temporal.api.nexus.v1.CancelOperationRequest - (*v113.CancelOperationResponse)(nil), // 287: temporal.api.nexus.v1.CancelOperationResponse - (*v1.PollWorkflowExecutionTimeSkippingRequest)(nil), // 288: temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingRequest - (*v1.PollWorkflowExecutionTimeSkippingResponse)(nil), // 289: temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingResponse - (*v115.WorkflowQuery)(nil), // 290: temporal.api.query.v1.WorkflowQuery - (*v118.ReplicationMessages)(nil), // 291: temporal.server.api.replication.v1.ReplicationMessages - (*descriptorpb.MessageOptions)(nil), // 292: google.protobuf.MessageOptions + (*v110.SessionState)(nil), // 202: temporal.server.api.persistence.v1.SessionState + (*v1.PollWorkflowTaskQueueRequest)(nil), // 203: temporal.api.workflowservice.v1.PollWorkflowTaskQueueRequest + (*v114.BuildIdRedirectInfo)(nil), // 204: temporal.server.api.taskqueue.v1.BuildIdRedirectInfo + (*v16.Deployment)(nil), // 205: temporal.api.deployment.v1.Deployment + (*v114.TaskVersionDirective)(nil), // 206: temporal.server.api.taskqueue.v1.TaskVersionDirective + (*v116.Message)(nil), // 207: temporal.api.protocol.v1.Message + (*v17.History)(nil), // 208: temporal.api.history.v1.History + (*v1.PollActivityTaskQueueRequest)(nil), // 209: temporal.api.workflowservice.v1.PollActivityTaskQueueRequest + (*v17.HistoryEvent)(nil), // 210: temporal.api.history.v1.HistoryEvent + (*v14.Priority)(nil), // 211: temporal.api.common.v1.Priority + (*v14.RetryPolicy)(nil), // 212: temporal.api.common.v1.RetryPolicy + (*v1.RespondWorkflowTaskCompletedRequest)(nil), // 213: temporal.api.workflowservice.v1.RespondWorkflowTaskCompletedRequest + (*v1.PollActivityTaskQueueResponse)(nil), // 214: temporal.api.workflowservice.v1.PollActivityTaskQueueResponse + (*v1.RespondWorkflowTaskFailedRequest)(nil), // 215: temporal.api.workflowservice.v1.RespondWorkflowTaskFailedRequest + (*v1.RecordActivityTaskHeartbeatRequest)(nil), // 216: temporal.api.workflowservice.v1.RecordActivityTaskHeartbeatRequest + (*v1.RespondActivityTaskCompletedRequest)(nil), // 217: temporal.api.workflowservice.v1.RespondActivityTaskCompletedRequest + (*v1.RespondActivityTaskFailedRequest)(nil), // 218: temporal.api.workflowservice.v1.RespondActivityTaskFailedRequest + (*v1.RespondActivityTaskCanceledRequest)(nil), // 219: temporal.api.workflowservice.v1.RespondActivityTaskCanceledRequest + (*v1.SignalWorkflowExecutionRequest)(nil), // 220: temporal.api.workflowservice.v1.SignalWorkflowExecutionRequest + (*v1.SignalWithStartWorkflowExecutionRequest)(nil), // 221: temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest + (*v1.TerminateWorkflowExecutionRequest)(nil), // 222: temporal.api.workflowservice.v1.TerminateWorkflowExecutionRequest + (*v1.ResetWorkflowExecutionRequest)(nil), // 223: temporal.api.workflowservice.v1.ResetWorkflowExecutionRequest + (*v1.RequestCancelWorkflowExecutionRequest)(nil), // 224: temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionRequest + (*v1.DescribeWorkflowExecutionRequest)(nil), // 225: temporal.api.workflowservice.v1.DescribeWorkflowExecutionRequest + (*v15.WorkflowExecutionConfig)(nil), // 226: temporal.api.workflow.v1.WorkflowExecutionConfig + (*v15.WorkflowExecutionInfo)(nil), // 227: temporal.api.workflow.v1.WorkflowExecutionInfo + (*v15.PendingActivityInfo)(nil), // 228: temporal.api.workflow.v1.PendingActivityInfo + (*v15.PendingChildExecutionInfo)(nil), // 229: temporal.api.workflow.v1.PendingChildExecutionInfo + (*v15.PendingWorkflowTaskInfo)(nil), // 230: temporal.api.workflow.v1.PendingWorkflowTaskInfo + (*v15.CallbackInfo)(nil), // 231: temporal.api.workflow.v1.CallbackInfo + (*v15.PendingNexusOperationInfo)(nil), // 232: temporal.api.workflow.v1.PendingNexusOperationInfo + (*v15.WorkflowExecutionExtendedInfo)(nil), // 233: temporal.api.workflow.v1.WorkflowExecutionExtendedInfo + (*v14.DataBlob)(nil), // 234: temporal.api.common.v1.DataBlob + (*v11.BaseExecutionInfo)(nil), // 235: temporal.server.api.workflow.v1.BaseExecutionInfo + (*v110.WorkflowMutableState)(nil), // 236: temporal.server.api.persistence.v1.WorkflowMutableState + (*v19.VersionHistory)(nil), // 237: temporal.server.api.history.v1.VersionHistory + (*v117.NamespaceCacheInfo)(nil), // 238: temporal.server.api.namespace.v1.NamespaceCacheInfo + (*v110.ShardInfo)(nil), // 239: temporal.server.api.persistence.v1.ShardInfo + (*v118.ReplicationToken)(nil), // 240: temporal.server.api.replication.v1.ReplicationToken + (*v118.ReplicationTaskInfo)(nil), // 241: temporal.server.api.replication.v1.ReplicationTaskInfo + (*v118.ReplicationTask)(nil), // 242: temporal.server.api.replication.v1.ReplicationTask + (*v1.QueryWorkflowRequest)(nil), // 243: temporal.api.workflowservice.v1.QueryWorkflowRequest + (*v1.QueryWorkflowResponse)(nil), // 244: temporal.api.workflowservice.v1.QueryWorkflowResponse + (*v119.ReapplyEventsRequest)(nil), // 245: temporal.server.api.adminservice.v1.ReapplyEventsRequest + (v112.DeadLetterQueueType)(0), // 246: temporal.server.api.enums.v1.DeadLetterQueueType + (*v119.RefreshWorkflowTasksRequest)(nil), // 247: temporal.server.api.adminservice.v1.RefreshWorkflowTasksRequest + (*v1.UpdateWorkflowExecutionRequest)(nil), // 248: temporal.api.workflowservice.v1.UpdateWorkflowExecutionRequest + (*v1.UpdateWorkflowExecutionResponse)(nil), // 249: temporal.api.workflowservice.v1.UpdateWorkflowExecutionResponse + (*v118.SyncReplicationState)(nil), // 250: temporal.server.api.replication.v1.SyncReplicationState + (*v118.WorkflowReplicationMessages)(nil), // 251: temporal.server.api.replication.v1.WorkflowReplicationMessages + (*v1.PollWorkflowExecutionUpdateRequest)(nil), // 252: temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateRequest + (*v1.PollWorkflowExecutionUpdateResponse)(nil), // 253: temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateResponse + (*v1.GetWorkflowExecutionHistoryRequest)(nil), // 254: temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryRequest + (*v1.GetWorkflowExecutionHistoryResponse)(nil), // 255: temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse + (*v1.GetWorkflowExecutionHistoryReverseRequest)(nil), // 256: temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseRequest + (*v1.GetWorkflowExecutionHistoryReverseResponse)(nil), // 257: temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseResponse + (*v119.GetWorkflowExecutionRawHistoryV2Request)(nil), // 258: temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Request + (*v119.GetWorkflowExecutionRawHistoryV2Response)(nil), // 259: temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Response + (*v119.GetWorkflowExecutionRawHistoryRequest)(nil), // 260: temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryRequest + (*v119.GetWorkflowExecutionRawHistoryResponse)(nil), // 261: temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryResponse + (*v119.DeleteWorkflowExecutionRequest)(nil), // 262: temporal.server.api.adminservice.v1.DeleteWorkflowExecutionRequest + (*v119.DeleteWorkflowExecutionResponse)(nil), // 263: temporal.server.api.adminservice.v1.DeleteWorkflowExecutionResponse + (*v120.HistoryDLQKey)(nil), // 264: temporal.server.api.common.v1.HistoryDLQKey + (*v120.HistoryDLQTask)(nil), // 265: temporal.server.api.common.v1.HistoryDLQTask + (*v120.HistoryDLQTaskMetadata)(nil), // 266: temporal.server.api.common.v1.HistoryDLQTaskMetadata + (*v119.ListHistoryTasksRequest)(nil), // 267: temporal.server.api.adminservice.v1.ListHistoryTasksRequest + (*v119.ListHistoryTasksResponse)(nil), // 268: temporal.server.api.adminservice.v1.ListHistoryTasksResponse + (*v121.NexusOperationCompletion)(nil), // 269: temporal.server.api.token.v1.NexusOperationCompletion + (*v14.Payload)(nil), // 270: temporal.api.common.v1.Payload + (*v113.Failure)(nil), // 271: temporal.api.nexus.v1.Failure + (*v110.StateMachineRef)(nil), // 272: temporal.server.api.persistence.v1.StateMachineRef + (v112.HealthState)(0), // 273: temporal.server.api.enums.v1.HealthState + (*v122.HealthCheck)(nil), // 274: temporal.server.api.health.v1.HealthCheck + (*v118.VersionedTransitionArtifact)(nil), // 275: temporal.server.api.replication.v1.VersionedTransitionArtifact + (*v1.UpdateActivityOptionsRequest)(nil), // 276: temporal.api.workflowservice.v1.UpdateActivityOptionsRequest + (*v123.ActivityOptions)(nil), // 277: temporal.api.activity.v1.ActivityOptions + (*v1.PauseActivityRequest)(nil), // 278: temporal.api.workflowservice.v1.PauseActivityRequest + (*v1.UnpauseActivityRequest)(nil), // 279: temporal.api.workflowservice.v1.UnpauseActivityRequest + (*v1.ResetActivityRequest)(nil), // 280: temporal.api.workflowservice.v1.ResetActivityRequest + (*v1.UpdateWorkflowExecutionOptionsRequest)(nil), // 281: temporal.api.workflowservice.v1.UpdateWorkflowExecutionOptionsRequest + (*v15.WorkflowExecutionOptions)(nil), // 282: temporal.api.workflow.v1.WorkflowExecutionOptions + (*v1.PauseWorkflowExecutionRequest)(nil), // 283: temporal.api.workflowservice.v1.PauseWorkflowExecutionRequest + (*v1.UnpauseWorkflowExecutionRequest)(nil), // 284: temporal.api.workflowservice.v1.UnpauseWorkflowExecutionRequest + (*v113.StartOperationRequest)(nil), // 285: temporal.api.nexus.v1.StartOperationRequest + (*v113.StartOperationResponse)(nil), // 286: temporal.api.nexus.v1.StartOperationResponse + (*v113.CancelOperationRequest)(nil), // 287: temporal.api.nexus.v1.CancelOperationRequest + (*v113.CancelOperationResponse)(nil), // 288: temporal.api.nexus.v1.CancelOperationResponse + (*v1.PollWorkflowExecutionTimeSkippingRequest)(nil), // 289: temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingRequest + (*v1.PollWorkflowExecutionTimeSkippingResponse)(nil), // 290: temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingResponse + (*v115.WorkflowQuery)(nil), // 291: temporal.api.query.v1.WorkflowQuery + (*v118.ReplicationMessages)(nil), // 292: temporal.server.api.replication.v1.ReplicationMessages + (*descriptorpb.MessageOptions)(nil), // 293: google.protobuf.MessageOptions } var file_temporal_server_api_historyservice_v1_request_response_proto_depIdxs = []int32{ 174, // 0: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.start_request:type_name -> temporal.api.workflowservice.v1.StartWorkflowExecutionRequest @@ -11933,250 +11944,251 @@ var file_temporal_server_api_historyservice_v1_request_response_proto_depIdxs = 199, // 32: temporal.server.api.historyservice.v1.GetMutableStateResponse.versioning_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionVersioningInfo 200, // 33: temporal.server.api.historyservice.v1.GetMutableStateResponse.transient_or_speculative_tasks:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo 201, // 34: temporal.server.api.historyservice.v1.GetMutableStateResponse.nexus_serialization_context:type_name -> temporal.api.nexus.v1.PropagatedSerializationContext - 192, // 35: temporal.server.api.historyservice.v1.PollMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 193, // 36: temporal.server.api.historyservice.v1.PollMutableStateRequest.version_history_item:type_name -> temporal.server.api.history.v1.VersionHistoryItem - 192, // 37: temporal.server.api.historyservice.v1.PollMutableStateResponse.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 195, // 38: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 196, // 39: temporal.server.api.historyservice.v1.PollMutableStateResponse.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 196, // 40: temporal.server.api.historyservice.v1.PollMutableStateResponse.sticky_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 180, // 41: temporal.server.api.historyservice.v1.PollMutableStateResponse.sticky_task_queue_schedule_to_start_timeout:type_name -> google.protobuf.Duration - 198, // 42: temporal.server.api.historyservice.v1.PollMutableStateResponse.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories - 197, // 43: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_state:type_name -> temporal.server.api.enums.v1.WorkflowExecutionState - 190, // 44: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_status:type_name -> temporal.api.enums.v1.WorkflowExecutionStatus - 192, // 45: temporal.server.api.historyservice.v1.ResetStickyTaskQueueRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 165, // 46: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.operations:type_name -> temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation - 166, // 47: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.responses:type_name -> temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response - 192, // 48: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 202, // 49: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.poll_request:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueRequest - 188, // 50: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 203, // 51: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.build_id_redirect_info:type_name -> temporal.server.api.taskqueue.v1.BuildIdRedirectInfo - 204, // 52: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.scheduled_deployment:type_name -> temporal.api.deployment.v1.Deployment - 205, // 53: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective - 184, // 54: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.target_deployment_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion - 195, // 55: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 200, // 56: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo - 196, // 57: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 176, // 58: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.scheduled_time:type_name -> google.protobuf.Timestamp - 176, // 59: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp - 167, // 60: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry - 188, // 61: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 206, // 62: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.messages:type_name -> temporal.api.protocol.v1.Message - 207, // 63: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.history:type_name -> temporal.api.history.v1.History - 207, // 64: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.raw_history:type_name -> temporal.api.history.v1.History - 195, // 65: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 200, // 66: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo - 196, // 67: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 176, // 68: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.scheduled_time:type_name -> google.protobuf.Timestamp - 176, // 69: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.started_time:type_name -> google.protobuf.Timestamp - 168, // 70: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry - 188, // 71: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 206, // 72: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.messages:type_name -> temporal.api.protocol.v1.Message - 207, // 73: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.history:type_name -> temporal.api.history.v1.History - 192, // 74: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 208, // 75: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.poll_request:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueRequest - 188, // 76: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 203, // 77: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.build_id_redirect_info:type_name -> temporal.server.api.taskqueue.v1.BuildIdRedirectInfo - 204, // 78: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.scheduled_deployment:type_name -> temporal.api.deployment.v1.Deployment - 205, // 79: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective - 209, // 80: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.scheduled_event:type_name -> temporal.api.history.v1.HistoryEvent - 176, // 81: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp - 176, // 82: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.current_attempt_scheduled_time:type_name -> google.protobuf.Timestamp - 179, // 83: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.heartbeat_details:type_name -> temporal.api.common.v1.Payloads - 195, // 84: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 188, // 85: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 210, // 86: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.priority:type_name -> temporal.api.common.v1.Priority - 211, // 87: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.retry_policy:type_name -> temporal.api.common.v1.RetryPolicy - 201, // 88: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.nexus_serialization_context:type_name -> temporal.api.nexus.v1.PropagatedSerializationContext - 212, // 89: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskCompletedRequest - 12, // 90: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.started_response:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse - 213, // 91: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.activity_tasks:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueResponse - 189, // 92: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.new_workflow_task:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponse - 214, // 93: temporal.server.api.historyservice.v1.RespondWorkflowTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskFailedRequest - 192, // 94: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 188, // 95: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 215, // 96: temporal.server.api.historyservice.v1.RecordActivityTaskHeartbeatRequest.heartbeat_request:type_name -> temporal.api.workflowservice.v1.RecordActivityTaskHeartbeatRequest - 216, // 97: temporal.server.api.historyservice.v1.RespondActivityTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCompletedRequest - 217, // 98: temporal.server.api.historyservice.v1.RespondActivityTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskFailedRequest - 218, // 99: temporal.server.api.historyservice.v1.RespondActivityTaskCanceledRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCanceledRequest - 192, // 100: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 188, // 101: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 219, // 102: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.signal_request:type_name -> temporal.api.workflowservice.v1.SignalWorkflowExecutionRequest - 192, // 103: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 191, // 104: temporal.server.api.historyservice.v1.SignalWorkflowExecutionResponse.link:type_name -> temporal.api.common.v1.Link - 220, // 105: temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionRequest.signal_with_start_request:type_name -> temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest - 191, // 106: temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionResponse.signal_link:type_name -> temporal.api.common.v1.Link - 192, // 107: temporal.server.api.historyservice.v1.RemoveSignalMutableStateRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 221, // 108: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.terminate_request:type_name -> temporal.api.workflowservice.v1.TerminateWorkflowExecutionRequest - 192, // 109: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 192, // 110: temporal.server.api.historyservice.v1.DeleteWorkflowExecutionRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 222, // 111: temporal.server.api.historyservice.v1.ResetWorkflowExecutionRequest.reset_request:type_name -> temporal.api.workflowservice.v1.ResetWorkflowExecutionRequest - 223, // 112: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionRequest - 192, // 113: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 192, // 114: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 188, // 115: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.child_clock:type_name -> temporal.server.api.clock.v1.VectorClock - 188, // 116: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.parent_clock:type_name -> temporal.server.api.clock.v1.VectorClock - 192, // 117: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 188, // 118: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 192, // 119: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 192, // 120: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 209, // 121: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.completion_event:type_name -> temporal.api.history.v1.HistoryEvent - 188, // 122: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 192, // 123: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 192, // 124: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 188, // 125: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 224, // 126: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.DescribeWorkflowExecutionRequest - 225, // 127: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.execution_config:type_name -> temporal.api.workflow.v1.WorkflowExecutionConfig - 226, // 128: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_execution_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionInfo - 227, // 129: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_activities:type_name -> temporal.api.workflow.v1.PendingActivityInfo - 228, // 130: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_children:type_name -> temporal.api.workflow.v1.PendingChildExecutionInfo - 229, // 131: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_workflow_task:type_name -> temporal.api.workflow.v1.PendingWorkflowTaskInfo - 230, // 132: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.callbacks:type_name -> temporal.api.workflow.v1.CallbackInfo - 231, // 133: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_nexus_operations:type_name -> temporal.api.workflow.v1.PendingNexusOperationInfo - 232, // 134: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_extended_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionExtendedInfo - 192, // 135: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 193, // 136: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.version_history_items:type_name -> temporal.server.api.history.v1.VersionHistoryItem - 233, // 137: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.events:type_name -> temporal.api.common.v1.DataBlob - 233, // 138: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.new_run_events:type_name -> temporal.api.common.v1.DataBlob - 234, // 139: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo - 235, // 140: temporal.server.api.historyservice.v1.ReplicateWorkflowStateRequest.workflow_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState - 176, // 141: temporal.server.api.historyservice.v1.SyncShardStatusRequest.status_time:type_name -> google.protobuf.Timestamp - 176, // 142: temporal.server.api.historyservice.v1.SyncActivityRequest.scheduled_time:type_name -> google.protobuf.Timestamp - 176, // 143: temporal.server.api.historyservice.v1.SyncActivityRequest.started_time:type_name -> google.protobuf.Timestamp - 176, // 144: temporal.server.api.historyservice.v1.SyncActivityRequest.last_heartbeat_time:type_name -> google.protobuf.Timestamp - 179, // 145: temporal.server.api.historyservice.v1.SyncActivityRequest.details:type_name -> temporal.api.common.v1.Payloads - 178, // 146: temporal.server.api.historyservice.v1.SyncActivityRequest.last_failure:type_name -> temporal.api.failure.v1.Failure - 236, // 147: temporal.server.api.historyservice.v1.SyncActivityRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory - 234, // 148: temporal.server.api.historyservice.v1.SyncActivityRequest.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo - 176, // 149: temporal.server.api.historyservice.v1.SyncActivityRequest.first_scheduled_time:type_name -> google.protobuf.Timestamp - 176, // 150: temporal.server.api.historyservice.v1.SyncActivityRequest.last_attempt_complete_time:type_name -> google.protobuf.Timestamp - 180, // 151: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_initial_interval:type_name -> google.protobuf.Duration - 180, // 152: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_maximum_interval:type_name -> google.protobuf.Duration - 64, // 153: temporal.server.api.historyservice.v1.SyncActivitiesRequest.activities_info:type_name -> temporal.server.api.historyservice.v1.ActivitySyncInfo - 176, // 154: temporal.server.api.historyservice.v1.ActivitySyncInfo.scheduled_time:type_name -> google.protobuf.Timestamp - 176, // 155: temporal.server.api.historyservice.v1.ActivitySyncInfo.started_time:type_name -> google.protobuf.Timestamp - 176, // 156: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_heartbeat_time:type_name -> google.protobuf.Timestamp - 179, // 157: temporal.server.api.historyservice.v1.ActivitySyncInfo.details:type_name -> temporal.api.common.v1.Payloads - 178, // 158: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_failure:type_name -> temporal.api.failure.v1.Failure - 236, // 159: temporal.server.api.historyservice.v1.ActivitySyncInfo.version_history:type_name -> temporal.server.api.history.v1.VersionHistory - 176, // 160: temporal.server.api.historyservice.v1.ActivitySyncInfo.first_scheduled_time:type_name -> google.protobuf.Timestamp - 176, // 161: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp - 180, // 162: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_initial_interval:type_name -> google.protobuf.Duration - 180, // 163: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_maximum_interval:type_name -> google.protobuf.Duration - 192, // 164: temporal.server.api.historyservice.v1.DescribeMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 235, // 165: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.cache_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState - 235, // 166: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.database_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState - 192, // 167: temporal.server.api.historyservice.v1.DescribeHistoryHostRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 237, // 168: temporal.server.api.historyservice.v1.DescribeHistoryHostResponse.namespace_cache:type_name -> temporal.server.api.namespace.v1.NamespaceCacheInfo - 238, // 169: temporal.server.api.historyservice.v1.GetShardResponse.shard_info:type_name -> temporal.server.api.persistence.v1.ShardInfo - 176, // 170: temporal.server.api.historyservice.v1.RemoveTaskRequest.visibility_time:type_name -> google.protobuf.Timestamp - 239, // 171: temporal.server.api.historyservice.v1.GetReplicationMessagesRequest.tokens:type_name -> temporal.server.api.replication.v1.ReplicationToken - 169, // 172: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.shard_messages:type_name -> temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry - 240, // 173: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesRequest.task_infos:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo - 241, // 174: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask - 242, // 175: temporal.server.api.historyservice.v1.QueryWorkflowRequest.request:type_name -> temporal.api.workflowservice.v1.QueryWorkflowRequest - 243, // 176: temporal.server.api.historyservice.v1.QueryWorkflowResponse.response:type_name -> temporal.api.workflowservice.v1.QueryWorkflowResponse - 244, // 177: temporal.server.api.historyservice.v1.ReapplyEventsRequest.request:type_name -> temporal.server.api.adminservice.v1.ReapplyEventsRequest - 245, // 178: temporal.server.api.historyservice.v1.GetDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 245, // 179: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 241, // 180: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask - 240, // 181: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks_info:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo - 245, // 182: temporal.server.api.historyservice.v1.PurgeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 245, // 183: temporal.server.api.historyservice.v1.MergeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 246, // 184: temporal.server.api.historyservice.v1.RefreshWorkflowTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.RefreshWorkflowTasksRequest - 192, // 185: temporal.server.api.historyservice.v1.GenerateLastHistoryReplicationTasksRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 96, // 186: temporal.server.api.historyservice.v1.GetReplicationStatusResponse.shards:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus - 176, // 187: temporal.server.api.historyservice.v1.ShardReplicationStatus.shard_local_time:type_name -> google.protobuf.Timestamp - 170, // 188: temporal.server.api.historyservice.v1.ShardReplicationStatus.remote_clusters:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry - 171, // 189: temporal.server.api.historyservice.v1.ShardReplicationStatus.handover_namespaces:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry - 176, // 190: temporal.server.api.historyservice.v1.ShardReplicationStatus.max_replication_task_visibility_time:type_name -> google.protobuf.Timestamp - 176, // 191: temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster.acked_task_visibility_time:type_name -> google.protobuf.Timestamp - 192, // 192: temporal.server.api.historyservice.v1.RebuildMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 192, // 193: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 233, // 194: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.history_batches:type_name -> temporal.api.common.v1.DataBlob - 236, // 195: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory - 192, // 196: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 176, // 197: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_start_time:type_name -> google.protobuf.Timestamp - 176, // 198: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_close_time:type_name -> google.protobuf.Timestamp - 247, // 199: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionRequest - 248, // 200: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse.response:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionResponse - 249, // 201: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesRequest.sync_replication_state:type_name -> temporal.server.api.replication.v1.SyncReplicationState - 250, // 202: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesResponse.messages:type_name -> temporal.server.api.replication.v1.WorkflowReplicationMessages - 251, // 203: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateRequest.request:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateRequest - 252, // 204: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateResponse.response:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateResponse - 253, // 205: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryRequest - 254, // 206: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse - 207, // 207: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.history:type_name -> temporal.api.history.v1.History - 254, // 208: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponseWithRaw.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse - 255, // 209: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseRequest - 256, // 210: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseResponse - 257, // 211: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Request.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Request - 258, // 212: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Response.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Response - 259, // 213: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryRequest.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryRequest - 260, // 214: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryResponse.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryResponse - 261, // 215: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionRequest.request:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionRequest - 262, // 216: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionResponse.response:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionResponse - 192, // 217: temporal.server.api.historyservice.v1.DeleteExecutionRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 263, // 218: temporal.server.api.historyservice.v1.GetDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey - 264, // 219: temporal.server.api.historyservice.v1.GetDLQTasksResponse.dlq_tasks:type_name -> temporal.server.api.common.v1.HistoryDLQTask - 263, // 220: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey - 265, // 221: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.inclusive_max_task_metadata:type_name -> temporal.server.api.common.v1.HistoryDLQTaskMetadata - 172, // 222: temporal.server.api.historyservice.v1.ListQueuesResponse.queues:type_name -> temporal.server.api.historyservice.v1.ListQueuesResponse.QueueInfo - 173, // 223: temporal.server.api.historyservice.v1.AddTasksRequest.tasks:type_name -> temporal.server.api.historyservice.v1.AddTasksRequest.Task - 266, // 224: temporal.server.api.historyservice.v1.ListTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksRequest - 267, // 225: temporal.server.api.historyservice.v1.ListTasksResponse.response:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksResponse - 268, // 226: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion - 269, // 227: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.success:type_name -> temporal.api.common.v1.Payload - 178, // 228: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.failure:type_name -> temporal.api.failure.v1.Failure - 176, // 229: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.close_time:type_name -> google.protobuf.Timestamp - 191, // 230: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.links:type_name -> temporal.api.common.v1.Link - 176, // 231: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.start_time:type_name -> google.protobuf.Timestamp - 268, // 232: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion - 269, // 233: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.success:type_name -> temporal.api.common.v1.Payload - 270, // 234: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.failure:type_name -> temporal.api.nexus.v1.Failure - 176, // 235: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.start_time:type_name -> google.protobuf.Timestamp - 191, // 236: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.links:type_name -> temporal.api.common.v1.Link - 271, // 237: temporal.server.api.historyservice.v1.InvokeStateMachineMethodRequest.ref:type_name -> temporal.server.api.persistence.v1.StateMachineRef - 272, // 238: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.state:type_name -> temporal.server.api.enums.v1.HealthState - 273, // 239: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.checks:type_name -> temporal.server.api.health.v1.HealthCheck - 272, // 240: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.unenforced_state:type_name -> temporal.server.api.enums.v1.HealthState - 192, // 241: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 194, // 242: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 198, // 243: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories - 274, // 244: temporal.server.api.historyservice.v1.SyncWorkflowStateResponse.versioned_transition_artifact:type_name -> temporal.server.api.replication.v1.VersionedTransitionArtifact - 275, // 245: temporal.server.api.historyservice.v1.UpdateActivityOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateActivityOptionsRequest - 276, // 246: temporal.server.api.historyservice.v1.UpdateActivityOptionsResponse.activity_options:type_name -> temporal.api.activity.v1.ActivityOptions - 277, // 247: temporal.server.api.historyservice.v1.PauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.PauseActivityRequest - 278, // 248: temporal.server.api.historyservice.v1.UnpauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.UnpauseActivityRequest - 279, // 249: temporal.server.api.historyservice.v1.ResetActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.ResetActivityRequest - 280, // 250: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionOptionsRequest - 281, // 251: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse.workflow_execution_options:type_name -> temporal.api.workflow.v1.WorkflowExecutionOptions - 176, // 252: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse.update_time:type_name -> google.protobuf.Timestamp - 282, // 253: temporal.server.api.historyservice.v1.PauseWorkflowExecutionRequest.pause_request:type_name -> temporal.api.workflowservice.v1.PauseWorkflowExecutionRequest - 283, // 254: temporal.server.api.historyservice.v1.UnpauseWorkflowExecutionRequest.unpause_request:type_name -> temporal.api.workflowservice.v1.UnpauseWorkflowExecutionRequest - 284, // 255: temporal.server.api.historyservice.v1.StartNexusOperationRequest.request:type_name -> temporal.api.nexus.v1.StartOperationRequest - 285, // 256: temporal.server.api.historyservice.v1.StartNexusOperationResponse.response:type_name -> temporal.api.nexus.v1.StartOperationResponse - 286, // 257: temporal.server.api.historyservice.v1.CancelNexusOperationRequest.request:type_name -> temporal.api.nexus.v1.CancelOperationRequest - 287, // 258: temporal.server.api.historyservice.v1.CancelNexusOperationResponse.response:type_name -> temporal.api.nexus.v1.CancelOperationResponse - 288, // 259: temporal.server.api.historyservice.v1.PollWorkflowExecutionTimeSkippingRequest.request:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingRequest - 289, // 260: temporal.server.api.historyservice.v1.PollWorkflowExecutionTimeSkippingResponse.response:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingResponse - 193, // 261: temporal.server.api.historyservice.v1.OrphanedChildReplacementInfo.parent_current_version_history_items:type_name -> temporal.server.api.history.v1.VersionHistoryItem - 1, // 262: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest - 105, // 263: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest - 2, // 264: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse - 106, // 265: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse - 290, // 266: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery - 290, // 267: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery - 291, // 268: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry.value:type_name -> temporal.server.api.replication.v1.ReplicationMessages - 98, // 269: temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry.value:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster - 97, // 270: temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry.value:type_name -> temporal.server.api.historyservice.v1.HandoverNamespaceInfo - 233, // 271: temporal.server.api.historyservice.v1.AddTasksRequest.Task.blob:type_name -> temporal.api.common.v1.DataBlob - 292, // 272: temporal.server.api.historyservice.v1.routing:extendee -> google.protobuf.MessageOptions - 0, // 273: temporal.server.api.historyservice.v1.routing:type_name -> temporal.server.api.historyservice.v1.RoutingOptions - 274, // [274:274] is the sub-list for method output_type - 274, // [274:274] is the sub-list for method input_type - 273, // [273:274] is the sub-list for extension type_name - 272, // [272:273] is the sub-list for extension extendee - 0, // [0:272] is the sub-list for field type_name + 202, // 35: temporal.server.api.historyservice.v1.GetMutableStateResponse.session_state:type_name -> temporal.server.api.persistence.v1.SessionState + 192, // 36: temporal.server.api.historyservice.v1.PollMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 193, // 37: temporal.server.api.historyservice.v1.PollMutableStateRequest.version_history_item:type_name -> temporal.server.api.history.v1.VersionHistoryItem + 192, // 38: temporal.server.api.historyservice.v1.PollMutableStateResponse.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 195, // 39: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 196, // 40: temporal.server.api.historyservice.v1.PollMutableStateResponse.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 196, // 41: temporal.server.api.historyservice.v1.PollMutableStateResponse.sticky_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 180, // 42: temporal.server.api.historyservice.v1.PollMutableStateResponse.sticky_task_queue_schedule_to_start_timeout:type_name -> google.protobuf.Duration + 198, // 43: temporal.server.api.historyservice.v1.PollMutableStateResponse.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories + 197, // 44: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_state:type_name -> temporal.server.api.enums.v1.WorkflowExecutionState + 190, // 45: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_status:type_name -> temporal.api.enums.v1.WorkflowExecutionStatus + 192, // 46: temporal.server.api.historyservice.v1.ResetStickyTaskQueueRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 165, // 47: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.operations:type_name -> temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation + 166, // 48: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.responses:type_name -> temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response + 192, // 49: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 203, // 50: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.poll_request:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueRequest + 188, // 51: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 204, // 52: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.build_id_redirect_info:type_name -> temporal.server.api.taskqueue.v1.BuildIdRedirectInfo + 205, // 53: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.scheduled_deployment:type_name -> temporal.api.deployment.v1.Deployment + 206, // 54: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective + 184, // 55: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.target_deployment_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion + 195, // 56: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 200, // 57: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo + 196, // 58: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 176, // 59: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.scheduled_time:type_name -> google.protobuf.Timestamp + 176, // 60: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp + 167, // 61: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry + 188, // 62: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 207, // 63: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.messages:type_name -> temporal.api.protocol.v1.Message + 208, // 64: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.history:type_name -> temporal.api.history.v1.History + 208, // 65: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.raw_history:type_name -> temporal.api.history.v1.History + 195, // 66: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 200, // 67: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo + 196, // 68: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 176, // 69: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.scheduled_time:type_name -> google.protobuf.Timestamp + 176, // 70: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.started_time:type_name -> google.protobuf.Timestamp + 168, // 71: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry + 188, // 72: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 207, // 73: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.messages:type_name -> temporal.api.protocol.v1.Message + 208, // 74: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.history:type_name -> temporal.api.history.v1.History + 192, // 75: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 209, // 76: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.poll_request:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueRequest + 188, // 77: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 204, // 78: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.build_id_redirect_info:type_name -> temporal.server.api.taskqueue.v1.BuildIdRedirectInfo + 205, // 79: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.scheduled_deployment:type_name -> temporal.api.deployment.v1.Deployment + 206, // 80: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective + 210, // 81: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.scheduled_event:type_name -> temporal.api.history.v1.HistoryEvent + 176, // 82: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp + 176, // 83: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.current_attempt_scheduled_time:type_name -> google.protobuf.Timestamp + 179, // 84: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.heartbeat_details:type_name -> temporal.api.common.v1.Payloads + 195, // 85: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 188, // 86: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 211, // 87: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.priority:type_name -> temporal.api.common.v1.Priority + 212, // 88: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.retry_policy:type_name -> temporal.api.common.v1.RetryPolicy + 201, // 89: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.nexus_serialization_context:type_name -> temporal.api.nexus.v1.PropagatedSerializationContext + 213, // 90: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskCompletedRequest + 12, // 91: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.started_response:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse + 214, // 92: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.activity_tasks:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueResponse + 189, // 93: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.new_workflow_task:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponse + 215, // 94: temporal.server.api.historyservice.v1.RespondWorkflowTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskFailedRequest + 192, // 95: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 188, // 96: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 216, // 97: temporal.server.api.historyservice.v1.RecordActivityTaskHeartbeatRequest.heartbeat_request:type_name -> temporal.api.workflowservice.v1.RecordActivityTaskHeartbeatRequest + 217, // 98: temporal.server.api.historyservice.v1.RespondActivityTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCompletedRequest + 218, // 99: temporal.server.api.historyservice.v1.RespondActivityTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskFailedRequest + 219, // 100: temporal.server.api.historyservice.v1.RespondActivityTaskCanceledRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCanceledRequest + 192, // 101: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 188, // 102: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 220, // 103: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.signal_request:type_name -> temporal.api.workflowservice.v1.SignalWorkflowExecutionRequest + 192, // 104: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 191, // 105: temporal.server.api.historyservice.v1.SignalWorkflowExecutionResponse.link:type_name -> temporal.api.common.v1.Link + 221, // 106: temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionRequest.signal_with_start_request:type_name -> temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest + 191, // 107: temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionResponse.signal_link:type_name -> temporal.api.common.v1.Link + 192, // 108: temporal.server.api.historyservice.v1.RemoveSignalMutableStateRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 222, // 109: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.terminate_request:type_name -> temporal.api.workflowservice.v1.TerminateWorkflowExecutionRequest + 192, // 110: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 192, // 111: temporal.server.api.historyservice.v1.DeleteWorkflowExecutionRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 223, // 112: temporal.server.api.historyservice.v1.ResetWorkflowExecutionRequest.reset_request:type_name -> temporal.api.workflowservice.v1.ResetWorkflowExecutionRequest + 224, // 113: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionRequest + 192, // 114: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 192, // 115: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 188, // 116: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.child_clock:type_name -> temporal.server.api.clock.v1.VectorClock + 188, // 117: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.parent_clock:type_name -> temporal.server.api.clock.v1.VectorClock + 192, // 118: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 188, // 119: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 192, // 120: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 192, // 121: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 210, // 122: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.completion_event:type_name -> temporal.api.history.v1.HistoryEvent + 188, // 123: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 192, // 124: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 192, // 125: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 188, // 126: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 225, // 127: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.DescribeWorkflowExecutionRequest + 226, // 128: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.execution_config:type_name -> temporal.api.workflow.v1.WorkflowExecutionConfig + 227, // 129: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_execution_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionInfo + 228, // 130: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_activities:type_name -> temporal.api.workflow.v1.PendingActivityInfo + 229, // 131: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_children:type_name -> temporal.api.workflow.v1.PendingChildExecutionInfo + 230, // 132: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_workflow_task:type_name -> temporal.api.workflow.v1.PendingWorkflowTaskInfo + 231, // 133: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.callbacks:type_name -> temporal.api.workflow.v1.CallbackInfo + 232, // 134: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_nexus_operations:type_name -> temporal.api.workflow.v1.PendingNexusOperationInfo + 233, // 135: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_extended_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionExtendedInfo + 192, // 136: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 193, // 137: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.version_history_items:type_name -> temporal.server.api.history.v1.VersionHistoryItem + 234, // 138: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.events:type_name -> temporal.api.common.v1.DataBlob + 234, // 139: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.new_run_events:type_name -> temporal.api.common.v1.DataBlob + 235, // 140: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo + 236, // 141: temporal.server.api.historyservice.v1.ReplicateWorkflowStateRequest.workflow_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState + 176, // 142: temporal.server.api.historyservice.v1.SyncShardStatusRequest.status_time:type_name -> google.protobuf.Timestamp + 176, // 143: temporal.server.api.historyservice.v1.SyncActivityRequest.scheduled_time:type_name -> google.protobuf.Timestamp + 176, // 144: temporal.server.api.historyservice.v1.SyncActivityRequest.started_time:type_name -> google.protobuf.Timestamp + 176, // 145: temporal.server.api.historyservice.v1.SyncActivityRequest.last_heartbeat_time:type_name -> google.protobuf.Timestamp + 179, // 146: temporal.server.api.historyservice.v1.SyncActivityRequest.details:type_name -> temporal.api.common.v1.Payloads + 178, // 147: temporal.server.api.historyservice.v1.SyncActivityRequest.last_failure:type_name -> temporal.api.failure.v1.Failure + 237, // 148: temporal.server.api.historyservice.v1.SyncActivityRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory + 235, // 149: temporal.server.api.historyservice.v1.SyncActivityRequest.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo + 176, // 150: temporal.server.api.historyservice.v1.SyncActivityRequest.first_scheduled_time:type_name -> google.protobuf.Timestamp + 176, // 151: temporal.server.api.historyservice.v1.SyncActivityRequest.last_attempt_complete_time:type_name -> google.protobuf.Timestamp + 180, // 152: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_initial_interval:type_name -> google.protobuf.Duration + 180, // 153: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_maximum_interval:type_name -> google.protobuf.Duration + 64, // 154: temporal.server.api.historyservice.v1.SyncActivitiesRequest.activities_info:type_name -> temporal.server.api.historyservice.v1.ActivitySyncInfo + 176, // 155: temporal.server.api.historyservice.v1.ActivitySyncInfo.scheduled_time:type_name -> google.protobuf.Timestamp + 176, // 156: temporal.server.api.historyservice.v1.ActivitySyncInfo.started_time:type_name -> google.protobuf.Timestamp + 176, // 157: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_heartbeat_time:type_name -> google.protobuf.Timestamp + 179, // 158: temporal.server.api.historyservice.v1.ActivitySyncInfo.details:type_name -> temporal.api.common.v1.Payloads + 178, // 159: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_failure:type_name -> temporal.api.failure.v1.Failure + 237, // 160: temporal.server.api.historyservice.v1.ActivitySyncInfo.version_history:type_name -> temporal.server.api.history.v1.VersionHistory + 176, // 161: temporal.server.api.historyservice.v1.ActivitySyncInfo.first_scheduled_time:type_name -> google.protobuf.Timestamp + 176, // 162: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp + 180, // 163: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_initial_interval:type_name -> google.protobuf.Duration + 180, // 164: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_maximum_interval:type_name -> google.protobuf.Duration + 192, // 165: temporal.server.api.historyservice.v1.DescribeMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 236, // 166: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.cache_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState + 236, // 167: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.database_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState + 192, // 168: temporal.server.api.historyservice.v1.DescribeHistoryHostRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 238, // 169: temporal.server.api.historyservice.v1.DescribeHistoryHostResponse.namespace_cache:type_name -> temporal.server.api.namespace.v1.NamespaceCacheInfo + 239, // 170: temporal.server.api.historyservice.v1.GetShardResponse.shard_info:type_name -> temporal.server.api.persistence.v1.ShardInfo + 176, // 171: temporal.server.api.historyservice.v1.RemoveTaskRequest.visibility_time:type_name -> google.protobuf.Timestamp + 240, // 172: temporal.server.api.historyservice.v1.GetReplicationMessagesRequest.tokens:type_name -> temporal.server.api.replication.v1.ReplicationToken + 169, // 173: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.shard_messages:type_name -> temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry + 241, // 174: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesRequest.task_infos:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo + 242, // 175: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask + 243, // 176: temporal.server.api.historyservice.v1.QueryWorkflowRequest.request:type_name -> temporal.api.workflowservice.v1.QueryWorkflowRequest + 244, // 177: temporal.server.api.historyservice.v1.QueryWorkflowResponse.response:type_name -> temporal.api.workflowservice.v1.QueryWorkflowResponse + 245, // 178: temporal.server.api.historyservice.v1.ReapplyEventsRequest.request:type_name -> temporal.server.api.adminservice.v1.ReapplyEventsRequest + 246, // 179: temporal.server.api.historyservice.v1.GetDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 246, // 180: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 242, // 181: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask + 241, // 182: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks_info:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo + 246, // 183: temporal.server.api.historyservice.v1.PurgeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 246, // 184: temporal.server.api.historyservice.v1.MergeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 247, // 185: temporal.server.api.historyservice.v1.RefreshWorkflowTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.RefreshWorkflowTasksRequest + 192, // 186: temporal.server.api.historyservice.v1.GenerateLastHistoryReplicationTasksRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 96, // 187: temporal.server.api.historyservice.v1.GetReplicationStatusResponse.shards:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus + 176, // 188: temporal.server.api.historyservice.v1.ShardReplicationStatus.shard_local_time:type_name -> google.protobuf.Timestamp + 170, // 189: temporal.server.api.historyservice.v1.ShardReplicationStatus.remote_clusters:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry + 171, // 190: temporal.server.api.historyservice.v1.ShardReplicationStatus.handover_namespaces:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry + 176, // 191: temporal.server.api.historyservice.v1.ShardReplicationStatus.max_replication_task_visibility_time:type_name -> google.protobuf.Timestamp + 176, // 192: temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster.acked_task_visibility_time:type_name -> google.protobuf.Timestamp + 192, // 193: temporal.server.api.historyservice.v1.RebuildMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 192, // 194: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 234, // 195: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.history_batches:type_name -> temporal.api.common.v1.DataBlob + 237, // 196: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory + 192, // 197: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 176, // 198: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_start_time:type_name -> google.protobuf.Timestamp + 176, // 199: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_close_time:type_name -> google.protobuf.Timestamp + 248, // 200: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionRequest + 249, // 201: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse.response:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionResponse + 250, // 202: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesRequest.sync_replication_state:type_name -> temporal.server.api.replication.v1.SyncReplicationState + 251, // 203: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesResponse.messages:type_name -> temporal.server.api.replication.v1.WorkflowReplicationMessages + 252, // 204: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateRequest.request:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateRequest + 253, // 205: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateResponse.response:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateResponse + 254, // 206: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryRequest + 255, // 207: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse + 208, // 208: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.history:type_name -> temporal.api.history.v1.History + 255, // 209: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponseWithRaw.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse + 256, // 210: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseRequest + 257, // 211: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseResponse + 258, // 212: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Request.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Request + 259, // 213: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Response.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Response + 260, // 214: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryRequest.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryRequest + 261, // 215: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryResponse.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryResponse + 262, // 216: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionRequest.request:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionRequest + 263, // 217: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionResponse.response:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionResponse + 192, // 218: temporal.server.api.historyservice.v1.DeleteExecutionRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 264, // 219: temporal.server.api.historyservice.v1.GetDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey + 265, // 220: temporal.server.api.historyservice.v1.GetDLQTasksResponse.dlq_tasks:type_name -> temporal.server.api.common.v1.HistoryDLQTask + 264, // 221: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey + 266, // 222: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.inclusive_max_task_metadata:type_name -> temporal.server.api.common.v1.HistoryDLQTaskMetadata + 172, // 223: temporal.server.api.historyservice.v1.ListQueuesResponse.queues:type_name -> temporal.server.api.historyservice.v1.ListQueuesResponse.QueueInfo + 173, // 224: temporal.server.api.historyservice.v1.AddTasksRequest.tasks:type_name -> temporal.server.api.historyservice.v1.AddTasksRequest.Task + 267, // 225: temporal.server.api.historyservice.v1.ListTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksRequest + 268, // 226: temporal.server.api.historyservice.v1.ListTasksResponse.response:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksResponse + 269, // 227: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion + 270, // 228: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.success:type_name -> temporal.api.common.v1.Payload + 178, // 229: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.failure:type_name -> temporal.api.failure.v1.Failure + 176, // 230: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.close_time:type_name -> google.protobuf.Timestamp + 191, // 231: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.links:type_name -> temporal.api.common.v1.Link + 176, // 232: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.start_time:type_name -> google.protobuf.Timestamp + 269, // 233: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion + 270, // 234: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.success:type_name -> temporal.api.common.v1.Payload + 271, // 235: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.failure:type_name -> temporal.api.nexus.v1.Failure + 176, // 236: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.start_time:type_name -> google.protobuf.Timestamp + 191, // 237: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.links:type_name -> temporal.api.common.v1.Link + 272, // 238: temporal.server.api.historyservice.v1.InvokeStateMachineMethodRequest.ref:type_name -> temporal.server.api.persistence.v1.StateMachineRef + 273, // 239: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.state:type_name -> temporal.server.api.enums.v1.HealthState + 274, // 240: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.checks:type_name -> temporal.server.api.health.v1.HealthCheck + 273, // 241: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.unenforced_state:type_name -> temporal.server.api.enums.v1.HealthState + 192, // 242: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 194, // 243: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 198, // 244: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories + 275, // 245: temporal.server.api.historyservice.v1.SyncWorkflowStateResponse.versioned_transition_artifact:type_name -> temporal.server.api.replication.v1.VersionedTransitionArtifact + 276, // 246: temporal.server.api.historyservice.v1.UpdateActivityOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateActivityOptionsRequest + 277, // 247: temporal.server.api.historyservice.v1.UpdateActivityOptionsResponse.activity_options:type_name -> temporal.api.activity.v1.ActivityOptions + 278, // 248: temporal.server.api.historyservice.v1.PauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.PauseActivityRequest + 279, // 249: temporal.server.api.historyservice.v1.UnpauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.UnpauseActivityRequest + 280, // 250: temporal.server.api.historyservice.v1.ResetActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.ResetActivityRequest + 281, // 251: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionOptionsRequest + 282, // 252: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse.workflow_execution_options:type_name -> temporal.api.workflow.v1.WorkflowExecutionOptions + 176, // 253: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse.update_time:type_name -> google.protobuf.Timestamp + 283, // 254: temporal.server.api.historyservice.v1.PauseWorkflowExecutionRequest.pause_request:type_name -> temporal.api.workflowservice.v1.PauseWorkflowExecutionRequest + 284, // 255: temporal.server.api.historyservice.v1.UnpauseWorkflowExecutionRequest.unpause_request:type_name -> temporal.api.workflowservice.v1.UnpauseWorkflowExecutionRequest + 285, // 256: temporal.server.api.historyservice.v1.StartNexusOperationRequest.request:type_name -> temporal.api.nexus.v1.StartOperationRequest + 286, // 257: temporal.server.api.historyservice.v1.StartNexusOperationResponse.response:type_name -> temporal.api.nexus.v1.StartOperationResponse + 287, // 258: temporal.server.api.historyservice.v1.CancelNexusOperationRequest.request:type_name -> temporal.api.nexus.v1.CancelOperationRequest + 288, // 259: temporal.server.api.historyservice.v1.CancelNexusOperationResponse.response:type_name -> temporal.api.nexus.v1.CancelOperationResponse + 289, // 260: temporal.server.api.historyservice.v1.PollWorkflowExecutionTimeSkippingRequest.request:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingRequest + 290, // 261: temporal.server.api.historyservice.v1.PollWorkflowExecutionTimeSkippingResponse.response:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingResponse + 193, // 262: temporal.server.api.historyservice.v1.OrphanedChildReplacementInfo.parent_current_version_history_items:type_name -> temporal.server.api.history.v1.VersionHistoryItem + 1, // 263: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest + 105, // 264: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest + 2, // 265: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse + 106, // 266: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse + 291, // 267: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery + 291, // 268: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery + 292, // 269: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry.value:type_name -> temporal.server.api.replication.v1.ReplicationMessages + 98, // 270: temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry.value:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster + 97, // 271: temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry.value:type_name -> temporal.server.api.historyservice.v1.HandoverNamespaceInfo + 234, // 272: temporal.server.api.historyservice.v1.AddTasksRequest.Task.blob:type_name -> temporal.api.common.v1.DataBlob + 293, // 273: temporal.server.api.historyservice.v1.routing:extendee -> google.protobuf.MessageOptions + 0, // 274: temporal.server.api.historyservice.v1.routing:type_name -> temporal.server.api.historyservice.v1.RoutingOptions + 275, // [275:275] is the sub-list for method output_type + 275, // [275:275] is the sub-list for method input_type + 274, // [274:275] is the sub-list for extension type_name + 273, // [273:274] is the sub-list for extension extendee + 0, // [0:273] is the sub-list for field type_name } func init() { file_temporal_server_api_historyservice_v1_request_response_proto_init() } diff --git a/api/persistence/v1/executions.go-helpers.pb.go b/api/persistence/v1/executions.go-helpers.pb.go index 86bcf8c7f9b..83575ec78c8 100644 --- a/api/persistence/v1/executions.go-helpers.pb.go +++ b/api/persistence/v1/executions.go-helpers.pb.go @@ -79,6 +79,43 @@ func (this *WorkflowExecutionInfo) Equal(that interface{}) bool { return proto.Equal(this, that1) } +// Marshal an object of type SessionState to the protobuf v3 wire format +func (val *SessionState) Marshal() ([]byte, error) { + return proto.Marshal(val) +} + +// Unmarshal an object of type SessionState from the protobuf v3 wire format +func (val *SessionState) Unmarshal(buf []byte) error { + return proto.Unmarshal(buf, val) +} + +// Size returns the size of the object, in bytes, once serialized +func (val *SessionState) Size() int { + return proto.Size(val) +} + +// Equal returns whether two SessionState values are equivalent by recursively +// comparing the message's fields. +// For more information see the documentation for +// https://pkg.go.dev/google.golang.org/protobuf/proto#Equal +func (this *SessionState) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + var that1 *SessionState + switch t := that.(type) { + case *SessionState: + that1 = t + case SessionState: + that1 = &t + default: + return false + } + + return proto.Equal(this, that1) +} + // Marshal an object of type TimeSkippingInfo to the protobuf v3 wire format func (val *TimeSkippingInfo) Marshal() ([]byte, error) { return proto.Marshal(val) diff --git a/api/persistence/v1/executions.pb.go b/api/persistence/v1/executions.pb.go index 533359cd882..ea731d6a4f7 100644 --- a/api/persistence/v1/executions.pb.go +++ b/api/persistence/v1/executions.pb.go @@ -373,8 +373,10 @@ type WorkflowExecutionInfo struct { MutableStateRebuildTime *timestamppb.Timestamp `protobuf:"bytes,116,opt,name=mutable_state_rebuild_time,json=mutableStateRebuildTime,proto3" json:"mutable_state_rebuild_time,omitempty"` // Nexus operation context retained across runs so workers can select payload serialization. PropagatedNexusSerializationContext *v18.PropagatedSerializationContext `protobuf:"bytes,117,opt,name=propagated_nexus_serialization_context,json=propagatedNexusSerializationContext,proto3" json:"propagated_nexus_serialization_context,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Session binding retained for the lifetime of this workflow execution. + SessionState *SessionState `protobuf:"bytes,118,opt,name=session_state,json=sessionState,proto3" json:"session_state,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *WorkflowExecutionInfo) Reset() { @@ -1160,6 +1162,13 @@ func (x *WorkflowExecutionInfo) GetPropagatedNexusSerializationContext() *v18.Pr return nil } +func (x *WorkflowExecutionInfo) GetSessionState() *SessionState { + if x != nil { + return x.SessionState + } + return nil +} + type isWorkflowExecutionInfo_LastWorkflowTaskFailure interface { isWorkflowExecutionInfo_LastWorkflowTaskFailure() } @@ -1178,6 +1187,62 @@ func (*WorkflowExecutionInfo_LastWorkflowTaskFailureCause) isWorkflowExecutionIn func (*WorkflowExecutionInfo_LastWorkflowTaskTimedOutType) isWorkflowExecutionInfo_LastWorkflowTaskFailure() { } +// Note: This is experimental and could change in the near future. +type SessionState struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Whether this session is mapped strongly to an individual worker. Exclusive sessions also remain at their assigned address. + ExclusiveWorker bool `protobuf:"varint,1,opt,name=exclusive_worker,json=exclusiveWorker,proto3" json:"exclusive_worker,omitempty"` + // Stable sticky task queue name, mirrored by WorkflowExecutionInfo.sticky_task_queue. + // This identifies the session across worker process replacements. + WorkerQueue string `protobuf:"bytes,2,opt,name=worker_queue,json=workerQueue,proto3" json:"worker_queue,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SessionState) Reset() { + *x = SessionState{} + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SessionState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SessionState) ProtoMessage() {} + +func (x *SessionState) ProtoReflect() protoreflect.Message { + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SessionState.ProtoReflect.Descriptor instead. +func (*SessionState) Descriptor() ([]byte, []int) { + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{2} +} + +func (x *SessionState) GetExclusiveWorker() bool { + if x != nil { + return x.ExclusiveWorker + } + return false +} + +func (x *SessionState) GetWorkerQueue() string { + if x != nil { + return x.WorkerQueue + } + return "" +} + type TimeSkippingInfo struct { state protoimpl.MessageState `protogen:"open.v1"` // Current time-skipping configuration applied to the execution. @@ -1203,7 +1268,7 @@ type TimeSkippingInfo struct { func (x *TimeSkippingInfo) Reset() { *x = TimeSkippingInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[2] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1215,7 +1280,7 @@ func (x *TimeSkippingInfo) String() string { func (*TimeSkippingInfo) ProtoMessage() {} func (x *TimeSkippingInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[2] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1228,7 +1293,7 @@ func (x *TimeSkippingInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use TimeSkippingInfo.ProtoReflect.Descriptor instead. func (*TimeSkippingInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{2} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{3} } func (x *TimeSkippingInfo) GetConfig() *v13.TimeSkippingConfig { @@ -1292,7 +1357,7 @@ type FastForwardInfo struct { func (x *FastForwardInfo) Reset() { *x = FastForwardInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[3] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1304,7 +1369,7 @@ func (x *FastForwardInfo) String() string { func (*FastForwardInfo) ProtoMessage() {} func (x *FastForwardInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[3] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1317,7 +1382,7 @@ func (x *FastForwardInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use FastForwardInfo.ProtoReflect.Descriptor instead. func (*FastForwardInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{3} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{4} } func (x *FastForwardInfo) GetTargetTime() *timestamppb.Timestamp { @@ -1350,7 +1415,7 @@ type LastNotifiedTargetVersion struct { func (x *LastNotifiedTargetVersion) Reset() { *x = LastNotifiedTargetVersion{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[4] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1362,7 +1427,7 @@ func (x *LastNotifiedTargetVersion) String() string { func (*LastNotifiedTargetVersion) ProtoMessage() {} func (x *LastNotifiedTargetVersion) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[4] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1375,7 +1440,7 @@ func (x *LastNotifiedTargetVersion) ProtoReflect() protoreflect.Message { // Deprecated: Use LastNotifiedTargetVersion.ProtoReflect.Descriptor instead. func (*LastNotifiedTargetVersion) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{4} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{5} } func (x *LastNotifiedTargetVersion) GetDeploymentVersion() *v19.WorkerDeploymentVersion { @@ -1407,7 +1472,7 @@ type ExecutionStats struct { func (x *ExecutionStats) Reset() { *x = ExecutionStats{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[5] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1419,7 +1484,7 @@ func (x *ExecutionStats) String() string { func (*ExecutionStats) ProtoMessage() {} func (x *ExecutionStats) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[5] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1432,7 +1497,7 @@ func (x *ExecutionStats) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecutionStats.ProtoReflect.Descriptor instead. func (*ExecutionStats) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{5} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{6} } func (x *ExecutionStats) GetHistorySize() int64 { @@ -1483,7 +1548,7 @@ type WorkflowExecutionState struct { func (x *WorkflowExecutionState) Reset() { *x = WorkflowExecutionState{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[6] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1495,7 +1560,7 @@ func (x *WorkflowExecutionState) String() string { func (*WorkflowExecutionState) ProtoMessage() {} func (x *WorkflowExecutionState) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[6] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1508,7 +1573,7 @@ func (x *WorkflowExecutionState) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkflowExecutionState.ProtoReflect.Descriptor instead. func (*WorkflowExecutionState) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{6} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{7} } func (x *WorkflowExecutionState) GetCreateRequestId() string { @@ -1580,7 +1645,7 @@ type RequestIDInfo struct { func (x *RequestIDInfo) Reset() { *x = RequestIDInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[7] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1592,7 +1657,7 @@ func (x *RequestIDInfo) String() string { func (*RequestIDInfo) ProtoMessage() {} func (x *RequestIDInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[7] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1605,7 +1670,7 @@ func (x *RequestIDInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestIDInfo.ProtoReflect.Descriptor instead. func (*RequestIDInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{7} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{8} } func (x *RequestIDInfo) GetEventType() v11.EventType { @@ -1663,7 +1728,7 @@ type TransferTaskInfo struct { func (x *TransferTaskInfo) Reset() { *x = TransferTaskInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[8] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1675,7 +1740,7 @@ func (x *TransferTaskInfo) String() string { func (*TransferTaskInfo) ProtoMessage() {} func (x *TransferTaskInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[8] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1688,7 +1753,7 @@ func (x *TransferTaskInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use TransferTaskInfo.ProtoReflect.Descriptor instead. func (*TransferTaskInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{8} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{9} } func (x *TransferTaskInfo) GetNamespaceId() string { @@ -1876,7 +1941,7 @@ type ReplicationTaskInfo struct { func (x *ReplicationTaskInfo) Reset() { *x = ReplicationTaskInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[9] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1888,7 +1953,7 @@ func (x *ReplicationTaskInfo) String() string { func (*ReplicationTaskInfo) ProtoMessage() {} func (x *ReplicationTaskInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[9] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1901,7 +1966,7 @@ func (x *ReplicationTaskInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ReplicationTaskInfo.ProtoReflect.Descriptor instead. func (*ReplicationTaskInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{9} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{10} } func (x *ReplicationTaskInfo) GetNamespaceId() string { @@ -2082,7 +2147,7 @@ type VisibilityTaskInfo struct { func (x *VisibilityTaskInfo) Reset() { *x = VisibilityTaskInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[10] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2094,7 +2159,7 @@ func (x *VisibilityTaskInfo) String() string { func (*VisibilityTaskInfo) ProtoMessage() {} func (x *VisibilityTaskInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[10] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2107,7 +2172,7 @@ func (x *VisibilityTaskInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use VisibilityTaskInfo.ProtoReflect.Descriptor instead. func (*VisibilityTaskInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{10} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{11} } func (x *VisibilityTaskInfo) GetNamespaceId() string { @@ -2255,7 +2320,7 @@ type TimerTaskInfo struct { func (x *TimerTaskInfo) Reset() { *x = TimerTaskInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[11] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2267,7 +2332,7 @@ func (x *TimerTaskInfo) String() string { func (*TimerTaskInfo) ProtoMessage() {} func (x *TimerTaskInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[11] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2280,7 +2345,7 @@ func (x *TimerTaskInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use TimerTaskInfo.ProtoReflect.Descriptor instead. func (*TimerTaskInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{11} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{12} } func (x *TimerTaskInfo) GetNamespaceId() string { @@ -2444,7 +2509,7 @@ type ArchivalTaskInfo struct { func (x *ArchivalTaskInfo) Reset() { *x = ArchivalTaskInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[12] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2456,7 +2521,7 @@ func (x *ArchivalTaskInfo) String() string { func (*ArchivalTaskInfo) ProtoMessage() {} func (x *ArchivalTaskInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[12] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2469,7 +2534,7 @@ func (x *ArchivalTaskInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ArchivalTaskInfo.ProtoReflect.Descriptor instead. func (*ArchivalTaskInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{12} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{13} } func (x *ArchivalTaskInfo) GetTaskId() int64 { @@ -2544,7 +2609,7 @@ type OutboundTaskInfo struct { func (x *OutboundTaskInfo) Reset() { *x = OutboundTaskInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[13] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2556,7 +2621,7 @@ func (x *OutboundTaskInfo) String() string { func (*OutboundTaskInfo) ProtoMessage() {} func (x *OutboundTaskInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[13] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2569,7 +2634,7 @@ func (x *OutboundTaskInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use OutboundTaskInfo.ProtoReflect.Descriptor instead. func (*OutboundTaskInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{13} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{14} } func (x *OutboundTaskInfo) GetNamespaceId() string { @@ -2690,7 +2755,7 @@ type WorkerCommandsTask struct { func (x *WorkerCommandsTask) Reset() { *x = WorkerCommandsTask{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[14] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2702,7 +2767,7 @@ func (x *WorkerCommandsTask) String() string { func (*WorkerCommandsTask) ProtoMessage() {} func (x *WorkerCommandsTask) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[14] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2715,7 +2780,7 @@ func (x *WorkerCommandsTask) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerCommandsTask.ProtoReflect.Descriptor instead. func (*WorkerCommandsTask) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{14} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{15} } func (x *WorkerCommandsTask) GetCommands() []*v110.WorkerCommand { @@ -2734,7 +2799,7 @@ type NexusInvocationTaskInfo struct { func (x *NexusInvocationTaskInfo) Reset() { *x = NexusInvocationTaskInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[15] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2746,7 +2811,7 @@ func (x *NexusInvocationTaskInfo) String() string { func (*NexusInvocationTaskInfo) ProtoMessage() {} func (x *NexusInvocationTaskInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[15] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2759,7 +2824,7 @@ func (x *NexusInvocationTaskInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NexusInvocationTaskInfo.ProtoReflect.Descriptor instead. func (*NexusInvocationTaskInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{15} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{16} } func (x *NexusInvocationTaskInfo) GetAttempt() int32 { @@ -2778,7 +2843,7 @@ type NexusCancelationTaskInfo struct { func (x *NexusCancelationTaskInfo) Reset() { *x = NexusCancelationTaskInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[16] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2790,7 +2855,7 @@ func (x *NexusCancelationTaskInfo) String() string { func (*NexusCancelationTaskInfo) ProtoMessage() {} func (x *NexusCancelationTaskInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[16] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2803,7 +2868,7 @@ func (x *NexusCancelationTaskInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NexusCancelationTaskInfo.ProtoReflect.Descriptor instead. func (*NexusCancelationTaskInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{16} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{17} } func (x *NexusCancelationTaskInfo) GetAttempt() int32 { @@ -2927,7 +2992,7 @@ type ActivityInfo struct { func (x *ActivityInfo) Reset() { *x = ActivityInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[17] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2939,7 +3004,7 @@ func (x *ActivityInfo) String() string { func (*ActivityInfo) ProtoMessage() {} func (x *ActivityInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[17] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2952,7 +3017,7 @@ func (x *ActivityInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ActivityInfo.ProtoReflect.Descriptor instead. func (*ActivityInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{17} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{18} } func (x *ActivityInfo) GetVersion() int64 { @@ -3354,7 +3419,7 @@ type ActivitySessionState struct { func (x *ActivitySessionState) Reset() { *x = ActivitySessionState{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[18] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3366,7 +3431,7 @@ func (x *ActivitySessionState) String() string { func (*ActivitySessionState) ProtoMessage() {} func (x *ActivitySessionState) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[18] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3379,7 +3444,7 @@ func (x *ActivitySessionState) ProtoReflect() protoreflect.Message { // Deprecated: Use ActivitySessionState.ProtoReflect.Descriptor instead. func (*ActivitySessionState) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{18} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{19} } func (x *ActivitySessionState) GetPlacement() isActivitySessionState_Placement { @@ -3425,7 +3490,7 @@ type TimerInfo struct { func (x *TimerInfo) Reset() { *x = TimerInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[19] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3437,7 +3502,7 @@ func (x *TimerInfo) String() string { func (*TimerInfo) ProtoMessage() {} func (x *TimerInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[19] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3450,7 +3515,7 @@ func (x *TimerInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use TimerInfo.ProtoReflect.Descriptor instead. func (*TimerInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{19} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{20} } func (x *TimerInfo) GetVersion() int64 { @@ -3518,7 +3583,7 @@ type ChildExecutionInfo struct { func (x *ChildExecutionInfo) Reset() { *x = ChildExecutionInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[20] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3530,7 +3595,7 @@ func (x *ChildExecutionInfo) String() string { func (*ChildExecutionInfo) ProtoMessage() {} func (x *ChildExecutionInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[20] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3543,7 +3608,7 @@ func (x *ChildExecutionInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ChildExecutionInfo.ProtoReflect.Descriptor instead. func (*ChildExecutionInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{20} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{21} } func (x *ChildExecutionInfo) GetVersion() int64 { @@ -3658,7 +3723,7 @@ type RequestCancelInfo struct { func (x *RequestCancelInfo) Reset() { *x = RequestCancelInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[21] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3670,7 +3735,7 @@ func (x *RequestCancelInfo) String() string { func (*RequestCancelInfo) ProtoMessage() {} func (x *RequestCancelInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[21] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3683,7 +3748,7 @@ func (x *RequestCancelInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestCancelInfo.ProtoReflect.Descriptor instead. func (*RequestCancelInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{21} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{22} } func (x *RequestCancelInfo) GetVersion() int64 { @@ -3735,7 +3800,7 @@ type SignalInfo struct { func (x *SignalInfo) Reset() { *x = SignalInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[22] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3747,7 +3812,7 @@ func (x *SignalInfo) String() string { func (*SignalInfo) ProtoMessage() {} func (x *SignalInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[22] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3760,7 +3825,7 @@ func (x *SignalInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use SignalInfo.ProtoReflect.Descriptor instead. func (*SignalInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{22} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{23} } func (x *SignalInfo) GetVersion() int64 { @@ -3810,7 +3875,7 @@ type Checksum struct { func (x *Checksum) Reset() { *x = Checksum{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[23] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3822,7 +3887,7 @@ func (x *Checksum) String() string { func (*Checksum) ProtoMessage() {} func (x *Checksum) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[23] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3835,7 +3900,7 @@ func (x *Checksum) ProtoReflect() protoreflect.Message { // Deprecated: Use Checksum.ProtoReflect.Descriptor instead. func (*Checksum) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{23} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{24} } func (x *Checksum) GetVersion() int32 { @@ -3873,7 +3938,7 @@ type Callback struct { func (x *Callback) Reset() { *x = Callback{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[24] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3885,7 +3950,7 @@ func (x *Callback) String() string { func (*Callback) ProtoMessage() {} func (x *Callback) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[24] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3898,7 +3963,7 @@ func (x *Callback) ProtoReflect() protoreflect.Message { // Deprecated: Use Callback.ProtoReflect.Descriptor instead. func (*Callback) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{24} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{25} } func (x *Callback) GetVariant() isCallback_Variant { @@ -3965,7 +4030,7 @@ type HSMCompletionCallbackArg struct { func (x *HSMCompletionCallbackArg) Reset() { *x = HSMCompletionCallbackArg{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[25] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3977,7 +4042,7 @@ func (x *HSMCompletionCallbackArg) String() string { func (*HSMCompletionCallbackArg) ProtoMessage() {} func (x *HSMCompletionCallbackArg) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[25] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3990,7 +4055,7 @@ func (x *HSMCompletionCallbackArg) ProtoReflect() protoreflect.Message { // Deprecated: Use HSMCompletionCallbackArg.ProtoReflect.Descriptor instead. func (*HSMCompletionCallbackArg) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{25} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{26} } func (x *HSMCompletionCallbackArg) GetNamespaceId() string { @@ -4047,7 +4112,7 @@ type CallbackInfo struct { func (x *CallbackInfo) Reset() { *x = CallbackInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[26] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4059,7 +4124,7 @@ func (x *CallbackInfo) String() string { func (*CallbackInfo) ProtoMessage() {} func (x *CallbackInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[26] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4072,7 +4137,7 @@ func (x *CallbackInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use CallbackInfo.ProtoReflect.Descriptor instead. func (*CallbackInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{26} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{27} } func (x *CallbackInfo) GetCallback() *Callback { @@ -4190,7 +4255,7 @@ type NexusOperationInfo struct { func (x *NexusOperationInfo) Reset() { *x = NexusOperationInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[27] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4202,7 +4267,7 @@ func (x *NexusOperationInfo) String() string { func (*NexusOperationInfo) ProtoMessage() {} func (x *NexusOperationInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[27] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4215,7 +4280,7 @@ func (x *NexusOperationInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NexusOperationInfo.ProtoReflect.Descriptor instead. func (*NexusOperationInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{27} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{28} } func (x *NexusOperationInfo) GetEndpoint() string { @@ -4360,7 +4425,7 @@ type NexusOperationCancellationInfo struct { func (x *NexusOperationCancellationInfo) Reset() { *x = NexusOperationCancellationInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[28] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4372,7 +4437,7 @@ func (x *NexusOperationCancellationInfo) String() string { func (*NexusOperationCancellationInfo) ProtoMessage() {} func (x *NexusOperationCancellationInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[28] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4385,7 +4450,7 @@ func (x *NexusOperationCancellationInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NexusOperationCancellationInfo.ProtoReflect.Descriptor instead. func (*NexusOperationCancellationInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{28} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{29} } func (x *NexusOperationCancellationInfo) GetRequestedTime() *timestamppb.Timestamp { @@ -4448,7 +4513,7 @@ type ResetChildInfo struct { func (x *ResetChildInfo) Reset() { *x = ResetChildInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[29] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4460,7 +4525,7 @@ func (x *ResetChildInfo) String() string { func (*ResetChildInfo) ProtoMessage() {} func (x *ResetChildInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[29] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4473,7 +4538,7 @@ func (x *ResetChildInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ResetChildInfo.ProtoReflect.Descriptor instead. func (*ResetChildInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{29} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{30} } func (x *ResetChildInfo) GetShouldTerminateAndStart() bool { @@ -4499,7 +4564,7 @@ type WorkflowPauseInfo struct { func (x *WorkflowPauseInfo) Reset() { *x = WorkflowPauseInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[30] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4511,7 +4576,7 @@ func (x *WorkflowPauseInfo) String() string { func (*WorkflowPauseInfo) ProtoMessage() {} func (x *WorkflowPauseInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[30] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4524,7 +4589,7 @@ func (x *WorkflowPauseInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkflowPauseInfo.ProtoReflect.Descriptor instead. func (*WorkflowPauseInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{30} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{31} } func (x *WorkflowPauseInfo) GetPauseTime() *timestamppb.Timestamp { @@ -4566,7 +4631,7 @@ type TransferTaskInfo_CloseExecutionTaskDetails struct { func (x *TransferTaskInfo_CloseExecutionTaskDetails) Reset() { *x = TransferTaskInfo_CloseExecutionTaskDetails{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[39] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4578,7 +4643,7 @@ func (x *TransferTaskInfo_CloseExecutionTaskDetails) String() string { func (*TransferTaskInfo_CloseExecutionTaskDetails) ProtoMessage() {} func (x *TransferTaskInfo_CloseExecutionTaskDetails) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[39] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4591,7 +4656,7 @@ func (x *TransferTaskInfo_CloseExecutionTaskDetails) ProtoReflect() protoreflect // Deprecated: Use TransferTaskInfo_CloseExecutionTaskDetails.ProtoReflect.Descriptor instead. func (*TransferTaskInfo_CloseExecutionTaskDetails) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{8, 0} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{9, 0} } func (x *TransferTaskInfo_CloseExecutionTaskDetails) GetCanSkipVisibilityArchival() bool { @@ -4615,7 +4680,7 @@ type ActivityInfo_UseWorkflowBuildIdInfo struct { func (x *ActivityInfo_UseWorkflowBuildIdInfo) Reset() { *x = ActivityInfo_UseWorkflowBuildIdInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[40] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4627,7 +4692,7 @@ func (x *ActivityInfo_UseWorkflowBuildIdInfo) String() string { func (*ActivityInfo_UseWorkflowBuildIdInfo) ProtoMessage() {} func (x *ActivityInfo_UseWorkflowBuildIdInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[40] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4640,7 +4705,7 @@ func (x *ActivityInfo_UseWorkflowBuildIdInfo) ProtoReflect() protoreflect.Messag // Deprecated: Use ActivityInfo_UseWorkflowBuildIdInfo.ProtoReflect.Descriptor instead. func (*ActivityInfo_UseWorkflowBuildIdInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{17, 0} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{18, 0} } func (x *ActivityInfo_UseWorkflowBuildIdInfo) GetLastUsedBuildId() string { @@ -4673,7 +4738,7 @@ type ActivityInfo_PauseInfo struct { func (x *ActivityInfo_PauseInfo) Reset() { *x = ActivityInfo_PauseInfo{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[41] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4685,7 +4750,7 @@ func (x *ActivityInfo_PauseInfo) String() string { func (*ActivityInfo_PauseInfo) ProtoMessage() {} func (x *ActivityInfo_PauseInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[41] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4698,7 +4763,7 @@ func (x *ActivityInfo_PauseInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ActivityInfo_PauseInfo.ProtoReflect.Descriptor instead. func (*ActivityInfo_PauseInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{17, 1} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{18, 1} } func (x *ActivityInfo_PauseInfo) GetPauseTime() *timestamppb.Timestamp { @@ -4770,7 +4835,7 @@ type ActivityInfo_PauseInfo_Manual struct { func (x *ActivityInfo_PauseInfo_Manual) Reset() { *x = ActivityInfo_PauseInfo_Manual{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[42] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4782,7 +4847,7 @@ func (x *ActivityInfo_PauseInfo_Manual) String() string { func (*ActivityInfo_PauseInfo_Manual) ProtoMessage() {} func (x *ActivityInfo_PauseInfo_Manual) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[42] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4795,7 +4860,7 @@ func (x *ActivityInfo_PauseInfo_Manual) ProtoReflect() protoreflect.Message { // Deprecated: Use ActivityInfo_PauseInfo_Manual.ProtoReflect.Descriptor instead. func (*ActivityInfo_PauseInfo_Manual) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{17, 1, 0} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{18, 1, 0} } func (x *ActivityInfo_PauseInfo_Manual) GetIdentity() string { @@ -4826,7 +4891,7 @@ type Callback_Nexus struct { func (x *Callback_Nexus) Reset() { *x = Callback_Nexus{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[43] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4838,7 +4903,7 @@ func (x *Callback_Nexus) String() string { func (*Callback_Nexus) ProtoMessage() {} func (x *Callback_Nexus) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[43] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4851,7 +4916,7 @@ func (x *Callback_Nexus) ProtoReflect() protoreflect.Message { // Deprecated: Use Callback_Nexus.ProtoReflect.Descriptor instead. func (*Callback_Nexus) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{24, 0} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{25, 0} } func (x *Callback_Nexus) GetUrl() string { @@ -4888,7 +4953,7 @@ type Callback_HSM struct { func (x *Callback_HSM) Reset() { *x = Callback_HSM{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[44] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4900,7 +4965,7 @@ func (x *Callback_HSM) String() string { func (*Callback_HSM) ProtoMessage() {} func (x *Callback_HSM) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[44] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4913,7 +4978,7 @@ func (x *Callback_HSM) ProtoReflect() protoreflect.Message { // Deprecated: Use Callback_HSM.ProtoReflect.Descriptor instead. func (*Callback_HSM) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{24, 1} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{25, 1} } func (x *Callback_HSM) GetNamespaceId() string { @@ -4960,7 +5025,7 @@ type CallbackInfo_WorkflowClosed struct { func (x *CallbackInfo_WorkflowClosed) Reset() { *x = CallbackInfo_WorkflowClosed{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[46] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4972,7 +5037,7 @@ func (x *CallbackInfo_WorkflowClosed) String() string { func (*CallbackInfo_WorkflowClosed) ProtoMessage() {} func (x *CallbackInfo_WorkflowClosed) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[46] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4985,7 +5050,7 @@ func (x *CallbackInfo_WorkflowClosed) ProtoReflect() protoreflect.Message { // Deprecated: Use CallbackInfo_WorkflowClosed.ProtoReflect.Descriptor instead. func (*CallbackInfo_WorkflowClosed) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{26, 0} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{27, 0} } type CallbackInfo_Trigger struct { @@ -5000,7 +5065,7 @@ type CallbackInfo_Trigger struct { func (x *CallbackInfo_Trigger) Reset() { *x = CallbackInfo_Trigger{} - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[47] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5012,7 +5077,7 @@ func (x *CallbackInfo_Trigger) String() string { func (*CallbackInfo_Trigger) ProtoMessage() {} func (x *CallbackInfo_Trigger) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[47] + mi := &file_temporal_server_api_persistence_v1_executions_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5025,7 +5090,7 @@ func (x *CallbackInfo_Trigger) ProtoReflect() protoreflect.Message { // Deprecated: Use CallbackInfo_Trigger.ProtoReflect.Descriptor instead. func (*CallbackInfo_Trigger) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{26, 1} + return file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP(), []int{27, 1} } func (x *CallbackInfo_Trigger) GetVariant() isCallbackInfo_Trigger_Variant { @@ -5075,7 +5140,7 @@ const file_temporal_server_api_persistence_v1_executions_proto_rawDesc = "" + "\x03key\x18\x01 \x01(\x05R\x03key\x12D\n" + "\x05value\x18\x02 \x01(\v2..temporal.server.api.persistence.v1.QueueStateR\x05value:\x028\x01J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06J\x04\b\b\x10\tJ\x04\b\t\x10\n" + "J\x04\b\n" + - "\x10\vJ\x04\b\v\x10\fJ\x04\b\f\x10\rJ\x04\b\x0e\x10\x0fJ\x04\b\x0f\x10\x10J\x04\b\x10\x10\x11\"\xb9D\n" + + "\x10\vJ\x04\b\v\x10\fJ\x04\b\f\x10\rJ\x04\b\x0e\x10\x0fJ\x04\b\x0f\x10\x10J\x04\b\x10\x10\x11\"\x90E\n" + "\x15WorkflowExecutionInfo\x12!\n" + "\fnamespace_id\x18\x01 \x01(\tR\vnamespaceId\x12\x1f\n" + "\vworkflow_id\x18\x02 \x01(\tR\n" + @@ -5189,7 +5254,8 @@ const file_temporal_server_api_persistence_v1_executions_proto_rawDesc = "" + "\x1fdeclined_target_version_upgrade\x18r \x01(\v25.temporal.api.history.v1.DeclinedTargetVersionUpgradeR\x1cdeclinedTargetVersionUpgrade\x12b\n" + "\x12time_skipping_info\x18s \x01(\v24.temporal.server.api.persistence.v1.TimeSkippingInfoR\x10timeSkippingInfo\x12W\n" + "\x1amutable_state_rebuild_time\x18t \x01(\v2\x1a.google.protobuf.TimestampR\x17mutableStateRebuildTime\x12\x8a\x01\n" + - "&propagated_nexus_serialization_context\x18u \x01(\v25.temporal.api.nexus.v1.PropagatedSerializationContextR#propagatedNexusSerializationContext\x1ad\n" + + "&propagated_nexus_serialization_context\x18u \x01(\v25.temporal.api.nexus.v1.PropagatedSerializationContextR#propagatedNexusSerializationContext\x12U\n" + + "\rsession_state\x18v \x01(\v20.temporal.server.api.persistence.v1.SessionStateR\fsessionState\x1ad\n" + "\x15SearchAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x125\n" + "\x05value\x18\x02 \x01(\v2\x1f.temporal.api.common.v1.PayloadR\x05value:\x028\x01\x1aX\n" + @@ -5205,7 +5271,10 @@ const file_temporal_server_api_persistence_v1_executions_proto_rawDesc = "" + "&ChildrenInitializedPostResetPointEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12H\n" + "\x05value\x18\x02 \x01(\v22.temporal.server.api.persistence.v1.ResetChildInfoR\x05value:\x028\x01B\x1c\n" + - "\x1alast_workflow_task_failureJ\x04\b\b\x10\tJ\x04\b\x0e\x10\x0fJ\x04\b\x0f\x10\x10J\x04\b\x10\x10\x11J\x04\bp\x10qJ\x04\b,\x10-J\x04\b-\x10.J\x04\b/\x100J\x04\b0\x101J\x04\b1\x102J\x04\b2\x103\"\xe4\x05\n" + + "\x1alast_workflow_task_failureJ\x04\b\b\x10\tJ\x04\b\x0e\x10\x0fJ\x04\b\x0f\x10\x10J\x04\b\x10\x10\x11J\x04\bp\x10qJ\x04\b,\x10-J\x04\b-\x10.J\x04\b/\x100J\x04\b0\x101J\x04\b1\x102J\x04\b2\x103\"\\\n" + + "\fSessionState\x12)\n" + + "\x10exclusive_worker\x18\x01 \x01(\bR\x0fexclusiveWorker\x12!\n" + + "\fworker_queue\x18\x02 \x01(\tR\vworkerQueue\"\xe4\x05\n" + "\x10TimeSkippingInfo\x12B\n" + "\x06config\x18\x01 \x01(\v2*.temporal.api.common.v1.TimeSkippingConfigR\x06config\x12[\n" + "\x1caccumulated_skipped_duration\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\x1aaccumulatedSkippedDuration\x12_\n" + @@ -5576,278 +5645,280 @@ func file_temporal_server_api_persistence_v1_executions_proto_rawDescGZIP() []by return file_temporal_server_api_persistence_v1_executions_proto_rawDescData } -var file_temporal_server_api_persistence_v1_executions_proto_msgTypes = make([]protoimpl.MessageInfo, 48) +var file_temporal_server_api_persistence_v1_executions_proto_msgTypes = make([]protoimpl.MessageInfo, 49) var file_temporal_server_api_persistence_v1_executions_proto_goTypes = []any{ (*ShardInfo)(nil), // 0: temporal.server.api.persistence.v1.ShardInfo (*WorkflowExecutionInfo)(nil), // 1: temporal.server.api.persistence.v1.WorkflowExecutionInfo - (*TimeSkippingInfo)(nil), // 2: temporal.server.api.persistence.v1.TimeSkippingInfo - (*FastForwardInfo)(nil), // 3: temporal.server.api.persistence.v1.FastForwardInfo - (*LastNotifiedTargetVersion)(nil), // 4: temporal.server.api.persistence.v1.LastNotifiedTargetVersion - (*ExecutionStats)(nil), // 5: temporal.server.api.persistence.v1.ExecutionStats - (*WorkflowExecutionState)(nil), // 6: temporal.server.api.persistence.v1.WorkflowExecutionState - (*RequestIDInfo)(nil), // 7: temporal.server.api.persistence.v1.RequestIDInfo - (*TransferTaskInfo)(nil), // 8: temporal.server.api.persistence.v1.TransferTaskInfo - (*ReplicationTaskInfo)(nil), // 9: temporal.server.api.persistence.v1.ReplicationTaskInfo - (*VisibilityTaskInfo)(nil), // 10: temporal.server.api.persistence.v1.VisibilityTaskInfo - (*TimerTaskInfo)(nil), // 11: temporal.server.api.persistence.v1.TimerTaskInfo - (*ArchivalTaskInfo)(nil), // 12: temporal.server.api.persistence.v1.ArchivalTaskInfo - (*OutboundTaskInfo)(nil), // 13: temporal.server.api.persistence.v1.OutboundTaskInfo - (*WorkerCommandsTask)(nil), // 14: temporal.server.api.persistence.v1.WorkerCommandsTask - (*NexusInvocationTaskInfo)(nil), // 15: temporal.server.api.persistence.v1.NexusInvocationTaskInfo - (*NexusCancelationTaskInfo)(nil), // 16: temporal.server.api.persistence.v1.NexusCancelationTaskInfo - (*ActivityInfo)(nil), // 17: temporal.server.api.persistence.v1.ActivityInfo - (*ActivitySessionState)(nil), // 18: temporal.server.api.persistence.v1.ActivitySessionState - (*TimerInfo)(nil), // 19: temporal.server.api.persistence.v1.TimerInfo - (*ChildExecutionInfo)(nil), // 20: temporal.server.api.persistence.v1.ChildExecutionInfo - (*RequestCancelInfo)(nil), // 21: temporal.server.api.persistence.v1.RequestCancelInfo - (*SignalInfo)(nil), // 22: temporal.server.api.persistence.v1.SignalInfo - (*Checksum)(nil), // 23: temporal.server.api.persistence.v1.Checksum - (*Callback)(nil), // 24: temporal.server.api.persistence.v1.Callback - (*HSMCompletionCallbackArg)(nil), // 25: temporal.server.api.persistence.v1.HSMCompletionCallbackArg - (*CallbackInfo)(nil), // 26: temporal.server.api.persistence.v1.CallbackInfo - (*NexusOperationInfo)(nil), // 27: temporal.server.api.persistence.v1.NexusOperationInfo - (*NexusOperationCancellationInfo)(nil), // 28: temporal.server.api.persistence.v1.NexusOperationCancellationInfo - (*ResetChildInfo)(nil), // 29: temporal.server.api.persistence.v1.ResetChildInfo - (*WorkflowPauseInfo)(nil), // 30: temporal.server.api.persistence.v1.WorkflowPauseInfo - nil, // 31: temporal.server.api.persistence.v1.ShardInfo.ReplicationDlqAckLevelEntry - nil, // 32: temporal.server.api.persistence.v1.ShardInfo.QueueStatesEntry - nil, // 33: temporal.server.api.persistence.v1.WorkflowExecutionInfo.SearchAttributesEntry - nil, // 34: temporal.server.api.persistence.v1.WorkflowExecutionInfo.MemoEntry - nil, // 35: temporal.server.api.persistence.v1.WorkflowExecutionInfo.UpdateInfosEntry - nil, // 36: temporal.server.api.persistence.v1.WorkflowExecutionInfo.SubStateMachinesByTypeEntry - nil, // 37: temporal.server.api.persistence.v1.WorkflowExecutionInfo.ChildrenInitializedPostResetPointEntry - nil, // 38: temporal.server.api.persistence.v1.WorkflowExecutionState.RequestIdsEntry - (*TransferTaskInfo_CloseExecutionTaskDetails)(nil), // 39: temporal.server.api.persistence.v1.TransferTaskInfo.CloseExecutionTaskDetails - (*ActivityInfo_UseWorkflowBuildIdInfo)(nil), // 40: temporal.server.api.persistence.v1.ActivityInfo.UseWorkflowBuildIdInfo - (*ActivityInfo_PauseInfo)(nil), // 41: temporal.server.api.persistence.v1.ActivityInfo.PauseInfo - (*ActivityInfo_PauseInfo_Manual)(nil), // 42: temporal.server.api.persistence.v1.ActivityInfo.PauseInfo.Manual - (*Callback_Nexus)(nil), // 43: temporal.server.api.persistence.v1.Callback.Nexus - (*Callback_HSM)(nil), // 44: temporal.server.api.persistence.v1.Callback.HSM - nil, // 45: temporal.server.api.persistence.v1.Callback.Nexus.HeaderEntry - (*CallbackInfo_WorkflowClosed)(nil), // 46: temporal.server.api.persistence.v1.CallbackInfo.WorkflowClosed - (*CallbackInfo_Trigger)(nil), // 47: temporal.server.api.persistence.v1.CallbackInfo.Trigger - (*timestamppb.Timestamp)(nil), // 48: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 49: google.protobuf.Duration - (v1.WorkflowTaskType)(0), // 50: temporal.server.api.enums.v1.WorkflowTaskType - (v11.SuggestContinueAsNewReason)(0), // 51: temporal.api.enums.v1.SuggestContinueAsNewReason - (*v12.ResetPoints)(nil), // 52: temporal.api.workflow.v1.ResetPoints - (*v14.VersionHistories)(nil), // 53: temporal.server.api.history.v1.VersionHistories - (*v15.VectorClock)(nil), // 54: temporal.server.api.clock.v1.VectorClock - (*v16.BaseExecutionInfo)(nil), // 55: temporal.server.api.workflow.v1.BaseExecutionInfo - (*v13.WorkerVersionStamp)(nil), // 56: temporal.api.common.v1.WorkerVersionStamp - (*VersionedTransition)(nil), // 57: temporal.server.api.persistence.v1.VersionedTransition - (*StateMachineTimerGroup)(nil), // 58: temporal.server.api.persistence.v1.StateMachineTimerGroup - (*StateMachineTombstoneBatch)(nil), // 59: temporal.server.api.persistence.v1.StateMachineTombstoneBatch - (*v12.WorkflowExecutionVersioningInfo)(nil), // 60: temporal.api.workflow.v1.WorkflowExecutionVersioningInfo - (*v13.Priority)(nil), // 61: temporal.api.common.v1.Priority - (v11.WorkflowTaskFailedCause)(0), // 62: temporal.api.enums.v1.WorkflowTaskFailedCause - (v11.TimeoutType)(0), // 63: temporal.api.enums.v1.TimeoutType - (*v17.DeclinedTargetVersionUpgrade)(nil), // 64: temporal.api.history.v1.DeclinedTargetVersionUpgrade - (*v18.PropagatedSerializationContext)(nil), // 65: temporal.api.nexus.v1.PropagatedSerializationContext - (*v13.TimeSkippingConfig)(nil), // 66: temporal.api.common.v1.TimeSkippingConfig - (*v19.WorkerDeploymentVersion)(nil), // 67: temporal.api.deployment.v1.WorkerDeploymentVersion - (v1.WorkflowExecutionState)(0), // 68: temporal.server.api.enums.v1.WorkflowExecutionState - (v11.WorkflowExecutionStatus)(0), // 69: temporal.api.enums.v1.WorkflowExecutionStatus - (v11.EventType)(0), // 70: temporal.api.enums.v1.EventType - (v1.TaskType)(0), // 71: temporal.server.api.enums.v1.TaskType - (*ChasmTaskInfo)(nil), // 72: temporal.server.api.persistence.v1.ChasmTaskInfo - (v1.TaskPriority)(0), // 73: temporal.server.api.enums.v1.TaskPriority - (*v14.VersionHistoryItem)(nil), // 74: temporal.server.api.history.v1.VersionHistoryItem - (*v14.VersionHistory)(nil), // 75: temporal.server.api.history.v1.VersionHistory - (v1.WorkflowBackoffType)(0), // 76: temporal.server.api.enums.v1.WorkflowBackoffType - (*StateMachineTaskInfo)(nil), // 77: temporal.server.api.persistence.v1.StateMachineTaskInfo - (*v110.WorkerCommand)(nil), // 78: temporal.api.worker.v1.WorkerCommand - (*v111.Failure)(nil), // 79: temporal.api.failure.v1.Failure - (*v13.Payloads)(nil), // 80: temporal.api.common.v1.Payloads - (*v13.ActivityType)(nil), // 81: temporal.api.common.v1.ActivityType - (*v19.Deployment)(nil), // 82: temporal.api.deployment.v1.Deployment - (*emptypb.Empty)(nil), // 83: google.protobuf.Empty - (v11.ParentClosePolicy)(0), // 84: temporal.api.enums.v1.ParentClosePolicy - (v1.ChecksumFlavor)(0), // 85: temporal.server.api.enums.v1.ChecksumFlavor - (*v13.Link)(nil), // 86: temporal.api.common.v1.Link - (*v17.HistoryEvent)(nil), // 87: temporal.api.history.v1.HistoryEvent - (v1.CallbackState)(0), // 88: temporal.server.api.enums.v1.CallbackState - (v1.NexusOperationState)(0), // 89: temporal.server.api.enums.v1.NexusOperationState - (v11.NexusOperationCancellationState)(0), // 90: temporal.api.enums.v1.NexusOperationCancellationState - (*QueueState)(nil), // 91: temporal.server.api.persistence.v1.QueueState - (*v13.Payload)(nil), // 92: temporal.api.common.v1.Payload - (*UpdateInfo)(nil), // 93: temporal.server.api.persistence.v1.UpdateInfo - (*StateMachineMap)(nil), // 94: temporal.server.api.persistence.v1.StateMachineMap - (*StateMachineRef)(nil), // 95: temporal.server.api.persistence.v1.StateMachineRef + (*SessionState)(nil), // 2: temporal.server.api.persistence.v1.SessionState + (*TimeSkippingInfo)(nil), // 3: temporal.server.api.persistence.v1.TimeSkippingInfo + (*FastForwardInfo)(nil), // 4: temporal.server.api.persistence.v1.FastForwardInfo + (*LastNotifiedTargetVersion)(nil), // 5: temporal.server.api.persistence.v1.LastNotifiedTargetVersion + (*ExecutionStats)(nil), // 6: temporal.server.api.persistence.v1.ExecutionStats + (*WorkflowExecutionState)(nil), // 7: temporal.server.api.persistence.v1.WorkflowExecutionState + (*RequestIDInfo)(nil), // 8: temporal.server.api.persistence.v1.RequestIDInfo + (*TransferTaskInfo)(nil), // 9: temporal.server.api.persistence.v1.TransferTaskInfo + (*ReplicationTaskInfo)(nil), // 10: temporal.server.api.persistence.v1.ReplicationTaskInfo + (*VisibilityTaskInfo)(nil), // 11: temporal.server.api.persistence.v1.VisibilityTaskInfo + (*TimerTaskInfo)(nil), // 12: temporal.server.api.persistence.v1.TimerTaskInfo + (*ArchivalTaskInfo)(nil), // 13: temporal.server.api.persistence.v1.ArchivalTaskInfo + (*OutboundTaskInfo)(nil), // 14: temporal.server.api.persistence.v1.OutboundTaskInfo + (*WorkerCommandsTask)(nil), // 15: temporal.server.api.persistence.v1.WorkerCommandsTask + (*NexusInvocationTaskInfo)(nil), // 16: temporal.server.api.persistence.v1.NexusInvocationTaskInfo + (*NexusCancelationTaskInfo)(nil), // 17: temporal.server.api.persistence.v1.NexusCancelationTaskInfo + (*ActivityInfo)(nil), // 18: temporal.server.api.persistence.v1.ActivityInfo + (*ActivitySessionState)(nil), // 19: temporal.server.api.persistence.v1.ActivitySessionState + (*TimerInfo)(nil), // 20: temporal.server.api.persistence.v1.TimerInfo + (*ChildExecutionInfo)(nil), // 21: temporal.server.api.persistence.v1.ChildExecutionInfo + (*RequestCancelInfo)(nil), // 22: temporal.server.api.persistence.v1.RequestCancelInfo + (*SignalInfo)(nil), // 23: temporal.server.api.persistence.v1.SignalInfo + (*Checksum)(nil), // 24: temporal.server.api.persistence.v1.Checksum + (*Callback)(nil), // 25: temporal.server.api.persistence.v1.Callback + (*HSMCompletionCallbackArg)(nil), // 26: temporal.server.api.persistence.v1.HSMCompletionCallbackArg + (*CallbackInfo)(nil), // 27: temporal.server.api.persistence.v1.CallbackInfo + (*NexusOperationInfo)(nil), // 28: temporal.server.api.persistence.v1.NexusOperationInfo + (*NexusOperationCancellationInfo)(nil), // 29: temporal.server.api.persistence.v1.NexusOperationCancellationInfo + (*ResetChildInfo)(nil), // 30: temporal.server.api.persistence.v1.ResetChildInfo + (*WorkflowPauseInfo)(nil), // 31: temporal.server.api.persistence.v1.WorkflowPauseInfo + nil, // 32: temporal.server.api.persistence.v1.ShardInfo.ReplicationDlqAckLevelEntry + nil, // 33: temporal.server.api.persistence.v1.ShardInfo.QueueStatesEntry + nil, // 34: temporal.server.api.persistence.v1.WorkflowExecutionInfo.SearchAttributesEntry + nil, // 35: temporal.server.api.persistence.v1.WorkflowExecutionInfo.MemoEntry + nil, // 36: temporal.server.api.persistence.v1.WorkflowExecutionInfo.UpdateInfosEntry + nil, // 37: temporal.server.api.persistence.v1.WorkflowExecutionInfo.SubStateMachinesByTypeEntry + nil, // 38: temporal.server.api.persistence.v1.WorkflowExecutionInfo.ChildrenInitializedPostResetPointEntry + nil, // 39: temporal.server.api.persistence.v1.WorkflowExecutionState.RequestIdsEntry + (*TransferTaskInfo_CloseExecutionTaskDetails)(nil), // 40: temporal.server.api.persistence.v1.TransferTaskInfo.CloseExecutionTaskDetails + (*ActivityInfo_UseWorkflowBuildIdInfo)(nil), // 41: temporal.server.api.persistence.v1.ActivityInfo.UseWorkflowBuildIdInfo + (*ActivityInfo_PauseInfo)(nil), // 42: temporal.server.api.persistence.v1.ActivityInfo.PauseInfo + (*ActivityInfo_PauseInfo_Manual)(nil), // 43: temporal.server.api.persistence.v1.ActivityInfo.PauseInfo.Manual + (*Callback_Nexus)(nil), // 44: temporal.server.api.persistence.v1.Callback.Nexus + (*Callback_HSM)(nil), // 45: temporal.server.api.persistence.v1.Callback.HSM + nil, // 46: temporal.server.api.persistence.v1.Callback.Nexus.HeaderEntry + (*CallbackInfo_WorkflowClosed)(nil), // 47: temporal.server.api.persistence.v1.CallbackInfo.WorkflowClosed + (*CallbackInfo_Trigger)(nil), // 48: temporal.server.api.persistence.v1.CallbackInfo.Trigger + (*timestamppb.Timestamp)(nil), // 49: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 50: google.protobuf.Duration + (v1.WorkflowTaskType)(0), // 51: temporal.server.api.enums.v1.WorkflowTaskType + (v11.SuggestContinueAsNewReason)(0), // 52: temporal.api.enums.v1.SuggestContinueAsNewReason + (*v12.ResetPoints)(nil), // 53: temporal.api.workflow.v1.ResetPoints + (*v14.VersionHistories)(nil), // 54: temporal.server.api.history.v1.VersionHistories + (*v15.VectorClock)(nil), // 55: temporal.server.api.clock.v1.VectorClock + (*v16.BaseExecutionInfo)(nil), // 56: temporal.server.api.workflow.v1.BaseExecutionInfo + (*v13.WorkerVersionStamp)(nil), // 57: temporal.api.common.v1.WorkerVersionStamp + (*VersionedTransition)(nil), // 58: temporal.server.api.persistence.v1.VersionedTransition + (*StateMachineTimerGroup)(nil), // 59: temporal.server.api.persistence.v1.StateMachineTimerGroup + (*StateMachineTombstoneBatch)(nil), // 60: temporal.server.api.persistence.v1.StateMachineTombstoneBatch + (*v12.WorkflowExecutionVersioningInfo)(nil), // 61: temporal.api.workflow.v1.WorkflowExecutionVersioningInfo + (*v13.Priority)(nil), // 62: temporal.api.common.v1.Priority + (v11.WorkflowTaskFailedCause)(0), // 63: temporal.api.enums.v1.WorkflowTaskFailedCause + (v11.TimeoutType)(0), // 64: temporal.api.enums.v1.TimeoutType + (*v17.DeclinedTargetVersionUpgrade)(nil), // 65: temporal.api.history.v1.DeclinedTargetVersionUpgrade + (*v18.PropagatedSerializationContext)(nil), // 66: temporal.api.nexus.v1.PropagatedSerializationContext + (*v13.TimeSkippingConfig)(nil), // 67: temporal.api.common.v1.TimeSkippingConfig + (*v19.WorkerDeploymentVersion)(nil), // 68: temporal.api.deployment.v1.WorkerDeploymentVersion + (v1.WorkflowExecutionState)(0), // 69: temporal.server.api.enums.v1.WorkflowExecutionState + (v11.WorkflowExecutionStatus)(0), // 70: temporal.api.enums.v1.WorkflowExecutionStatus + (v11.EventType)(0), // 71: temporal.api.enums.v1.EventType + (v1.TaskType)(0), // 72: temporal.server.api.enums.v1.TaskType + (*ChasmTaskInfo)(nil), // 73: temporal.server.api.persistence.v1.ChasmTaskInfo + (v1.TaskPriority)(0), // 74: temporal.server.api.enums.v1.TaskPriority + (*v14.VersionHistoryItem)(nil), // 75: temporal.server.api.history.v1.VersionHistoryItem + (*v14.VersionHistory)(nil), // 76: temporal.server.api.history.v1.VersionHistory + (v1.WorkflowBackoffType)(0), // 77: temporal.server.api.enums.v1.WorkflowBackoffType + (*StateMachineTaskInfo)(nil), // 78: temporal.server.api.persistence.v1.StateMachineTaskInfo + (*v110.WorkerCommand)(nil), // 79: temporal.api.worker.v1.WorkerCommand + (*v111.Failure)(nil), // 80: temporal.api.failure.v1.Failure + (*v13.Payloads)(nil), // 81: temporal.api.common.v1.Payloads + (*v13.ActivityType)(nil), // 82: temporal.api.common.v1.ActivityType + (*v19.Deployment)(nil), // 83: temporal.api.deployment.v1.Deployment + (*emptypb.Empty)(nil), // 84: google.protobuf.Empty + (v11.ParentClosePolicy)(0), // 85: temporal.api.enums.v1.ParentClosePolicy + (v1.ChecksumFlavor)(0), // 86: temporal.server.api.enums.v1.ChecksumFlavor + (*v13.Link)(nil), // 87: temporal.api.common.v1.Link + (*v17.HistoryEvent)(nil), // 88: temporal.api.history.v1.HistoryEvent + (v1.CallbackState)(0), // 89: temporal.server.api.enums.v1.CallbackState + (v1.NexusOperationState)(0), // 90: temporal.server.api.enums.v1.NexusOperationState + (v11.NexusOperationCancellationState)(0), // 91: temporal.api.enums.v1.NexusOperationCancellationState + (*QueueState)(nil), // 92: temporal.server.api.persistence.v1.QueueState + (*v13.Payload)(nil), // 93: temporal.api.common.v1.Payload + (*UpdateInfo)(nil), // 94: temporal.server.api.persistence.v1.UpdateInfo + (*StateMachineMap)(nil), // 95: temporal.server.api.persistence.v1.StateMachineMap + (*StateMachineRef)(nil), // 96: temporal.server.api.persistence.v1.StateMachineRef } var file_temporal_server_api_persistence_v1_executions_proto_depIdxs = []int32{ - 48, // 0: temporal.server.api.persistence.v1.ShardInfo.update_time:type_name -> google.protobuf.Timestamp - 31, // 1: temporal.server.api.persistence.v1.ShardInfo.replication_dlq_ack_level:type_name -> temporal.server.api.persistence.v1.ShardInfo.ReplicationDlqAckLevelEntry - 32, // 2: temporal.server.api.persistence.v1.ShardInfo.queue_states:type_name -> temporal.server.api.persistence.v1.ShardInfo.QueueStatesEntry - 49, // 3: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_execution_timeout:type_name -> google.protobuf.Duration - 49, // 4: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_run_timeout:type_name -> google.protobuf.Duration - 49, // 5: temporal.server.api.persistence.v1.WorkflowExecutionInfo.default_workflow_task_timeout:type_name -> google.protobuf.Duration - 48, // 6: temporal.server.api.persistence.v1.WorkflowExecutionInfo.start_time:type_name -> google.protobuf.Timestamp - 48, // 7: temporal.server.api.persistence.v1.WorkflowExecutionInfo.last_update_time:type_name -> google.protobuf.Timestamp - 49, // 8: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_timeout:type_name -> google.protobuf.Duration - 48, // 9: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_started_time:type_name -> google.protobuf.Timestamp - 48, // 10: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_scheduled_time:type_name -> google.protobuf.Timestamp - 48, // 11: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_original_scheduled_time:type_name -> google.protobuf.Timestamp - 50, // 12: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_type:type_name -> temporal.server.api.enums.v1.WorkflowTaskType - 51, // 13: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_suggest_continue_as_new_reasons:type_name -> temporal.api.enums.v1.SuggestContinueAsNewReason - 49, // 14: temporal.server.api.persistence.v1.WorkflowExecutionInfo.sticky_schedule_to_start_timeout:type_name -> google.protobuf.Duration - 49, // 15: temporal.server.api.persistence.v1.WorkflowExecutionInfo.retry_initial_interval:type_name -> google.protobuf.Duration - 49, // 16: temporal.server.api.persistence.v1.WorkflowExecutionInfo.retry_maximum_interval:type_name -> google.protobuf.Duration - 48, // 17: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_execution_expiration_time:type_name -> google.protobuf.Timestamp - 52, // 18: temporal.server.api.persistence.v1.WorkflowExecutionInfo.auto_reset_points:type_name -> temporal.api.workflow.v1.ResetPoints - 33, // 19: temporal.server.api.persistence.v1.WorkflowExecutionInfo.search_attributes:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionInfo.SearchAttributesEntry - 34, // 20: temporal.server.api.persistence.v1.WorkflowExecutionInfo.memo:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionInfo.MemoEntry - 53, // 21: temporal.server.api.persistence.v1.WorkflowExecutionInfo.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories - 5, // 22: temporal.server.api.persistence.v1.WorkflowExecutionInfo.execution_stats:type_name -> temporal.server.api.persistence.v1.ExecutionStats - 48, // 23: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_run_expiration_time:type_name -> google.protobuf.Timestamp - 48, // 24: temporal.server.api.persistence.v1.WorkflowExecutionInfo.execution_time:type_name -> google.protobuf.Timestamp - 54, // 25: temporal.server.api.persistence.v1.WorkflowExecutionInfo.parent_clock:type_name -> temporal.server.api.clock.v1.VectorClock - 48, // 26: temporal.server.api.persistence.v1.WorkflowExecutionInfo.close_time:type_name -> google.protobuf.Timestamp - 55, // 27: temporal.server.api.persistence.v1.WorkflowExecutionInfo.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo - 56, // 28: temporal.server.api.persistence.v1.WorkflowExecutionInfo.most_recent_worker_version_stamp:type_name -> temporal.api.common.v1.WorkerVersionStamp - 35, // 29: temporal.server.api.persistence.v1.WorkflowExecutionInfo.update_infos:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionInfo.UpdateInfosEntry - 57, // 30: temporal.server.api.persistence.v1.WorkflowExecutionInfo.transition_history:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 36, // 31: temporal.server.api.persistence.v1.WorkflowExecutionInfo.sub_state_machines_by_type:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionInfo.SubStateMachinesByTypeEntry - 58, // 32: temporal.server.api.persistence.v1.WorkflowExecutionInfo.state_machine_timers:type_name -> temporal.server.api.persistence.v1.StateMachineTimerGroup - 57, // 33: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 57, // 34: temporal.server.api.persistence.v1.WorkflowExecutionInfo.visibility_last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 57, // 35: temporal.server.api.persistence.v1.WorkflowExecutionInfo.signal_request_ids_last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 59, // 36: temporal.server.api.persistence.v1.WorkflowExecutionInfo.sub_state_machine_tombstone_batches:type_name -> temporal.server.api.persistence.v1.StateMachineTombstoneBatch - 60, // 37: temporal.server.api.persistence.v1.WorkflowExecutionInfo.versioning_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionVersioningInfo - 57, // 38: temporal.server.api.persistence.v1.WorkflowExecutionInfo.previous_transition_history:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 57, // 39: temporal.server.api.persistence.v1.WorkflowExecutionInfo.last_transition_history_break_point:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 37, // 40: temporal.server.api.persistence.v1.WorkflowExecutionInfo.children_initialized_post_reset_point:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionInfo.ChildrenInitializedPostResetPointEntry - 61, // 41: temporal.server.api.persistence.v1.WorkflowExecutionInfo.priority:type_name -> temporal.api.common.v1.Priority - 30, // 42: temporal.server.api.persistence.v1.WorkflowExecutionInfo.pause_info:type_name -> temporal.server.api.persistence.v1.WorkflowPauseInfo - 62, // 43: temporal.server.api.persistence.v1.WorkflowExecutionInfo.last_workflow_task_failure_cause:type_name -> temporal.api.enums.v1.WorkflowTaskFailedCause - 63, // 44: temporal.server.api.persistence.v1.WorkflowExecutionInfo.last_workflow_task_timed_out_type:type_name -> temporal.api.enums.v1.TimeoutType - 4, // 45: temporal.server.api.persistence.v1.WorkflowExecutionInfo.last_notified_target_version:type_name -> temporal.server.api.persistence.v1.LastNotifiedTargetVersion - 64, // 46: temporal.server.api.persistence.v1.WorkflowExecutionInfo.declined_target_version_upgrade:type_name -> temporal.api.history.v1.DeclinedTargetVersionUpgrade - 2, // 47: temporal.server.api.persistence.v1.WorkflowExecutionInfo.time_skipping_info:type_name -> temporal.server.api.persistence.v1.TimeSkippingInfo - 48, // 48: temporal.server.api.persistence.v1.WorkflowExecutionInfo.mutable_state_rebuild_time:type_name -> google.protobuf.Timestamp - 65, // 49: temporal.server.api.persistence.v1.WorkflowExecutionInfo.propagated_nexus_serialization_context:type_name -> temporal.api.nexus.v1.PropagatedSerializationContext - 66, // 50: temporal.server.api.persistence.v1.TimeSkippingInfo.config:type_name -> temporal.api.common.v1.TimeSkippingConfig - 49, // 51: temporal.server.api.persistence.v1.TimeSkippingInfo.accumulated_skipped_duration:type_name -> google.protobuf.Duration - 3, // 52: temporal.server.api.persistence.v1.TimeSkippingInfo.fast_forward_info:type_name -> temporal.server.api.persistence.v1.FastForwardInfo - 57, // 53: temporal.server.api.persistence.v1.TimeSkippingInfo.fast_forward_info_last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 57, // 54: temporal.server.api.persistence.v1.TimeSkippingInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 49, // 55: temporal.server.api.persistence.v1.TimeSkippingInfo.initial_skipped_duration:type_name -> google.protobuf.Duration - 48, // 56: temporal.server.api.persistence.v1.FastForwardInfo.target_time:type_name -> google.protobuf.Timestamp - 67, // 57: temporal.server.api.persistence.v1.LastNotifiedTargetVersion.deployment_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion - 68, // 58: temporal.server.api.persistence.v1.WorkflowExecutionState.state:type_name -> temporal.server.api.enums.v1.WorkflowExecutionState - 69, // 59: temporal.server.api.persistence.v1.WorkflowExecutionState.status:type_name -> temporal.api.enums.v1.WorkflowExecutionStatus - 57, // 60: temporal.server.api.persistence.v1.WorkflowExecutionState.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 48, // 61: temporal.server.api.persistence.v1.WorkflowExecutionState.start_time:type_name -> google.protobuf.Timestamp - 38, // 62: temporal.server.api.persistence.v1.WorkflowExecutionState.request_ids:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionState.RequestIdsEntry - 70, // 63: temporal.server.api.persistence.v1.RequestIDInfo.event_type:type_name -> temporal.api.enums.v1.EventType - 48, // 64: temporal.server.api.persistence.v1.RequestIDInfo.attach_time:type_name -> google.protobuf.Timestamp - 71, // 65: temporal.server.api.persistence.v1.TransferTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType - 48, // 66: temporal.server.api.persistence.v1.TransferTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp - 39, // 67: temporal.server.api.persistence.v1.TransferTaskInfo.close_execution_task_details:type_name -> temporal.server.api.persistence.v1.TransferTaskInfo.CloseExecutionTaskDetails - 72, // 68: temporal.server.api.persistence.v1.TransferTaskInfo.chasm_task_info:type_name -> temporal.server.api.persistence.v1.ChasmTaskInfo - 71, // 69: temporal.server.api.persistence.v1.ReplicationTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType - 48, // 70: temporal.server.api.persistence.v1.ReplicationTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp - 73, // 71: temporal.server.api.persistence.v1.ReplicationTaskInfo.priority:type_name -> temporal.server.api.enums.v1.TaskPriority - 57, // 72: temporal.server.api.persistence.v1.ReplicationTaskInfo.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 9, // 73: temporal.server.api.persistence.v1.ReplicationTaskInfo.task_equivalents:type_name -> temporal.server.api.persistence.v1.ReplicationTaskInfo - 74, // 74: temporal.server.api.persistence.v1.ReplicationTaskInfo.last_version_history_item:type_name -> temporal.server.api.history.v1.VersionHistoryItem - 75, // 75: temporal.server.api.persistence.v1.ReplicationTaskInfo.current_version_history:type_name -> temporal.server.api.history.v1.VersionHistory - 71, // 76: temporal.server.api.persistence.v1.VisibilityTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType - 48, // 77: temporal.server.api.persistence.v1.VisibilityTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp - 48, // 78: temporal.server.api.persistence.v1.VisibilityTaskInfo.close_time:type_name -> google.protobuf.Timestamp - 48, // 79: temporal.server.api.persistence.v1.VisibilityTaskInfo.start_time:type_name -> google.protobuf.Timestamp - 72, // 80: temporal.server.api.persistence.v1.VisibilityTaskInfo.chasm_task_info:type_name -> temporal.server.api.persistence.v1.ChasmTaskInfo - 71, // 81: temporal.server.api.persistence.v1.TimerTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType - 63, // 82: temporal.server.api.persistence.v1.TimerTaskInfo.timeout_type:type_name -> temporal.api.enums.v1.TimeoutType - 76, // 83: temporal.server.api.persistence.v1.TimerTaskInfo.workflow_backoff_type:type_name -> temporal.server.api.enums.v1.WorkflowBackoffType - 48, // 84: temporal.server.api.persistence.v1.TimerTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp - 72, // 85: temporal.server.api.persistence.v1.TimerTaskInfo.chasm_task_info:type_name -> temporal.server.api.persistence.v1.ChasmTaskInfo - 57, // 86: temporal.server.api.persistence.v1.TimerTaskInfo.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 71, // 87: temporal.server.api.persistence.v1.ArchivalTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType - 48, // 88: temporal.server.api.persistence.v1.ArchivalTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp - 71, // 89: temporal.server.api.persistence.v1.OutboundTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType - 48, // 90: temporal.server.api.persistence.v1.OutboundTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp - 77, // 91: temporal.server.api.persistence.v1.OutboundTaskInfo.state_machine_info:type_name -> temporal.server.api.persistence.v1.StateMachineTaskInfo - 72, // 92: temporal.server.api.persistence.v1.OutboundTaskInfo.chasm_task_info:type_name -> temporal.server.api.persistence.v1.ChasmTaskInfo - 14, // 93: temporal.server.api.persistence.v1.OutboundTaskInfo.worker_commands_task:type_name -> temporal.server.api.persistence.v1.WorkerCommandsTask - 78, // 94: temporal.server.api.persistence.v1.WorkerCommandsTask.commands:type_name -> temporal.api.worker.v1.WorkerCommand - 48, // 95: temporal.server.api.persistence.v1.ActivityInfo.scheduled_time:type_name -> google.protobuf.Timestamp - 48, // 96: temporal.server.api.persistence.v1.ActivityInfo.started_time:type_name -> google.protobuf.Timestamp - 49, // 97: temporal.server.api.persistence.v1.ActivityInfo.schedule_to_start_timeout:type_name -> google.protobuf.Duration - 49, // 98: temporal.server.api.persistence.v1.ActivityInfo.schedule_to_close_timeout:type_name -> google.protobuf.Duration - 49, // 99: temporal.server.api.persistence.v1.ActivityInfo.start_to_close_timeout:type_name -> google.protobuf.Duration - 49, // 100: temporal.server.api.persistence.v1.ActivityInfo.heartbeat_timeout:type_name -> google.protobuf.Duration - 49, // 101: temporal.server.api.persistence.v1.ActivityInfo.retry_initial_interval:type_name -> google.protobuf.Duration - 49, // 102: temporal.server.api.persistence.v1.ActivityInfo.retry_maximum_interval:type_name -> google.protobuf.Duration - 48, // 103: temporal.server.api.persistence.v1.ActivityInfo.retry_expiration_time:type_name -> google.protobuf.Timestamp - 79, // 104: temporal.server.api.persistence.v1.ActivityInfo.retry_last_failure:type_name -> temporal.api.failure.v1.Failure - 80, // 105: temporal.server.api.persistence.v1.ActivityInfo.last_heartbeat_details:type_name -> temporal.api.common.v1.Payloads - 48, // 106: temporal.server.api.persistence.v1.ActivityInfo.last_heartbeat_update_time:type_name -> google.protobuf.Timestamp - 81, // 107: temporal.server.api.persistence.v1.ActivityInfo.activity_type:type_name -> temporal.api.common.v1.ActivityType - 40, // 108: temporal.server.api.persistence.v1.ActivityInfo.use_workflow_build_id_info:type_name -> temporal.server.api.persistence.v1.ActivityInfo.UseWorkflowBuildIdInfo - 56, // 109: temporal.server.api.persistence.v1.ActivityInfo.last_worker_version_stamp:type_name -> temporal.api.common.v1.WorkerVersionStamp - 57, // 110: temporal.server.api.persistence.v1.ActivityInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 48, // 111: temporal.server.api.persistence.v1.ActivityInfo.first_scheduled_time:type_name -> google.protobuf.Timestamp - 48, // 112: temporal.server.api.persistence.v1.ActivityInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp - 82, // 113: temporal.server.api.persistence.v1.ActivityInfo.last_started_deployment:type_name -> temporal.api.deployment.v1.Deployment - 67, // 114: temporal.server.api.persistence.v1.ActivityInfo.last_deployment_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion - 61, // 115: temporal.server.api.persistence.v1.ActivityInfo.priority:type_name -> temporal.api.common.v1.Priority - 41, // 116: temporal.server.api.persistence.v1.ActivityInfo.pause_info:type_name -> temporal.server.api.persistence.v1.ActivityInfo.PauseInfo - 54, // 117: temporal.server.api.persistence.v1.ActivityInfo.started_clock:type_name -> temporal.server.api.clock.v1.VectorClock - 18, // 118: temporal.server.api.persistence.v1.ActivityInfo.session_state:type_name -> temporal.server.api.persistence.v1.ActivitySessionState - 83, // 119: temporal.server.api.persistence.v1.ActivitySessionState.workflow_session:type_name -> google.protobuf.Empty - 48, // 120: temporal.server.api.persistence.v1.TimerInfo.expiry_time:type_name -> google.protobuf.Timestamp - 57, // 121: temporal.server.api.persistence.v1.TimerInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 84, // 122: temporal.server.api.persistence.v1.ChildExecutionInfo.parent_close_policy:type_name -> temporal.api.enums.v1.ParentClosePolicy - 54, // 123: temporal.server.api.persistence.v1.ChildExecutionInfo.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 57, // 124: temporal.server.api.persistence.v1.ChildExecutionInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 61, // 125: temporal.server.api.persistence.v1.ChildExecutionInfo.priority:type_name -> temporal.api.common.v1.Priority - 57, // 126: temporal.server.api.persistence.v1.RequestCancelInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 57, // 127: temporal.server.api.persistence.v1.SignalInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 85, // 128: temporal.server.api.persistence.v1.Checksum.flavor:type_name -> temporal.server.api.enums.v1.ChecksumFlavor - 43, // 129: temporal.server.api.persistence.v1.Callback.nexus:type_name -> temporal.server.api.persistence.v1.Callback.Nexus - 44, // 130: temporal.server.api.persistence.v1.Callback.hsm:type_name -> temporal.server.api.persistence.v1.Callback.HSM - 86, // 131: temporal.server.api.persistence.v1.Callback.links:type_name -> temporal.api.common.v1.Link - 87, // 132: temporal.server.api.persistence.v1.HSMCompletionCallbackArg.last_event:type_name -> temporal.api.history.v1.HistoryEvent - 24, // 133: temporal.server.api.persistence.v1.CallbackInfo.callback:type_name -> temporal.server.api.persistence.v1.Callback - 47, // 134: temporal.server.api.persistence.v1.CallbackInfo.trigger:type_name -> temporal.server.api.persistence.v1.CallbackInfo.Trigger - 48, // 135: temporal.server.api.persistence.v1.CallbackInfo.registration_time:type_name -> google.protobuf.Timestamp - 88, // 136: temporal.server.api.persistence.v1.CallbackInfo.state:type_name -> temporal.server.api.enums.v1.CallbackState - 48, // 137: temporal.server.api.persistence.v1.CallbackInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp - 79, // 138: temporal.server.api.persistence.v1.CallbackInfo.last_attempt_failure:type_name -> temporal.api.failure.v1.Failure - 48, // 139: temporal.server.api.persistence.v1.CallbackInfo.next_attempt_schedule_time:type_name -> google.protobuf.Timestamp - 49, // 140: temporal.server.api.persistence.v1.NexusOperationInfo.schedule_to_close_timeout:type_name -> google.protobuf.Duration - 48, // 141: temporal.server.api.persistence.v1.NexusOperationInfo.scheduled_time:type_name -> google.protobuf.Timestamp - 89, // 142: temporal.server.api.persistence.v1.NexusOperationInfo.state:type_name -> temporal.server.api.enums.v1.NexusOperationState - 48, // 143: temporal.server.api.persistence.v1.NexusOperationInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp - 79, // 144: temporal.server.api.persistence.v1.NexusOperationInfo.last_attempt_failure:type_name -> temporal.api.failure.v1.Failure - 48, // 145: temporal.server.api.persistence.v1.NexusOperationInfo.next_attempt_schedule_time:type_name -> google.protobuf.Timestamp - 49, // 146: temporal.server.api.persistence.v1.NexusOperationInfo.schedule_to_start_timeout:type_name -> google.protobuf.Duration - 49, // 147: temporal.server.api.persistence.v1.NexusOperationInfo.start_to_close_timeout:type_name -> google.protobuf.Duration - 48, // 148: temporal.server.api.persistence.v1.NexusOperationInfo.started_time:type_name -> google.protobuf.Timestamp - 48, // 149: temporal.server.api.persistence.v1.NexusOperationCancellationInfo.requested_time:type_name -> google.protobuf.Timestamp - 90, // 150: temporal.server.api.persistence.v1.NexusOperationCancellationInfo.state:type_name -> temporal.api.enums.v1.NexusOperationCancellationState - 48, // 151: temporal.server.api.persistence.v1.NexusOperationCancellationInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp - 79, // 152: temporal.server.api.persistence.v1.NexusOperationCancellationInfo.last_attempt_failure:type_name -> temporal.api.failure.v1.Failure - 48, // 153: temporal.server.api.persistence.v1.NexusOperationCancellationInfo.next_attempt_schedule_time:type_name -> google.protobuf.Timestamp - 48, // 154: temporal.server.api.persistence.v1.WorkflowPauseInfo.pause_time:type_name -> google.protobuf.Timestamp - 91, // 155: temporal.server.api.persistence.v1.ShardInfo.QueueStatesEntry.value:type_name -> temporal.server.api.persistence.v1.QueueState - 92, // 156: temporal.server.api.persistence.v1.WorkflowExecutionInfo.SearchAttributesEntry.value:type_name -> temporal.api.common.v1.Payload - 92, // 157: temporal.server.api.persistence.v1.WorkflowExecutionInfo.MemoEntry.value:type_name -> temporal.api.common.v1.Payload - 93, // 158: temporal.server.api.persistence.v1.WorkflowExecutionInfo.UpdateInfosEntry.value:type_name -> temporal.server.api.persistence.v1.UpdateInfo - 94, // 159: temporal.server.api.persistence.v1.WorkflowExecutionInfo.SubStateMachinesByTypeEntry.value:type_name -> temporal.server.api.persistence.v1.StateMachineMap - 29, // 160: temporal.server.api.persistence.v1.WorkflowExecutionInfo.ChildrenInitializedPostResetPointEntry.value:type_name -> temporal.server.api.persistence.v1.ResetChildInfo - 7, // 161: temporal.server.api.persistence.v1.WorkflowExecutionState.RequestIdsEntry.value:type_name -> temporal.server.api.persistence.v1.RequestIDInfo - 48, // 162: temporal.server.api.persistence.v1.ActivityInfo.PauseInfo.pause_time:type_name -> google.protobuf.Timestamp - 42, // 163: temporal.server.api.persistence.v1.ActivityInfo.PauseInfo.manual:type_name -> temporal.server.api.persistence.v1.ActivityInfo.PauseInfo.Manual - 45, // 164: temporal.server.api.persistence.v1.Callback.Nexus.header:type_name -> temporal.server.api.persistence.v1.Callback.Nexus.HeaderEntry - 95, // 165: temporal.server.api.persistence.v1.Callback.HSM.ref:type_name -> temporal.server.api.persistence.v1.StateMachineRef - 46, // 166: temporal.server.api.persistence.v1.CallbackInfo.Trigger.workflow_closed:type_name -> temporal.server.api.persistence.v1.CallbackInfo.WorkflowClosed - 167, // [167:167] is the sub-list for method output_type - 167, // [167:167] is the sub-list for method input_type - 167, // [167:167] is the sub-list for extension type_name - 167, // [167:167] is the sub-list for extension extendee - 0, // [0:167] is the sub-list for field type_name + 49, // 0: temporal.server.api.persistence.v1.ShardInfo.update_time:type_name -> google.protobuf.Timestamp + 32, // 1: temporal.server.api.persistence.v1.ShardInfo.replication_dlq_ack_level:type_name -> temporal.server.api.persistence.v1.ShardInfo.ReplicationDlqAckLevelEntry + 33, // 2: temporal.server.api.persistence.v1.ShardInfo.queue_states:type_name -> temporal.server.api.persistence.v1.ShardInfo.QueueStatesEntry + 50, // 3: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_execution_timeout:type_name -> google.protobuf.Duration + 50, // 4: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_run_timeout:type_name -> google.protobuf.Duration + 50, // 5: temporal.server.api.persistence.v1.WorkflowExecutionInfo.default_workflow_task_timeout:type_name -> google.protobuf.Duration + 49, // 6: temporal.server.api.persistence.v1.WorkflowExecutionInfo.start_time:type_name -> google.protobuf.Timestamp + 49, // 7: temporal.server.api.persistence.v1.WorkflowExecutionInfo.last_update_time:type_name -> google.protobuf.Timestamp + 50, // 8: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_timeout:type_name -> google.protobuf.Duration + 49, // 9: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_started_time:type_name -> google.protobuf.Timestamp + 49, // 10: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_scheduled_time:type_name -> google.protobuf.Timestamp + 49, // 11: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_original_scheduled_time:type_name -> google.protobuf.Timestamp + 51, // 12: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_type:type_name -> temporal.server.api.enums.v1.WorkflowTaskType + 52, // 13: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_suggest_continue_as_new_reasons:type_name -> temporal.api.enums.v1.SuggestContinueAsNewReason + 50, // 14: temporal.server.api.persistence.v1.WorkflowExecutionInfo.sticky_schedule_to_start_timeout:type_name -> google.protobuf.Duration + 50, // 15: temporal.server.api.persistence.v1.WorkflowExecutionInfo.retry_initial_interval:type_name -> google.protobuf.Duration + 50, // 16: temporal.server.api.persistence.v1.WorkflowExecutionInfo.retry_maximum_interval:type_name -> google.protobuf.Duration + 49, // 17: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_execution_expiration_time:type_name -> google.protobuf.Timestamp + 53, // 18: temporal.server.api.persistence.v1.WorkflowExecutionInfo.auto_reset_points:type_name -> temporal.api.workflow.v1.ResetPoints + 34, // 19: temporal.server.api.persistence.v1.WorkflowExecutionInfo.search_attributes:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionInfo.SearchAttributesEntry + 35, // 20: temporal.server.api.persistence.v1.WorkflowExecutionInfo.memo:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionInfo.MemoEntry + 54, // 21: temporal.server.api.persistence.v1.WorkflowExecutionInfo.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories + 6, // 22: temporal.server.api.persistence.v1.WorkflowExecutionInfo.execution_stats:type_name -> temporal.server.api.persistence.v1.ExecutionStats + 49, // 23: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_run_expiration_time:type_name -> google.protobuf.Timestamp + 49, // 24: temporal.server.api.persistence.v1.WorkflowExecutionInfo.execution_time:type_name -> google.protobuf.Timestamp + 55, // 25: temporal.server.api.persistence.v1.WorkflowExecutionInfo.parent_clock:type_name -> temporal.server.api.clock.v1.VectorClock + 49, // 26: temporal.server.api.persistence.v1.WorkflowExecutionInfo.close_time:type_name -> google.protobuf.Timestamp + 56, // 27: temporal.server.api.persistence.v1.WorkflowExecutionInfo.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo + 57, // 28: temporal.server.api.persistence.v1.WorkflowExecutionInfo.most_recent_worker_version_stamp:type_name -> temporal.api.common.v1.WorkerVersionStamp + 36, // 29: temporal.server.api.persistence.v1.WorkflowExecutionInfo.update_infos:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionInfo.UpdateInfosEntry + 58, // 30: temporal.server.api.persistence.v1.WorkflowExecutionInfo.transition_history:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 37, // 31: temporal.server.api.persistence.v1.WorkflowExecutionInfo.sub_state_machines_by_type:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionInfo.SubStateMachinesByTypeEntry + 59, // 32: temporal.server.api.persistence.v1.WorkflowExecutionInfo.state_machine_timers:type_name -> temporal.server.api.persistence.v1.StateMachineTimerGroup + 58, // 33: temporal.server.api.persistence.v1.WorkflowExecutionInfo.workflow_task_last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 58, // 34: temporal.server.api.persistence.v1.WorkflowExecutionInfo.visibility_last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 58, // 35: temporal.server.api.persistence.v1.WorkflowExecutionInfo.signal_request_ids_last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 60, // 36: temporal.server.api.persistence.v1.WorkflowExecutionInfo.sub_state_machine_tombstone_batches:type_name -> temporal.server.api.persistence.v1.StateMachineTombstoneBatch + 61, // 37: temporal.server.api.persistence.v1.WorkflowExecutionInfo.versioning_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionVersioningInfo + 58, // 38: temporal.server.api.persistence.v1.WorkflowExecutionInfo.previous_transition_history:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 58, // 39: temporal.server.api.persistence.v1.WorkflowExecutionInfo.last_transition_history_break_point:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 38, // 40: temporal.server.api.persistence.v1.WorkflowExecutionInfo.children_initialized_post_reset_point:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionInfo.ChildrenInitializedPostResetPointEntry + 62, // 41: temporal.server.api.persistence.v1.WorkflowExecutionInfo.priority:type_name -> temporal.api.common.v1.Priority + 31, // 42: temporal.server.api.persistence.v1.WorkflowExecutionInfo.pause_info:type_name -> temporal.server.api.persistence.v1.WorkflowPauseInfo + 63, // 43: temporal.server.api.persistence.v1.WorkflowExecutionInfo.last_workflow_task_failure_cause:type_name -> temporal.api.enums.v1.WorkflowTaskFailedCause + 64, // 44: temporal.server.api.persistence.v1.WorkflowExecutionInfo.last_workflow_task_timed_out_type:type_name -> temporal.api.enums.v1.TimeoutType + 5, // 45: temporal.server.api.persistence.v1.WorkflowExecutionInfo.last_notified_target_version:type_name -> temporal.server.api.persistence.v1.LastNotifiedTargetVersion + 65, // 46: temporal.server.api.persistence.v1.WorkflowExecutionInfo.declined_target_version_upgrade:type_name -> temporal.api.history.v1.DeclinedTargetVersionUpgrade + 3, // 47: temporal.server.api.persistence.v1.WorkflowExecutionInfo.time_skipping_info:type_name -> temporal.server.api.persistence.v1.TimeSkippingInfo + 49, // 48: temporal.server.api.persistence.v1.WorkflowExecutionInfo.mutable_state_rebuild_time:type_name -> google.protobuf.Timestamp + 66, // 49: temporal.server.api.persistence.v1.WorkflowExecutionInfo.propagated_nexus_serialization_context:type_name -> temporal.api.nexus.v1.PropagatedSerializationContext + 2, // 50: temporal.server.api.persistence.v1.WorkflowExecutionInfo.session_state:type_name -> temporal.server.api.persistence.v1.SessionState + 67, // 51: temporal.server.api.persistence.v1.TimeSkippingInfo.config:type_name -> temporal.api.common.v1.TimeSkippingConfig + 50, // 52: temporal.server.api.persistence.v1.TimeSkippingInfo.accumulated_skipped_duration:type_name -> google.protobuf.Duration + 4, // 53: temporal.server.api.persistence.v1.TimeSkippingInfo.fast_forward_info:type_name -> temporal.server.api.persistence.v1.FastForwardInfo + 58, // 54: temporal.server.api.persistence.v1.TimeSkippingInfo.fast_forward_info_last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 58, // 55: temporal.server.api.persistence.v1.TimeSkippingInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 50, // 56: temporal.server.api.persistence.v1.TimeSkippingInfo.initial_skipped_duration:type_name -> google.protobuf.Duration + 49, // 57: temporal.server.api.persistence.v1.FastForwardInfo.target_time:type_name -> google.protobuf.Timestamp + 68, // 58: temporal.server.api.persistence.v1.LastNotifiedTargetVersion.deployment_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion + 69, // 59: temporal.server.api.persistence.v1.WorkflowExecutionState.state:type_name -> temporal.server.api.enums.v1.WorkflowExecutionState + 70, // 60: temporal.server.api.persistence.v1.WorkflowExecutionState.status:type_name -> temporal.api.enums.v1.WorkflowExecutionStatus + 58, // 61: temporal.server.api.persistence.v1.WorkflowExecutionState.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 49, // 62: temporal.server.api.persistence.v1.WorkflowExecutionState.start_time:type_name -> google.protobuf.Timestamp + 39, // 63: temporal.server.api.persistence.v1.WorkflowExecutionState.request_ids:type_name -> temporal.server.api.persistence.v1.WorkflowExecutionState.RequestIdsEntry + 71, // 64: temporal.server.api.persistence.v1.RequestIDInfo.event_type:type_name -> temporal.api.enums.v1.EventType + 49, // 65: temporal.server.api.persistence.v1.RequestIDInfo.attach_time:type_name -> google.protobuf.Timestamp + 72, // 66: temporal.server.api.persistence.v1.TransferTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType + 49, // 67: temporal.server.api.persistence.v1.TransferTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp + 40, // 68: temporal.server.api.persistence.v1.TransferTaskInfo.close_execution_task_details:type_name -> temporal.server.api.persistence.v1.TransferTaskInfo.CloseExecutionTaskDetails + 73, // 69: temporal.server.api.persistence.v1.TransferTaskInfo.chasm_task_info:type_name -> temporal.server.api.persistence.v1.ChasmTaskInfo + 72, // 70: temporal.server.api.persistence.v1.ReplicationTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType + 49, // 71: temporal.server.api.persistence.v1.ReplicationTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp + 74, // 72: temporal.server.api.persistence.v1.ReplicationTaskInfo.priority:type_name -> temporal.server.api.enums.v1.TaskPriority + 58, // 73: temporal.server.api.persistence.v1.ReplicationTaskInfo.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 10, // 74: temporal.server.api.persistence.v1.ReplicationTaskInfo.task_equivalents:type_name -> temporal.server.api.persistence.v1.ReplicationTaskInfo + 75, // 75: temporal.server.api.persistence.v1.ReplicationTaskInfo.last_version_history_item:type_name -> temporal.server.api.history.v1.VersionHistoryItem + 76, // 76: temporal.server.api.persistence.v1.ReplicationTaskInfo.current_version_history:type_name -> temporal.server.api.history.v1.VersionHistory + 72, // 77: temporal.server.api.persistence.v1.VisibilityTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType + 49, // 78: temporal.server.api.persistence.v1.VisibilityTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp + 49, // 79: temporal.server.api.persistence.v1.VisibilityTaskInfo.close_time:type_name -> google.protobuf.Timestamp + 49, // 80: temporal.server.api.persistence.v1.VisibilityTaskInfo.start_time:type_name -> google.protobuf.Timestamp + 73, // 81: temporal.server.api.persistence.v1.VisibilityTaskInfo.chasm_task_info:type_name -> temporal.server.api.persistence.v1.ChasmTaskInfo + 72, // 82: temporal.server.api.persistence.v1.TimerTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType + 64, // 83: temporal.server.api.persistence.v1.TimerTaskInfo.timeout_type:type_name -> temporal.api.enums.v1.TimeoutType + 77, // 84: temporal.server.api.persistence.v1.TimerTaskInfo.workflow_backoff_type:type_name -> temporal.server.api.enums.v1.WorkflowBackoffType + 49, // 85: temporal.server.api.persistence.v1.TimerTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp + 73, // 86: temporal.server.api.persistence.v1.TimerTaskInfo.chasm_task_info:type_name -> temporal.server.api.persistence.v1.ChasmTaskInfo + 58, // 87: temporal.server.api.persistence.v1.TimerTaskInfo.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 72, // 88: temporal.server.api.persistence.v1.ArchivalTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType + 49, // 89: temporal.server.api.persistence.v1.ArchivalTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp + 72, // 90: temporal.server.api.persistence.v1.OutboundTaskInfo.task_type:type_name -> temporal.server.api.enums.v1.TaskType + 49, // 91: temporal.server.api.persistence.v1.OutboundTaskInfo.visibility_time:type_name -> google.protobuf.Timestamp + 78, // 92: temporal.server.api.persistence.v1.OutboundTaskInfo.state_machine_info:type_name -> temporal.server.api.persistence.v1.StateMachineTaskInfo + 73, // 93: temporal.server.api.persistence.v1.OutboundTaskInfo.chasm_task_info:type_name -> temporal.server.api.persistence.v1.ChasmTaskInfo + 15, // 94: temporal.server.api.persistence.v1.OutboundTaskInfo.worker_commands_task:type_name -> temporal.server.api.persistence.v1.WorkerCommandsTask + 79, // 95: temporal.server.api.persistence.v1.WorkerCommandsTask.commands:type_name -> temporal.api.worker.v1.WorkerCommand + 49, // 96: temporal.server.api.persistence.v1.ActivityInfo.scheduled_time:type_name -> google.protobuf.Timestamp + 49, // 97: temporal.server.api.persistence.v1.ActivityInfo.started_time:type_name -> google.protobuf.Timestamp + 50, // 98: temporal.server.api.persistence.v1.ActivityInfo.schedule_to_start_timeout:type_name -> google.protobuf.Duration + 50, // 99: temporal.server.api.persistence.v1.ActivityInfo.schedule_to_close_timeout:type_name -> google.protobuf.Duration + 50, // 100: temporal.server.api.persistence.v1.ActivityInfo.start_to_close_timeout:type_name -> google.protobuf.Duration + 50, // 101: temporal.server.api.persistence.v1.ActivityInfo.heartbeat_timeout:type_name -> google.protobuf.Duration + 50, // 102: temporal.server.api.persistence.v1.ActivityInfo.retry_initial_interval:type_name -> google.protobuf.Duration + 50, // 103: temporal.server.api.persistence.v1.ActivityInfo.retry_maximum_interval:type_name -> google.protobuf.Duration + 49, // 104: temporal.server.api.persistence.v1.ActivityInfo.retry_expiration_time:type_name -> google.protobuf.Timestamp + 80, // 105: temporal.server.api.persistence.v1.ActivityInfo.retry_last_failure:type_name -> temporal.api.failure.v1.Failure + 81, // 106: temporal.server.api.persistence.v1.ActivityInfo.last_heartbeat_details:type_name -> temporal.api.common.v1.Payloads + 49, // 107: temporal.server.api.persistence.v1.ActivityInfo.last_heartbeat_update_time:type_name -> google.protobuf.Timestamp + 82, // 108: temporal.server.api.persistence.v1.ActivityInfo.activity_type:type_name -> temporal.api.common.v1.ActivityType + 41, // 109: temporal.server.api.persistence.v1.ActivityInfo.use_workflow_build_id_info:type_name -> temporal.server.api.persistence.v1.ActivityInfo.UseWorkflowBuildIdInfo + 57, // 110: temporal.server.api.persistence.v1.ActivityInfo.last_worker_version_stamp:type_name -> temporal.api.common.v1.WorkerVersionStamp + 58, // 111: temporal.server.api.persistence.v1.ActivityInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 49, // 112: temporal.server.api.persistence.v1.ActivityInfo.first_scheduled_time:type_name -> google.protobuf.Timestamp + 49, // 113: temporal.server.api.persistence.v1.ActivityInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp + 83, // 114: temporal.server.api.persistence.v1.ActivityInfo.last_started_deployment:type_name -> temporal.api.deployment.v1.Deployment + 68, // 115: temporal.server.api.persistence.v1.ActivityInfo.last_deployment_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion + 62, // 116: temporal.server.api.persistence.v1.ActivityInfo.priority:type_name -> temporal.api.common.v1.Priority + 42, // 117: temporal.server.api.persistence.v1.ActivityInfo.pause_info:type_name -> temporal.server.api.persistence.v1.ActivityInfo.PauseInfo + 55, // 118: temporal.server.api.persistence.v1.ActivityInfo.started_clock:type_name -> temporal.server.api.clock.v1.VectorClock + 19, // 119: temporal.server.api.persistence.v1.ActivityInfo.session_state:type_name -> temporal.server.api.persistence.v1.ActivitySessionState + 84, // 120: temporal.server.api.persistence.v1.ActivitySessionState.workflow_session:type_name -> google.protobuf.Empty + 49, // 121: temporal.server.api.persistence.v1.TimerInfo.expiry_time:type_name -> google.protobuf.Timestamp + 58, // 122: temporal.server.api.persistence.v1.TimerInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 85, // 123: temporal.server.api.persistence.v1.ChildExecutionInfo.parent_close_policy:type_name -> temporal.api.enums.v1.ParentClosePolicy + 55, // 124: temporal.server.api.persistence.v1.ChildExecutionInfo.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 58, // 125: temporal.server.api.persistence.v1.ChildExecutionInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 62, // 126: temporal.server.api.persistence.v1.ChildExecutionInfo.priority:type_name -> temporal.api.common.v1.Priority + 58, // 127: temporal.server.api.persistence.v1.RequestCancelInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 58, // 128: temporal.server.api.persistence.v1.SignalInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 86, // 129: temporal.server.api.persistence.v1.Checksum.flavor:type_name -> temporal.server.api.enums.v1.ChecksumFlavor + 44, // 130: temporal.server.api.persistence.v1.Callback.nexus:type_name -> temporal.server.api.persistence.v1.Callback.Nexus + 45, // 131: temporal.server.api.persistence.v1.Callback.hsm:type_name -> temporal.server.api.persistence.v1.Callback.HSM + 87, // 132: temporal.server.api.persistence.v1.Callback.links:type_name -> temporal.api.common.v1.Link + 88, // 133: temporal.server.api.persistence.v1.HSMCompletionCallbackArg.last_event:type_name -> temporal.api.history.v1.HistoryEvent + 25, // 134: temporal.server.api.persistence.v1.CallbackInfo.callback:type_name -> temporal.server.api.persistence.v1.Callback + 48, // 135: temporal.server.api.persistence.v1.CallbackInfo.trigger:type_name -> temporal.server.api.persistence.v1.CallbackInfo.Trigger + 49, // 136: temporal.server.api.persistence.v1.CallbackInfo.registration_time:type_name -> google.protobuf.Timestamp + 89, // 137: temporal.server.api.persistence.v1.CallbackInfo.state:type_name -> temporal.server.api.enums.v1.CallbackState + 49, // 138: temporal.server.api.persistence.v1.CallbackInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp + 80, // 139: temporal.server.api.persistence.v1.CallbackInfo.last_attempt_failure:type_name -> temporal.api.failure.v1.Failure + 49, // 140: temporal.server.api.persistence.v1.CallbackInfo.next_attempt_schedule_time:type_name -> google.protobuf.Timestamp + 50, // 141: temporal.server.api.persistence.v1.NexusOperationInfo.schedule_to_close_timeout:type_name -> google.protobuf.Duration + 49, // 142: temporal.server.api.persistence.v1.NexusOperationInfo.scheduled_time:type_name -> google.protobuf.Timestamp + 90, // 143: temporal.server.api.persistence.v1.NexusOperationInfo.state:type_name -> temporal.server.api.enums.v1.NexusOperationState + 49, // 144: temporal.server.api.persistence.v1.NexusOperationInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp + 80, // 145: temporal.server.api.persistence.v1.NexusOperationInfo.last_attempt_failure:type_name -> temporal.api.failure.v1.Failure + 49, // 146: temporal.server.api.persistence.v1.NexusOperationInfo.next_attempt_schedule_time:type_name -> google.protobuf.Timestamp + 50, // 147: temporal.server.api.persistence.v1.NexusOperationInfo.schedule_to_start_timeout:type_name -> google.protobuf.Duration + 50, // 148: temporal.server.api.persistence.v1.NexusOperationInfo.start_to_close_timeout:type_name -> google.protobuf.Duration + 49, // 149: temporal.server.api.persistence.v1.NexusOperationInfo.started_time:type_name -> google.protobuf.Timestamp + 49, // 150: temporal.server.api.persistence.v1.NexusOperationCancellationInfo.requested_time:type_name -> google.protobuf.Timestamp + 91, // 151: temporal.server.api.persistence.v1.NexusOperationCancellationInfo.state:type_name -> temporal.api.enums.v1.NexusOperationCancellationState + 49, // 152: temporal.server.api.persistence.v1.NexusOperationCancellationInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp + 80, // 153: temporal.server.api.persistence.v1.NexusOperationCancellationInfo.last_attempt_failure:type_name -> temporal.api.failure.v1.Failure + 49, // 154: temporal.server.api.persistence.v1.NexusOperationCancellationInfo.next_attempt_schedule_time:type_name -> google.protobuf.Timestamp + 49, // 155: temporal.server.api.persistence.v1.WorkflowPauseInfo.pause_time:type_name -> google.protobuf.Timestamp + 92, // 156: temporal.server.api.persistence.v1.ShardInfo.QueueStatesEntry.value:type_name -> temporal.server.api.persistence.v1.QueueState + 93, // 157: temporal.server.api.persistence.v1.WorkflowExecutionInfo.SearchAttributesEntry.value:type_name -> temporal.api.common.v1.Payload + 93, // 158: temporal.server.api.persistence.v1.WorkflowExecutionInfo.MemoEntry.value:type_name -> temporal.api.common.v1.Payload + 94, // 159: temporal.server.api.persistence.v1.WorkflowExecutionInfo.UpdateInfosEntry.value:type_name -> temporal.server.api.persistence.v1.UpdateInfo + 95, // 160: temporal.server.api.persistence.v1.WorkflowExecutionInfo.SubStateMachinesByTypeEntry.value:type_name -> temporal.server.api.persistence.v1.StateMachineMap + 30, // 161: temporal.server.api.persistence.v1.WorkflowExecutionInfo.ChildrenInitializedPostResetPointEntry.value:type_name -> temporal.server.api.persistence.v1.ResetChildInfo + 8, // 162: temporal.server.api.persistence.v1.WorkflowExecutionState.RequestIdsEntry.value:type_name -> temporal.server.api.persistence.v1.RequestIDInfo + 49, // 163: temporal.server.api.persistence.v1.ActivityInfo.PauseInfo.pause_time:type_name -> google.protobuf.Timestamp + 43, // 164: temporal.server.api.persistence.v1.ActivityInfo.PauseInfo.manual:type_name -> temporal.server.api.persistence.v1.ActivityInfo.PauseInfo.Manual + 46, // 165: temporal.server.api.persistence.v1.Callback.Nexus.header:type_name -> temporal.server.api.persistence.v1.Callback.Nexus.HeaderEntry + 96, // 166: temporal.server.api.persistence.v1.Callback.HSM.ref:type_name -> temporal.server.api.persistence.v1.StateMachineRef + 47, // 167: temporal.server.api.persistence.v1.CallbackInfo.Trigger.workflow_closed:type_name -> temporal.server.api.persistence.v1.CallbackInfo.WorkflowClosed + 168, // [168:168] is the sub-list for method output_type + 168, // [168:168] is the sub-list for method input_type + 168, // [168:168] is the sub-list for extension type_name + 168, // [168:168] is the sub-list for extension extendee + 0, // [0:168] is the sub-list for field type_name } func init() { file_temporal_server_api_persistence_v1_executions_proto_init() } @@ -5863,37 +5934,37 @@ func file_temporal_server_api_persistence_v1_executions_proto_init() { (*WorkflowExecutionInfo_LastWorkflowTaskFailureCause)(nil), (*WorkflowExecutionInfo_LastWorkflowTaskTimedOutType)(nil), } - file_temporal_server_api_persistence_v1_executions_proto_msgTypes[8].OneofWrappers = []any{ + file_temporal_server_api_persistence_v1_executions_proto_msgTypes[9].OneofWrappers = []any{ (*TransferTaskInfo_CloseExecutionTaskDetails_)(nil), (*TransferTaskInfo_ChasmTaskInfo)(nil), } - file_temporal_server_api_persistence_v1_executions_proto_msgTypes[10].OneofWrappers = []any{ + file_temporal_server_api_persistence_v1_executions_proto_msgTypes[11].OneofWrappers = []any{ (*VisibilityTaskInfo_ChasmTaskInfo)(nil), } - file_temporal_server_api_persistence_v1_executions_proto_msgTypes[11].OneofWrappers = []any{ + file_temporal_server_api_persistence_v1_executions_proto_msgTypes[12].OneofWrappers = []any{ (*TimerTaskInfo_ChasmTaskInfo)(nil), } - file_temporal_server_api_persistence_v1_executions_proto_msgTypes[13].OneofWrappers = []any{ + file_temporal_server_api_persistence_v1_executions_proto_msgTypes[14].OneofWrappers = []any{ (*OutboundTaskInfo_StateMachineInfo)(nil), (*OutboundTaskInfo_ChasmTaskInfo)(nil), (*OutboundTaskInfo_WorkerCommandsTask)(nil), } - file_temporal_server_api_persistence_v1_executions_proto_msgTypes[17].OneofWrappers = []any{ + file_temporal_server_api_persistence_v1_executions_proto_msgTypes[18].OneofWrappers = []any{ (*ActivityInfo_UseWorkflowBuildIdInfo_)(nil), (*ActivityInfo_LastIndependentlyAssignedBuildId)(nil), } - file_temporal_server_api_persistence_v1_executions_proto_msgTypes[18].OneofWrappers = []any{ + file_temporal_server_api_persistence_v1_executions_proto_msgTypes[19].OneofWrappers = []any{ (*ActivitySessionState_WorkflowSession)(nil), } - file_temporal_server_api_persistence_v1_executions_proto_msgTypes[24].OneofWrappers = []any{ + file_temporal_server_api_persistence_v1_executions_proto_msgTypes[25].OneofWrappers = []any{ (*Callback_Nexus_)(nil), (*Callback_Hsm)(nil), } - file_temporal_server_api_persistence_v1_executions_proto_msgTypes[41].OneofWrappers = []any{ + file_temporal_server_api_persistence_v1_executions_proto_msgTypes[42].OneofWrappers = []any{ (*ActivityInfo_PauseInfo_Manual_)(nil), (*ActivityInfo_PauseInfo_RuleId)(nil), } - file_temporal_server_api_persistence_v1_executions_proto_msgTypes[47].OneofWrappers = []any{ + file_temporal_server_api_persistence_v1_executions_proto_msgTypes[48].OneofWrappers = []any{ (*CallbackInfo_Trigger_WorkflowClosed)(nil), } type x struct{} @@ -5902,7 +5973,7 @@ func file_temporal_server_api_persistence_v1_executions_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_temporal_server_api_persistence_v1_executions_proto_rawDesc), len(file_temporal_server_api_persistence_v1_executions_proto_rawDesc)), NumEnums: 0, - NumMessages: 48, + NumMessages: 49, NumExtensions: 0, NumServices: 0, }, diff --git a/api/persistence/v1/tasks.go-helpers.pb.go b/api/persistence/v1/tasks.go-helpers.pb.go index 8da8d658292..9b820c8ac18 100644 --- a/api/persistence/v1/tasks.go-helpers.pb.go +++ b/api/persistence/v1/tasks.go-helpers.pb.go @@ -116,6 +116,43 @@ func (this *TaskQueueInfo) Equal(that interface{}) bool { return proto.Equal(this, that1) } +// Marshal an object of type ExclusiveWorkerAssignment to the protobuf v3 wire format +func (val *ExclusiveWorkerAssignment) Marshal() ([]byte, error) { + return proto.Marshal(val) +} + +// Unmarshal an object of type ExclusiveWorkerAssignment from the protobuf v3 wire format +func (val *ExclusiveWorkerAssignment) Unmarshal(buf []byte) error { + return proto.Unmarshal(buf, val) +} + +// Size returns the size of the object, in bytes, once serialized +func (val *ExclusiveWorkerAssignment) Size() int { + return proto.Size(val) +} + +// Equal returns whether two ExclusiveWorkerAssignment values are equivalent by recursively +// comparing the message's fields. +// For more information see the documentation for +// https://pkg.go.dev/google.golang.org/protobuf/proto#Equal +func (this *ExclusiveWorkerAssignment) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + var that1 *ExclusiveWorkerAssignment + switch t := that.(type) { + case *ExclusiveWorkerAssignment: + that1 = t + case ExclusiveWorkerAssignment: + that1 = &t + default: + return false + } + + return proto.Equal(this, that1) +} + // Marshal an object of type SubqueueInfo to the protobuf v3 wire format func (val *SubqueueInfo) Marshal() ([]byte, error) { return proto.Marshal(val) diff --git a/api/persistence/v1/tasks.pb.go b/api/persistence/v1/tasks.pb.go index 9cadda01553..445e66654c6 100644 --- a/api/persistence/v1/tasks.pb.go +++ b/api/persistence/v1/tasks.pb.go @@ -254,9 +254,10 @@ type TaskQueueInfo struct { // can be cleared on the active table. OtherHasTasks bool `protobuf:"varint,10,opt,name=other_has_tasks,json=otherHasTasks,proto3" json:"other_has_tasks,omitempty"` // Dynamic partition scaling state (root only): - PartitionScaleState *PartitionScaleState `protobuf:"bytes,11,opt,name=partition_scale_state,json=partitionScaleState,proto3" json:"partition_scale_state,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + PartitionScaleState *PartitionScaleState `protobuf:"bytes,11,opt,name=partition_scale_state,json=partitionScaleState,proto3" json:"partition_scale_state,omitempty"` + ExclusiveWorkerAssignment *ExclusiveWorkerAssignment `protobuf:"bytes,12,opt,name=exclusive_worker_assignment,json=exclusiveWorkerAssignment,proto3" json:"exclusive_worker_assignment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TaskQueueInfo) Reset() { @@ -366,6 +367,53 @@ func (x *TaskQueueInfo) GetPartitionScaleState() *PartitionScaleState { return nil } +func (x *TaskQueueInfo) GetExclusiveWorkerAssignment() *ExclusiveWorkerAssignment { + if x != nil { + return x.ExclusiveWorkerAssignment + } + return nil +} + +// Presence records that an exclusive worker's sticky workflow queue has acquired a workflow. +// Retained after workflow completion and worker replacement to prevent reuse. +// Note: This message may grow in the recent future to include more information such as the workflowID +// for added optimizations. +type ExclusiveWorkerAssignment struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExclusiveWorkerAssignment) Reset() { + *x = ExclusiveWorkerAssignment{} + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExclusiveWorkerAssignment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExclusiveWorkerAssignment) ProtoMessage() {} + +func (x *ExclusiveWorkerAssignment) ProtoReflect() protoreflect.Message { + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExclusiveWorkerAssignment.ProtoReflect.Descriptor instead. +func (*ExclusiveWorkerAssignment) Descriptor() ([]byte, []int) { + return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{3} +} + type SubqueueInfo struct { state protoimpl.MessageState `protogen:"open.v1"` // Key is the information used by a splitting algorithm to decide which tasks should go in @@ -387,7 +435,7 @@ type SubqueueInfo struct { func (x *SubqueueInfo) Reset() { *x = SubqueueInfo{} - mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[3] + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -399,7 +447,7 @@ func (x *SubqueueInfo) String() string { func (*SubqueueInfo) ProtoMessage() {} func (x *SubqueueInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[3] + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -412,7 +460,7 @@ func (x *SubqueueInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use SubqueueInfo.ProtoReflect.Descriptor instead. func (*SubqueueInfo) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{3} + return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{4} } func (x *SubqueueInfo) GetKey() *SubqueueKey { @@ -467,7 +515,7 @@ type FairnessKeyCount struct { func (x *FairnessKeyCount) Reset() { *x = FairnessKeyCount{} - mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[4] + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -479,7 +527,7 @@ func (x *FairnessKeyCount) String() string { func (*FairnessKeyCount) ProtoMessage() {} func (x *FairnessKeyCount) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[4] + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -492,7 +540,7 @@ func (x *FairnessKeyCount) ProtoReflect() protoreflect.Message { // Deprecated: Use FairnessKeyCount.ProtoReflect.Descriptor instead. func (*FairnessKeyCount) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{4} + return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{5} } func (x *FairnessKeyCount) GetKey() string { @@ -519,7 +567,7 @@ type SubqueueKey struct { func (x *SubqueueKey) Reset() { *x = SubqueueKey{} - mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[5] + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -531,7 +579,7 @@ func (x *SubqueueKey) String() string { func (*SubqueueKey) ProtoMessage() {} func (x *SubqueueKey) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[5] + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -544,7 +592,7 @@ func (x *SubqueueKey) ProtoReflect() protoreflect.Message { // Deprecated: Use SubqueueKey.ProtoReflect.Descriptor instead. func (*SubqueueKey) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{5} + return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{6} } func (x *SubqueueKey) GetPriority() int32 { @@ -586,7 +634,7 @@ type PartitionScaleState struct { func (x *PartitionScaleState) Reset() { *x = PartitionScaleState{} - mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[6] + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -598,7 +646,7 @@ func (x *PartitionScaleState) String() string { func (*PartitionScaleState) ProtoMessage() {} func (x *PartitionScaleState) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[6] + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -611,7 +659,7 @@ func (x *PartitionScaleState) ProtoReflect() protoreflect.Message { // Deprecated: Use PartitionScaleState.ProtoReflect.Descriptor instead. func (*PartitionScaleState) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{6} + return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{7} } func (x *PartitionScaleState) GetTarget() int32 { @@ -678,7 +726,7 @@ type SimplePartitionScalerState struct { func (x *SimplePartitionScalerState) Reset() { *x = SimplePartitionScalerState{} - mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[7] + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -690,7 +738,7 @@ func (x *SimplePartitionScalerState) String() string { func (*SimplePartitionScalerState) ProtoMessage() {} func (x *SimplePartitionScalerState) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[7] + mi := &file_temporal_server_api_persistence_v1_tasks_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -703,7 +751,7 @@ func (x *SimplePartitionScalerState) ProtoReflect() protoreflect.Message { // Deprecated: Use SimplePartitionScalerState.ProtoReflect.Descriptor instead. func (*SimplePartitionScalerState) Descriptor() ([]byte, []int) { - return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{7} + return file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP(), []int{8} } func (x *SimplePartitionScalerState) GetAddTarget() int32 { @@ -744,7 +792,7 @@ const file_temporal_server_api_persistence_v1_tasks_proto_rawDesc = "" + "\x05stamp\x18\t \x01(\x05R\x05stamp\x12<\n" + "\bpriority\x18\n" + " \x01(\v2 .temporal.api.common.v1.PriorityR\bpriority\x12#\n" + - "\rcomponent_ref\x18\v \x01(\fR\fcomponentRef\"\x84\x05\n" + + "\rcomponent_ref\x18\v \x01(\fR\fcomponentRef\"\x83\x06\n" + "\rTaskQueueInfo\x12!\n" + "\fnamespace_id\x18\x01 \x01(\tR\vnamespaceId\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12A\n" + @@ -758,7 +806,9 @@ const file_temporal_server_api_persistence_v1_tasks_proto_rawDesc = "" + "\tsubqueues\x18\t \x03(\v20.temporal.server.api.persistence.v1.SubqueueInfoR\tsubqueues\x12&\n" + "\x0fother_has_tasks\x18\n" + " \x01(\bR\rotherHasTasks\x12k\n" + - "\x15partition_scale_state\x18\v \x01(\v27.temporal.server.api.persistence.v1.PartitionScaleStateR\x13partitionScaleState\"\xc2\x03\n" + + "\x15partition_scale_state\x18\v \x01(\v27.temporal.server.api.persistence.v1.PartitionScaleStateR\x13partitionScaleState\x12}\n" + + "\x1bexclusive_worker_assignment\x18\f \x01(\v2=.temporal.server.api.persistence.v1.ExclusiveWorkerAssignmentR\x19exclusiveWorkerAssignment\"\x1b\n" + + "\x19ExclusiveWorkerAssignment\"\xc2\x03\n" + "\fSubqueueInfo\x12A\n" + "\x03key\x18\x01 \x01(\v2/.temporal.server.api.persistence.v1.SubqueueKeyR\x03key\x12\x1b\n" + "\tack_level\x18\x02 \x01(\x03R\backLevel\x12Q\n" + @@ -798,48 +848,50 @@ func file_temporal_server_api_persistence_v1_tasks_proto_rawDescGZIP() []byte { return file_temporal_server_api_persistence_v1_tasks_proto_rawDescData } -var file_temporal_server_api_persistence_v1_tasks_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_temporal_server_api_persistence_v1_tasks_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_temporal_server_api_persistence_v1_tasks_proto_goTypes = []any{ (*AllocatedTaskInfo)(nil), // 0: temporal.server.api.persistence.v1.AllocatedTaskInfo (*TaskInfo)(nil), // 1: temporal.server.api.persistence.v1.TaskInfo (*TaskQueueInfo)(nil), // 2: temporal.server.api.persistence.v1.TaskQueueInfo - (*SubqueueInfo)(nil), // 3: temporal.server.api.persistence.v1.SubqueueInfo - (*FairnessKeyCount)(nil), // 4: temporal.server.api.persistence.v1.FairnessKeyCount - (*SubqueueKey)(nil), // 5: temporal.server.api.persistence.v1.SubqueueKey - (*PartitionScaleState)(nil), // 6: temporal.server.api.persistence.v1.PartitionScaleState - (*SimplePartitionScalerState)(nil), // 7: temporal.server.api.persistence.v1.SimplePartitionScalerState - (*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp - (*v1.VectorClock)(nil), // 9: temporal.server.api.clock.v1.VectorClock - (*v11.TaskVersionDirective)(nil), // 10: temporal.server.api.taskqueue.v1.TaskVersionDirective - (*v12.Priority)(nil), // 11: temporal.api.common.v1.Priority - (v13.TaskQueueType)(0), // 12: temporal.api.enums.v1.TaskQueueType - (v13.TaskQueueKind)(0), // 13: temporal.api.enums.v1.TaskQueueKind - (*v11.FairLevel)(nil), // 14: temporal.server.api.taskqueue.v1.FairLevel - (*anypb.Any)(nil), // 15: google.protobuf.Any + (*ExclusiveWorkerAssignment)(nil), // 3: temporal.server.api.persistence.v1.ExclusiveWorkerAssignment + (*SubqueueInfo)(nil), // 4: temporal.server.api.persistence.v1.SubqueueInfo + (*FairnessKeyCount)(nil), // 5: temporal.server.api.persistence.v1.FairnessKeyCount + (*SubqueueKey)(nil), // 6: temporal.server.api.persistence.v1.SubqueueKey + (*PartitionScaleState)(nil), // 7: temporal.server.api.persistence.v1.PartitionScaleState + (*SimplePartitionScalerState)(nil), // 8: temporal.server.api.persistence.v1.SimplePartitionScalerState + (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp + (*v1.VectorClock)(nil), // 10: temporal.server.api.clock.v1.VectorClock + (*v11.TaskVersionDirective)(nil), // 11: temporal.server.api.taskqueue.v1.TaskVersionDirective + (*v12.Priority)(nil), // 12: temporal.api.common.v1.Priority + (v13.TaskQueueType)(0), // 13: temporal.api.enums.v1.TaskQueueType + (v13.TaskQueueKind)(0), // 14: temporal.api.enums.v1.TaskQueueKind + (*v11.FairLevel)(nil), // 15: temporal.server.api.taskqueue.v1.FairLevel + (*anypb.Any)(nil), // 16: google.protobuf.Any } var file_temporal_server_api_persistence_v1_tasks_proto_depIdxs = []int32{ 1, // 0: temporal.server.api.persistence.v1.AllocatedTaskInfo.data:type_name -> temporal.server.api.persistence.v1.TaskInfo - 8, // 1: temporal.server.api.persistence.v1.TaskInfo.create_time:type_name -> google.protobuf.Timestamp - 8, // 2: temporal.server.api.persistence.v1.TaskInfo.expiry_time:type_name -> google.protobuf.Timestamp - 9, // 3: temporal.server.api.persistence.v1.TaskInfo.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 10, // 4: temporal.server.api.persistence.v1.TaskInfo.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective - 11, // 5: temporal.server.api.persistence.v1.TaskInfo.priority:type_name -> temporal.api.common.v1.Priority - 12, // 6: temporal.server.api.persistence.v1.TaskQueueInfo.task_type:type_name -> temporal.api.enums.v1.TaskQueueType - 13, // 7: temporal.server.api.persistence.v1.TaskQueueInfo.kind:type_name -> temporal.api.enums.v1.TaskQueueKind - 8, // 8: temporal.server.api.persistence.v1.TaskQueueInfo.expiry_time:type_name -> google.protobuf.Timestamp - 8, // 9: temporal.server.api.persistence.v1.TaskQueueInfo.last_update_time:type_name -> google.protobuf.Timestamp - 3, // 10: temporal.server.api.persistence.v1.TaskQueueInfo.subqueues:type_name -> temporal.server.api.persistence.v1.SubqueueInfo - 6, // 11: temporal.server.api.persistence.v1.TaskQueueInfo.partition_scale_state:type_name -> temporal.server.api.persistence.v1.PartitionScaleState - 5, // 12: temporal.server.api.persistence.v1.SubqueueInfo.key:type_name -> temporal.server.api.persistence.v1.SubqueueKey - 14, // 13: temporal.server.api.persistence.v1.SubqueueInfo.fair_ack_level:type_name -> temporal.server.api.taskqueue.v1.FairLevel - 14, // 14: temporal.server.api.persistence.v1.SubqueueInfo.fair_max_read_level:type_name -> temporal.server.api.taskqueue.v1.FairLevel - 4, // 15: temporal.server.api.persistence.v1.SubqueueInfo.top_k_fairness_counts:type_name -> temporal.server.api.persistence.v1.FairnessKeyCount - 15, // 16: temporal.server.api.persistence.v1.PartitionScaleState.private_scaler_state:type_name -> google.protobuf.Any - 17, // [17:17] is the sub-list for method output_type - 17, // [17:17] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 9, // 1: temporal.server.api.persistence.v1.TaskInfo.create_time:type_name -> google.protobuf.Timestamp + 9, // 2: temporal.server.api.persistence.v1.TaskInfo.expiry_time:type_name -> google.protobuf.Timestamp + 10, // 3: temporal.server.api.persistence.v1.TaskInfo.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 11, // 4: temporal.server.api.persistence.v1.TaskInfo.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective + 12, // 5: temporal.server.api.persistence.v1.TaskInfo.priority:type_name -> temporal.api.common.v1.Priority + 13, // 6: temporal.server.api.persistence.v1.TaskQueueInfo.task_type:type_name -> temporal.api.enums.v1.TaskQueueType + 14, // 7: temporal.server.api.persistence.v1.TaskQueueInfo.kind:type_name -> temporal.api.enums.v1.TaskQueueKind + 9, // 8: temporal.server.api.persistence.v1.TaskQueueInfo.expiry_time:type_name -> google.protobuf.Timestamp + 9, // 9: temporal.server.api.persistence.v1.TaskQueueInfo.last_update_time:type_name -> google.protobuf.Timestamp + 4, // 10: temporal.server.api.persistence.v1.TaskQueueInfo.subqueues:type_name -> temporal.server.api.persistence.v1.SubqueueInfo + 7, // 11: temporal.server.api.persistence.v1.TaskQueueInfo.partition_scale_state:type_name -> temporal.server.api.persistence.v1.PartitionScaleState + 3, // 12: temporal.server.api.persistence.v1.TaskQueueInfo.exclusive_worker_assignment:type_name -> temporal.server.api.persistence.v1.ExclusiveWorkerAssignment + 6, // 13: temporal.server.api.persistence.v1.SubqueueInfo.key:type_name -> temporal.server.api.persistence.v1.SubqueueKey + 15, // 14: temporal.server.api.persistence.v1.SubqueueInfo.fair_ack_level:type_name -> temporal.server.api.taskqueue.v1.FairLevel + 15, // 15: temporal.server.api.persistence.v1.SubqueueInfo.fair_max_read_level:type_name -> temporal.server.api.taskqueue.v1.FairLevel + 5, // 16: temporal.server.api.persistence.v1.SubqueueInfo.top_k_fairness_counts:type_name -> temporal.server.api.persistence.v1.FairnessKeyCount + 16, // 17: temporal.server.api.persistence.v1.PartitionScaleState.private_scaler_state:type_name -> google.protobuf.Any + 18, // [18:18] is the sub-list for method output_type + 18, // [18:18] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_temporal_server_api_persistence_v1_tasks_proto_init() } @@ -853,7 +905,7 @@ func file_temporal_server_api_persistence_v1_tasks_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_temporal_server_api_persistence_v1_tasks_proto_rawDesc), len(file_temporal_server_api_persistence_v1_tasks_proto_rawDesc)), NumEnums: 0, - NumMessages: 8, + NumMessages: 9, NumExtensions: 0, NumServices: 0, }, diff --git a/client/matching/client_test.go b/client/matching/client_test.go index ab3925b4408..db2f2b49fb6 100644 --- a/client/matching/client_test.go +++ b/client/matching/client_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/stretchr/testify/require" + deploymentpb "go.temporal.io/api/deployment/v1" enumspb "go.temporal.io/api/enums/v1" "go.temporal.io/api/serviceerror" sessionpb "go.temporal.io/api/session/v1" @@ -16,6 +17,7 @@ import ( taskqueuespb "go.temporal.io/server/api/taskqueue/v1" "go.temporal.io/server/common/dynamicconfig" "go.temporal.io/server/common/log" + "go.temporal.io/server/common/testing/protorequire" "go.temporal.io/server/common/tqid" "go.uber.org/mock/gomock" "google.golang.org/grpc" @@ -239,25 +241,39 @@ func TestDoPollWorkflowTaskQueue_LoadBalancedRoot(t *testing.T) { client := newTestClient(cache, loadBalancer) request := &matchingservice.PollWorkflowTaskQueueRequest{ NamespaceId: testNamespaceID, + PollerId: "poller-id", PollRequest: &workflowservice.PollWorkflowTaskQueueRequest{ - TaskQueue: &taskqueuepb.TaskQueue{Name: testTaskQueueName}, - Identity: "worker", - SessionOptions: &sessionpb.WorkerSessionOptions{}, + TaskQueue: &taskqueuepb.TaskQueue{Name: testTaskQueueName}, + Identity: "worker", + SessionOptions: &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "session-identity"}, + }}, + DeploymentOptions: &deploymentpb.WorkerDeploymentOptions{ + DeploymentName: "deployment", BuildId: "v1", WorkerVersioningMode: enumspb.WORKER_VERSIONING_MODE_VERSIONED, + }, }, + Conditions: &matchingservice.PollConditions{NoWait: true}, } response := &matchingservice.PollWorkflowTaskQueueResponse{} serviceClient.EXPECT().PollWorkflowTaskQueue(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, outgoing *matchingservice.PollWorkflowTaskQueueRequest, _ ...grpc.CallOption) (*matchingservice.PollWorkflowTaskQueueResponse, error) { + func(ctx context.Context, outgoing *matchingservice.PollWorkflowTaskQueueRequest, _ ...grpc.CallOption) (*matchingservice.PollWorkflowTaskQueueResponse, error) { require.Equal(t, selectedPartition.RpcName(), outgoing.GetPollRequest().GetTaskQueue().GetName()) require.Equal(t, "worker", outgoing.GetPollRequest().GetIdentity()) + require.Equal(t, request.GetPollerId(), outgoing.GetPollerId()) + protorequire.ProtoEqual(t, request.GetPollRequest().GetSessionOptions(), outgoing.GetPollRequest().GetSessionOptions()) require.Same(t, request.GetPollRequest().GetSessionOptions(), outgoing.GetPollRequest().GetSessionOptions()) + protorequire.ProtoEqual(t, request.GetPollRequest().GetDeploymentOptions(), outgoing.GetPollRequest().GetDeploymentOptions()) + protorequire.ProtoEqual(t, request.GetConditions(), outgoing.GetConditions()) + outgoingMetadata, ok := metadata.FromOutgoingContext(ctx) + require.True(t, ok) + require.Equal(t, []string{"worker"}, outgoingMetadata.Get("acquisition-trace")) return response, nil }, ) actual, err := client.doPollWorkflowTaskQueue( - context.Background(), + metadata.NewOutgoingContext(t.Context(), metadata.Pairs("acquisition-trace", "worker")), taskQueue.RootPartition(), true, PartitionCounts{Read: 4, Write: 4}, diff --git a/common/dynamicconfig/constants.go b/common/dynamicconfig/constants.go index 02eb534fb3c..64fe3122143 100644 --- a/common/dynamicconfig/constants.go +++ b/common/dynamicconfig/constants.go @@ -1227,6 +1227,11 @@ so forwarding by endpoint ID will not work out of the box.`, to allow waiting on the "Accepted" lifecycle stage.`, ) + EnableExclusiveWorkerSessions = NewNamespaceBoolSetting( + "frontend.enableExclusiveWorkerSessions", + false, + `EnableExclusiveWorkerSessions allows workflow and activity polls with single-session worker options.`, + ) FrontendEnableWorkerVersioningDataAPIs = NewNamespaceBoolSetting( "frontend.workerVersioningDataAPIs", false, diff --git a/common/persistence/cassandra/matching_task_store_queue.go b/common/persistence/cassandra/matching_task_store_queue.go index ce3960a62df..e17fed403a9 100644 --- a/common/persistence/cassandra/matching_task_store_queue.go +++ b/common/persistence/cassandra/matching_task_store_queue.go @@ -13,6 +13,7 @@ import ( p "go.temporal.io/server/common/persistence" "go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql" "go.temporal.io/server/common/primitives/timestamp" + "google.golang.org/protobuf/types/known/timestamppb" ) // matchingTaskVersion represents the task schema version @@ -188,10 +189,7 @@ func (d *taskQueueStore) UpdateTaskQueue( if d.version == matchingTaskVersion1 && request.TaskQueueKind == enumspb.TASK_QUEUE_KIND_STICKY { // V1 TTL logic - only applies to V1 - if request.ExpiryTime == nil { - return nil, serviceerror.NewInternal("ExpiryTime cannot be nil for sticky task queue") - } - expiryTTL := min(convert.Int64Ceil(time.Until(timestamp.TimeValue(request.ExpiryTime)).Seconds()), maxCassandraTTL) + expiryTTL := taskQueueMetadataTTL(request.ExpiryTime, time.Now()) batch := d.Session.NewBatch(gocql.LoggedBatch).WithContext(ctx) batch.Query(switchTasksTable(templateUpdateTaskQueueQueryWithTTLPart1, d.version), @@ -251,6 +249,13 @@ func (d *taskQueueStore) UpdateTaskQueue( return &p.UpdateTaskQueueResponse{}, nil } +func taskQueueMetadataTTL(expiryTime *timestamppb.Timestamp, now time.Time) int64 { + if expiryTime == nil { + return 0 + } + return min(convert.Int64Ceil(timestamp.TimeValue(expiryTime).Sub(now).Seconds()), maxCassandraTTL) +} + func (d *taskQueueStore) ListTaskQueue( _ context.Context, _ *p.ListTaskQueueRequest, diff --git a/common/persistence/cassandra/matching_task_store_queue_test.go b/common/persistence/cassandra/matching_task_store_queue_test.go new file mode 100644 index 00000000000..40fe6e45197 --- /dev/null +++ b/common/persistence/cassandra/matching_task_store_queue_test.go @@ -0,0 +1,34 @@ +package cassandra + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" +) + +/* +TestTaskQueueMetadataTTL tests how metadata expiry becomes a Cassandra TTL: +1. Verify omitted expiry produces no TTL and a future expiry preserves its remaining seconds. +2. Verify fractional seconds round up and oversized TTL values are capped. +*/ +func TestTaskQueueMetadataTTL(t *testing.T) { + t.Parallel() + now := time.Unix(1000, 0) + for _, tc := range []struct { + name string + expiry *timestamppb.Timestamp + ttl int64 + }{ + {name: "no expiry has no ttl"}, + {name: "ordinary sticky keeps ttl", expiry: timestamppb.New(now.Add(time.Hour)), ttl: 3600}, + {name: "fractional ttl rounds up", expiry: timestamppb.New(now.Add(time.Second + time.Millisecond)), ttl: 2}, + {name: "ttl is capped", expiry: timestamppb.New(now.Add(time.Duration(maxCassandraTTL+3600) * time.Second)), ttl: maxCassandraTTL}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tc.ttl, taskQueueMetadataTTL(tc.expiry, now)) + }) + } +} diff --git a/common/persistence/task_manager.go b/common/persistence/task_manager.go index 680be2d0012..d60f8160549 100644 --- a/common/persistence/task_manager.go +++ b/common/persistence/task_manager.go @@ -49,7 +49,9 @@ func (m *taskManagerImpl) CreateTaskQueue( if taskQueueInfo.LastUpdateTime == nil { panic("CreateTaskQueue encountered LastUpdateTime not set") } - if taskQueueInfo.ExpiryTime == nil && taskQueueInfo.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY { + // TODO (Shivam): Revisit this if and when we add a Placement concept on Workflow Start requests + // and if that requires assignment before queue creation. + if taskQueueInfo.GetExpiryTime() == nil && taskQueueInfo.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY { panic("CreateTaskQueue encountered ExpiryTime not set for sticky task queue") } taskQueueInfoBlob, err := m.serializer.TaskQueueInfoToBlob(taskQueueInfo) @@ -80,7 +82,10 @@ func (m *taskManagerImpl) UpdateTaskQueue( if taskQueueInfo.LastUpdateTime == nil { panic("UpdateTaskQueue encountered LastUpdateTime not set") } - if taskQueueInfo.ExpiryTime == nil && taskQueueInfo.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY { + expiryTime := taskQueueInfo.GetExpiryTime() + if taskQueueInfo.GetExclusiveWorkerAssignment() != nil { + expiryTime = nil + } else if expiryTime == nil && taskQueueInfo.GetKind() == enumspb.TASK_QUEUE_KIND_STICKY { panic("UpdateTaskQueue encountered ExpiryTime not set for sticky task queue") } taskQueueInfoBlob, err := m.serializer.TaskQueueInfoToBlob(taskQueueInfo) @@ -96,7 +101,7 @@ func (m *taskManagerImpl) UpdateTaskQueue( TaskQueueInfo: taskQueueInfoBlob, TaskQueueKind: request.TaskQueueInfo.GetKind(), - ExpiryTime: taskQueueInfo.ExpiryTime, + ExpiryTime: expiryTime, PrevRangeID: request.PrevRangeID, } diff --git a/common/persistence/task_manager_single_session_test.go b/common/persistence/task_manager_single_session_test.go new file mode 100644 index 00000000000..8713d88f1fb --- /dev/null +++ b/common/persistence/task_manager_single_session_test.go @@ -0,0 +1,120 @@ +package persistence_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + enumspb "go.temporal.io/api/enums/v1" + "go.temporal.io/api/serviceerror" + persistencespb "go.temporal.io/server/api/persistence/v1" + "go.temporal.io/server/common/persistence" + "go.temporal.io/server/common/persistence/mock" + "go.temporal.io/server/common/persistence/serialization" + "go.temporal.io/server/common/testing/protorequire" + "go.temporal.io/server/common/testing/testvars" + "go.uber.org/mock/gomock" + "google.golang.org/protobuf/types/known/timestamppb" +) + +/* +TestTaskManagerSingleSessionMetadata tests how queue metadata reaches the persistence store: +1. Create and update assigned, ordinary, and activity sticky queue records. +2. Verify metadata survives serialization and only assigned updates clear backend expiry. +3. Reject missing required expiry values and propagate store errors. +*/ +func TestTaskManagerSingleSessionMetadata(t *testing.T) { + t.Parallel() + for _, operation := range []string{"create", "update"} { + t.Run(operation, func(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + assigned bool + taskType enumspb.TaskQueueType + expiry bool + invalid bool + invalidCreate bool + storeError bool + }{ + {name: "assigned workflow without expiry", assigned: true, taskType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, invalidCreate: true}, + {name: "assigned workflow clears backend expiry", assigned: true, taskType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, expiry: true}, + {name: "ordinary sticky expires", taskType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, expiry: true}, + {name: "activity expiry is preserved", taskType: enumspb.TASK_QUEUE_TYPE_ACTIVITY, expiry: true}, + {name: "unassigned requires expiry", taskType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, invalid: true}, + {name: "activity requires expiry", taskType: enumspb.TASK_QUEUE_TYPE_ACTIVITY, invalid: true}, + {name: "store failure", taskType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, expiry: true, storeError: true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + invalid := tc.invalid || (operation == "create" && tc.invalidCreate) + tv := testvars.New(t) + store := mock.NewMockTaskStore(gomock.NewController(t)) + serializer := serialization.NewSerializer() + manager := persistence.NewTaskManager(store, serializer) + info := &persistencespb.TaskQueueInfo{ + NamespaceId: tv.NamespaceID().String(), + Name: tv.StickyTaskQueue().GetName(), + TaskType: tc.taskType, + Kind: enumspb.TASK_QUEUE_KIND_STICKY, + LastUpdateTime: timestamppb.New(time.Unix(42, 0)), + } + if tc.assigned { + info.ExclusiveWorkerAssignment = &persistencespb.ExclusiveWorkerAssignment{} + } + if tc.expiry { + info.ExpiryTime = timestamppb.New(time.Unix(3600, 0)) + } + var storeErr error + if tc.storeError { + storeErr = serviceerror.NewUnavailable("store unavailable") + } + if !invalid { + if operation == "create" { + store.EXPECT().CreateTaskQueue(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, req *persistence.InternalCreateTaskQueueRequest) error { + restored, err := serializer.TaskQueueInfoFromBlob(req.TaskQueueInfo) + require.NoError(t, err) + protorequire.ProtoEqual(t, info, restored) + protorequire.ProtoEqual(t, info.GetExpiryTime(), req.ExpiryTime) + return storeErr + }) + } else { + store.EXPECT().UpdateTaskQueue(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, req *persistence.InternalUpdateTaskQueueRequest) (*persistence.UpdateTaskQueueResponse, error) { + restored, err := serializer.TaskQueueInfoFromBlob(req.TaskQueueInfo) + require.NoError(t, err) + protorequire.ProtoEqual(t, info, restored) + if tc.assigned { + require.Nil(t, req.ExpiryTime) + } else { + protorequire.ProtoEqual(t, info.GetExpiryTime(), req.ExpiryTime) + } + return &persistence.UpdateTaskQueueResponse{}, storeErr + }) + } + } + invoke := func() { + var err error + if operation == "create" { + _, err = manager.CreateTaskQueue(t.Context(), &persistence.CreateTaskQueueRequest{RangeID: 2, TaskQueueInfo: info}) + } else { + _, err = manager.UpdateTaskQueue(t.Context(), &persistence.UpdateTaskQueueRequest{RangeID: 2, PrevRangeID: 1, TaskQueueInfo: info}) + } + if tc.storeError { + require.ErrorIs(t, err, storeErr) + } else { + require.NoError(t, err) + } + } + if invalid { + require.Panics(t, invoke) + } else { + invoke() + } + }) + } + }) + } +} diff --git a/proto/internal/temporal/server/api/historyservice/v1/request_response.proto b/proto/internal/temporal/server/api/historyservice/v1/request_response.proto index 8696241261d..a272301bbf1 100644 --- a/proto/internal/temporal/server/api/historyservice/v1/request_response.proto +++ b/proto/internal/temporal/server/api/historyservice/v1/request_response.proto @@ -176,6 +176,8 @@ message GetMutableStateResponse { temporal.server.api.history.v1.TransientWorkflowTaskInfo transient_or_speculative_tasks = 26; // Nexus operation context for this workflow, used by workers to select payload serialization. temporal.api.nexus.v1.PropagatedSerializationContext nexus_serialization_context = 27; + // Session assignment and worker destination for this workflow. + temporal.server.api.persistence.v1.SessionState session_state = 28; } message PollMutableStateRequest { diff --git a/proto/internal/temporal/server/api/persistence/v1/executions.proto b/proto/internal/temporal/server/api/persistence/v1/executions.proto index 6de6f806a95..ce75624e2d8 100644 --- a/proto/internal/temporal/server/api/persistence/v1/executions.proto +++ b/proto/internal/temporal/server/api/persistence/v1/executions.proto @@ -328,6 +328,18 @@ message WorkflowExecutionInfo { // Nexus operation context retained across runs so workers can select payload serialization. temporal.api.nexus.v1.PropagatedSerializationContext propagated_nexus_serialization_context = 117; + + // Session binding retained for the lifetime of this workflow execution. + SessionState session_state = 118; +} + +// Note: This is experimental and could change in the near future. +message SessionState { + // Whether this session is mapped strongly to an individual worker. Exclusive sessions also remain at their assigned address. + bool exclusive_worker = 1; + // Stable sticky task queue name, mirrored by WorkflowExecutionInfo.sticky_task_queue. + // This identifies the session across worker process replacements. + string worker_queue = 2; } message TimeSkippingInfo { diff --git a/proto/internal/temporal/server/api/persistence/v1/tasks.proto b/proto/internal/temporal/server/api/persistence/v1/tasks.proto index ea6388e3433..1da981c67df 100644 --- a/proto/internal/temporal/server/api/persistence/v1/tasks.proto +++ b/proto/internal/temporal/server/api/persistence/v1/tasks.proto @@ -76,8 +76,16 @@ message TaskQueueInfo { // Dynamic partition scaling state (root only): PartitionScaleState partition_scale_state = 11; + + ExclusiveWorkerAssignment exclusive_worker_assignment = 12; } +// Presence records that an exclusive worker's sticky workflow queue has acquired a workflow. +// Retained after workflow completion and worker replacement to prevent reuse. +// Note: This message may grow in the recent future to include more information such as the workflowID +// for added optimizations. +message ExclusiveWorkerAssignment {} + message SubqueueInfo { // Key is the information used by a splitting algorithm to decide which tasks should go in // this subqueue. It should not change after being registered in TaskQueueInfo. diff --git a/service/frontend/errors.go b/service/frontend/errors.go index f796300a25d..b0ba0d0553c 100644 --- a/service/frontend/errors.go +++ b/service/frontend/errors.go @@ -115,6 +115,8 @@ var ( errWorkerVersioningV2_0NotAllowed = serviceerror.NewPermissionDenied("Worker versioning v0.2 (Rules-based, deprecated) is disabled on this namespace.", "") errWorkerVersioningWorkflowAPIsNotAllowed = serviceerror.NewPermissionDenied("Worker versioning in workflow progress APIs is disabled on this namespace.", "") + errExclusiveWorkerSessionsDisabled = serviceerror.NewFailedPrecondition("Exclusive-worker sessions are disabled for this namespace.") + errListHistoryTasksNotAllowed = serviceerror.NewPermissionDenied("ListHistoryTasks feature is disabled on this cluster.", "") errWorkflowTimeSkippingNotEnabled = workflow.ErrWorkflowTimeSkippingNotEnabled diff --git a/service/frontend/service.go b/service/frontend/service.go index 3bd8c991a97..36a86d41b64 100644 --- a/service/frontend/service.go +++ b/service/frontend/service.go @@ -247,7 +247,8 @@ type Config struct { LogAllReqErrors dynamicconfig.BoolPropertyFnWithNamespaceFilter - EnableEagerWorkflowStart dynamicconfig.BoolPropertyFnWithNamespaceFilter + EnableEagerWorkflowStart dynamicconfig.BoolPropertyFnWithNamespaceFilter + EnableExclusiveWorkerSessions dynamicconfig.BoolPropertyFnWithNamespaceFilter WorkflowRulesAPIsEnabled dynamicconfig.BoolPropertyFnWithNamespaceFilter MaxWorkflowRulesPerNamespace dynamicconfig.IntPropertyFnWithNamespaceFilter @@ -449,6 +450,7 @@ func NewConfig( HistoryHostErrorPercentage: dynamicconfig.HistoryHostErrorPercentage.Get(dc), LogAllReqErrors: dynamicconfig.LogAllReqErrors.Get(dc), EnableEagerWorkflowStart: dynamicconfig.EnableEagerWorkflowStart.Get(dc), + EnableExclusiveWorkerSessions: dynamicconfig.EnableExclusiveWorkerSessions.Get(dc), WorkflowRulesAPIsEnabled: dynamicconfig.WorkflowRulesAPIsEnabled.Get(dc), MaxWorkflowRulesPerNamespace: dynamicconfig.MaxWorkflowRulesPerNamespace.Get(dc), WorkerHeartbeatsEnabled: dynamicconfig.WorkerHeartbeatsEnabled.Get(dc), diff --git a/service/frontend/worker_session_options_test.go b/service/frontend/worker_session_options_test.go index 30bcff04e1a..8d0649fe5cf 100644 --- a/service/frontend/worker_session_options_test.go +++ b/service/frontend/worker_session_options_test.go @@ -13,27 +13,34 @@ import ( func TestValidateWorkerSessionOptions(t *testing.T) { t.Parallel() for _, tc := range []struct { - name string - kind enumspb.TaskQueueKind - options *sessionpb.WorkerSessionOptions - invalid bool - unsupported bool + name string + kind enumspb.TaskQueueKind + options *sessionpb.WorkerSessionOptions + singleSession bool + stableIdentity string + invalid bool }{ {name: "regular poll", kind: enumspb.TASK_QUEUE_KIND_NORMAL}, {name: "ordinary sticky poll", kind: enumspb.TASK_QUEUE_KIND_STICKY}, {name: "empty options on sticky poll", kind: enumspb.TASK_QUEUE_KIND_STICKY, options: &sessionpb.WorkerSessionOptions{}}, {name: "options on normal queue", kind: enumspb.TASK_QUEUE_KIND_NORMAL, options: &sessionpb.WorkerSessionOptions{}, invalid: true}, - {name: "unsupported isolation", kind: enumspb.TASK_QUEUE_KIND_STICKY, options: &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "session"}}}, unsupported: true}, + {name: "valid single session", kind: enumspb.TASK_QUEUE_KIND_STICKY, singleSession: true, stableIdentity: "queue"}, + {name: "empty stable identity", kind: enumspb.TASK_QUEUE_KIND_STICKY, singleSession: true, invalid: true}, + {name: "sticky queue differs from identity", kind: enumspb.TASK_QUEUE_KIND_STICKY, singleSession: true, stableIdentity: "session", invalid: true}, + {name: "single session on normal queue", kind: enumspb.TASK_QUEUE_KIND_NORMAL, singleSession: true, stableIdentity: "queue", invalid: true}, } { t.Run(tc.name, func(t *testing.T) { t.Parallel() - err := validateWorkerSessionOptions(tc.options, &taskqueuepb.TaskQueue{Name: "queue", Kind: tc.kind}) - switch { - case tc.invalid: + options := tc.options + if tc.singleSession { + options = &sessionpb.WorkerSessionOptions{ + Admission: &sessionpb.WorkerSessionOptions_SingleSession{SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: tc.stableIdentity}}, + } + } + err := validateWorkerSessionOptions(options, &taskqueuepb.TaskQueue{Name: "queue", Kind: tc.kind}) + if tc.invalid { require.ErrorAs(t, err, new(*serviceerror.InvalidArgument)) - case tc.unsupported: - require.ErrorAs(t, err, new(*serviceerror.Unimplemented)) - default: + } else { require.NoError(t, err) } }) diff --git a/service/frontend/workflow_handler.go b/service/frontend/workflow_handler.go index e7df561c9b4..8dac944ea4b 100644 --- a/service/frontend/workflow_handler.go +++ b/service/frontend/workflow_handler.go @@ -1147,6 +1147,9 @@ func (wh *WorkflowHandler) PollWorkflowTaskQueue(ctx context.Context, request *w if err := validateWorkerSessionOptions(request.GetSessionOptions(), request.GetTaskQueue()); err != nil { return nil, err } + if err := wh.checkExclusiveWorkerSessionsEnabled(request.GetNamespace(), request.GetSessionOptions()); err != nil { + return nil, err + } callTime := time.Now().UTC() @@ -1405,6 +1408,9 @@ func (wh *WorkflowHandler) PollActivityTaskQueue(ctx context.Context, request *w if err := validateWorkerSessionOptions(request.GetSessionOptions(), request.GetTaskQueue()); err != nil { return nil, err } + if err := wh.checkExclusiveWorkerSessionsEnabled(request.GetNamespace(), request.GetSessionOptions()); err != nil { + return nil, err + } if len(request.GetIdentity()) > wh.config.MaxIDLengthLimit() { return nil, errIdentityTooLong @@ -7975,6 +7981,13 @@ func (wh *WorkflowHandler) PollWorkflowExecutionTimeSkipping(ctx context.Context return histResp.GetResponse(), nil } +func (wh *WorkflowHandler) checkExclusiveWorkerSessionsEnabled(namespaceName string, options *sessionpb.WorkerSessionOptions) error { + if options.GetSingleSession() != nil && !wh.config.EnableExclusiveWorkerSessions(namespaceName) { + return errExclusiveWorkerSessionsDisabled + } + return nil +} + func validateWorkerSessionOptions(options *sessionpb.WorkerSessionOptions, taskQueue *taskqueuepb.TaskQueue) error { if options == nil { return nil @@ -7982,8 +7995,14 @@ func validateWorkerSessionOptions(options *sessionpb.WorkerSessionOptions, taskQ if taskQueue.GetKind() != enumspb.TASK_QUEUE_KIND_STICKY { return serviceerror.NewInvalidArgument("Worker session options require a sticky task queue") } - if options.GetAdmission() != nil { - return serviceerror.NewUnimplemented("Single-session isolation is not supported") + if singleSession := options.GetSingleSession(); singleSession != nil { + stableIdentity := singleSession.GetStableIdentity() + if stableIdentity == "" { + return serviceerror.NewInvalidArgument("single-session stable identity is required") + } + if taskQueue.GetName() != stableIdentity { + return serviceerror.NewInvalidArgument("single-session sticky queue must match stable identity") + } } return nil } diff --git a/service/frontend/workflow_handler_test.go b/service/frontend/workflow_handler_test.go index c8a68a2dc3a..6fdba8f7bc2 100644 --- a/service/frontend/workflow_handler_test.go +++ b/service/frontend/workflow_handler_test.go @@ -6919,3 +6919,90 @@ func (s *WorkflowHandlerSuite) TestStickyActivityPollForwardedToMatching() { _, err := wh.PollActivityTaskQueue(ctx, request) s.Require().NoError(err) } + +/* +TestExclusiveWorkerSessionPolls tests the namespace gate for workflow and activity polls: +1. Ordinary and nonexclusive polls continue while exclusive sessions are disabled. +2. Exclusive polls are rejected before Matching when the namespace flag is off. +3. Enabling the flag admits exclusive polls and leaves the original requests unchanged. +*/ +func (s *WorkflowHandlerSuite) TestExclusiveWorkerSessionPolls() { + singleSession := &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "sticky-worker"}, + }} + for _, taskType := range []enumspb.TaskQueueType{enumspb.TASK_QUEUE_TYPE_WORKFLOW, enumspb.TASK_QUEUE_TYPE_ACTIVITY} { + s.Run(taskType.String(), func() { + for _, tc := range []struct { + name string + namespace string + kind enumspb.TaskQueueKind + options *sessionpb.WorkerSessionOptions + disabled bool + }{ + {name: "ordinary normal poll", namespace: "disabled-namespace", kind: enumspb.TASK_QUEUE_KIND_NORMAL}, + {name: "ordinary sticky poll", namespace: "disabled-namespace", kind: enumspb.TASK_QUEUE_KIND_STICKY}, + {name: "nonexclusive session poll", namespace: "disabled-namespace", kind: enumspb.TASK_QUEUE_KIND_STICKY, options: &sessionpb.WorkerSessionOptions{}}, + {name: "exclusive session disabled by default", namespace: "disabled-namespace", kind: enumspb.TASK_QUEUE_KIND_STICKY, options: singleSession, disabled: true}, + {name: "exclusive session enabled for namespace", namespace: "enabled-namespace", kind: enumspb.TASK_QUEUE_KIND_STICKY, options: singleSession}, + } { + s.Run(tc.name, func() { + client := dc.NewMemoryClient() + client.OverrideSetting(dc.EnableExclusiveWorkerSessions, []dc.ConstrainedValue{{ + Constraints: dc.Constraints{Namespace: "enabled-namespace"}, Value: true, + }}) + config := NewConfig(dc.NewCollection(client, log.NewNoopLogger()), numHistoryShards) + wh := s.getWorkflowHandler(config) + ctx, cancel := context.WithTimeout(s.T().Context(), time.Minute) + defer cancel() + taskQueue := &taskqueuepb.TaskQueue{Name: "sticky-worker", Kind: tc.kind} + if tc.kind == enumspb.TASK_QUEUE_KIND_STICKY { + taskQueue.NormalName = "normal-queue" + } + var err error + if taskType == enumspb.TASK_QUEUE_TYPE_WORKFLOW { + request := &workflowservice.PollWorkflowTaskQueueRequest{ + Namespace: tc.namespace, TaskQueue: taskQueue, SessionOptions: common.CloneProto(tc.options), + } + original := common.CloneProto(request) + if !tc.disabled { + ns := namespace.NewLocalNamespaceForTest( + &persistencespb.NamespaceInfo{Id: s.testNamespaceID.String(), Name: tc.namespace}, + &persistencespb.NamespaceConfig{}, cluster.TestCurrentClusterName, + ) + s.mockNamespaceCache.EXPECT().GetNamespace(namespace.Name(tc.namespace)).Return(ns, nil) + s.mockMatchingClient.EXPECT().PollWorkflowTaskQueue(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, requestToMatching *matchingservice.PollWorkflowTaskQueueRequest, _ ...grpc.CallOption) (*matchingservice.PollWorkflowTaskQueueResponse, error) { + s.Require().Same(request, requestToMatching.GetPollRequest()) + return &matchingservice.PollWorkflowTaskQueueResponse{}, nil + }) + } + _, err = wh.PollWorkflowTaskQueue(ctx, request) + s.ProtoEqual(original, request) + } else { + request := &workflowservice.PollActivityTaskQueueRequest{ + Namespace: tc.namespace, TaskQueue: taskQueue, SessionOptions: common.CloneProto(tc.options), + } + original := common.CloneProto(request) + if !tc.disabled { + s.mockNamespaceCache.EXPECT().GetNamespaceID(namespace.Name(tc.namespace)).Return(s.testNamespaceID, nil) + s.mockMatchingClient.EXPECT().PollActivityTaskQueue(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, requestToMatching *matchingservice.PollActivityTaskQueueRequest, _ ...grpc.CallOption) (*matchingservice.PollActivityTaskQueueResponse, error) { + s.Require().Same(request, requestToMatching.GetPollRequest()) + return &matchingservice.PollActivityTaskQueueResponse{}, nil + }) + } + _, err = wh.PollActivityTaskQueue(ctx, request) + s.ProtoEqual(original, request) + } + if tc.disabled { + var disabled *serviceerror.FailedPrecondition + s.Require().ErrorAs(err, &disabled) + s.Require().ErrorContains(err, "Exclusive-worker sessions are disabled for this namespace") + } else { + s.Require().NoError(err) + } + }) + } + }) + } +} diff --git a/service/history/api/get_workflow_util.go b/service/history/api/get_workflow_util.go index 05b6e100192..ff05fcf3637 100644 --- a/service/history/api/get_workflow_util.go +++ b/service/history/api/get_workflow_util.go @@ -405,6 +405,7 @@ func MutableStateToGetResponse( WorkflowState: workflowState, WorkflowStatus: workflowStatus, IsStickyTaskQueueEnabled: mutableState.IsStickyTaskQueueSet(), + SessionState: common.CloneProto(executionInfo.GetSessionState()), VersionHistories: versionhistory.CopyVersionHistories( mutableState.GetExecutionInfo().GetVersionHistories(), ), diff --git a/service/history/api/queryworkflow/api.go b/service/history/api/queryworkflow/api.go index b0b85a8daae..3f91a3618c6 100644 --- a/service/history/api/queryworkflow/api.go +++ b/service/history/api/queryworkflow/api.go @@ -368,9 +368,11 @@ func queryDirectlyThroughMatching( false, ) - if msResp.GetIsStickyTaskQueueEnabled() && + exclusiveWorker := msResp.GetSessionState().GetExclusiveWorker() + // Queries for exclusive workers must not fall back to the normal task queue. + if exclusiveWorker || (msResp.GetIsStickyTaskQueueEnabled() && len(msResp.GetStickyTaskQueue().GetName()) != 0 && - shard.GetConfig().EnableStickyQuery(queryRequest.GetNamespace()) { + shard.GetConfig().EnableStickyQuery(queryRequest.GetNamespace())) { stickyMatchingRequest := &matchingservice.QueryWorkflowRequest{ NamespaceId: namespaceID, @@ -382,7 +384,17 @@ func queryDirectlyThroughMatching( // using a clean new context in case customer provide a context which has // a really short deadline, causing we clear the stickiness - stickyContext, cancel := rpc.ResetContextTimeout(ctx, timestamp.DurationValue(msResp.GetStickyTaskQueueScheduleToStartTimeout())) + // Note: For session based queries, the timeout used is the timeout provided by the caller itself and + // not the default sticky query timeout. This is because unlike the non-session query, the session based query + // is not dispatched to the normal queue on timeout. + // TODO (Shivam): Think about how session moves shall be done with queries that are dispatched via matching. + var stickyContext context.Context + var cancel context.CancelFunc + if exclusiveWorker { + stickyContext, cancel = context.WithCancel(ctx) + } else { + stickyContext, cancel = rpc.ResetContextTimeout(ctx, timestamp.DurationValue(msResp.GetStickyTaskQueueScheduleToStartTimeout())) + } stickyStartTime := time.Now().UTC() matchingResp, err := rawMatchingClient.QueryWorkflow(stickyContext, stickyMatchingRequest) metrics.DirectQueryDispatchStickyLatency.With(metricsHandler).Record(time.Since(stickyStartTime)) @@ -395,6 +407,10 @@ func queryDirectlyThroughMatching( QueryRejected: matchingResp.GetQueryRejected(), }}, nil } + if exclusiveWorker { + // Return errors without clearing stickiness if single-session is enabled + return nil, err + } if !common.IsContextDeadlineExceededErr(err) && !common.IsContextCanceledErr(err) && !common.IsStickyWorkerUnavailable(err) { return nil, err } diff --git a/service/history/api/recordworkflowtaskstarted/api.go b/service/history/api/recordworkflowtaskstarted/api.go index c38fde1e9d8..4d9caf5ddf4 100644 --- a/service/history/api/recordworkflowtaskstarted/api.go +++ b/service/history/api/recordworkflowtaskstarted/api.go @@ -12,6 +12,7 @@ import ( taskqueuepb "go.temporal.io/api/taskqueue/v1" enumsspb "go.temporal.io/server/api/enums/v1" "go.temporal.io/server/api/historyservice/v1" + persistencespb "go.temporal.io/server/api/persistence/v1" tokenspb "go.temporal.io/server/api/token/v1" "go.temporal.io/server/common" "go.temporal.io/server/common/definition" @@ -78,6 +79,16 @@ func Invoke( return nil, serviceerrors.NewObsoleteMatchingTask("Workflow task stamp mismatch") } + singleSession := req.PollRequest.GetSessionOptions().GetSingleSession() + stableIdentity := singleSession.GetStableIdentity() + executionInfo := mutableState.GetExecutionInfo() + // An unbound execution can acquire the polling session's stable identity. + // Once bound, every start request, including retries, must identify the same session. + sessionState := executionInfo.GetSessionState() + if sessionState.GetExclusiveWorker() && (stableIdentity == "" || stableIdentity != sessionState.GetWorkerQueue()) { + return nil, serviceerrors.NewObsoleteMatchingTask("workflow is bound to another single-session destination") + } + metricsScope := shardContext.GetMetricsHandler().WithTags(metrics.OperationTag(metrics.HistoryRecordWorkflowTaskStartedScope)) // Check to see if mutable cache is stale in some extreme cassandra failure cases. @@ -177,11 +188,22 @@ func Invoke( return nil, err } + newSingleSessionBinding := singleSession != nil && !sessionState.GetExclusiveWorker() + if newSingleSessionBinding { + // Bind after the task start is accepted so both changes commit in the same transaction. + mutableState.SetSessionState(&persistencespb.SessionState{ + ExclusiveWorker: true, + WorkerQueue: stableIdentity, + }) + } + if workflowTask.Type == enumsspb.WORKFLOW_TASK_TYPE_SPECULATIVE { // A speculative workflow task can remain in memory only if starting it did not // produce changes that need to be persisted. Otherwise, closing the transaction // converts it to a normal workflow task and persists all of the changes. - updateAction.Noop = !mutableState.IsDirty() + // Note: newSingleSessionBinding is set to true when a worker is bound to a single-session sticky task queue. + // When this bit is set, we have to persist it immediately, which is why we don't have updateAction.Noop set to true. + updateAction.Noop = !newSingleSessionBinding && !mutableState.IsDirty() } else { // Start the deployment transition here only for a normal WFT. For a speculative WFT, // even when dirty mutable state causes the start to be persisted, the transition can diff --git a/service/history/api/recordworkflowtaskstarted/api_test.go b/service/history/api/recordworkflowtaskstarted/api_test.go index 90b3614854e..ab732c45746 100644 --- a/service/history/api/recordworkflowtaskstarted/api_test.go +++ b/service/history/api/recordworkflowtaskstarted/api_test.go @@ -9,6 +9,7 @@ import ( commonpb "go.temporal.io/api/common/v1" enumspb "go.temporal.io/api/enums/v1" "go.temporal.io/api/serviceerror" + sessionpb "go.temporal.io/api/session/v1" taskqueuepb "go.temporal.io/api/taskqueue/v1" "go.temporal.io/api/workflowservice/v1" "go.temporal.io/server/api/historyservice/v1" @@ -63,7 +64,7 @@ func TestRecordWorkflowTaskStarted_Errors(t *testing.T) { }) } -func invoke(t *testing.T, modifyMutableState mutableStateModifier) func() (*historyservice.RecordWorkflowTaskStartedResponseWithRawHistory, error) { +func invoke(t *testing.T, modifyMutableState mutableStateModifier, modifyRequest ...func(*historyservice.RecordWorkflowTaskStartedRequest)) func() (*historyservice.RecordWorkflowTaskStartedResponseWithRawHistory, error) { ctx := context.Background() ctrl := gomock.NewController(t) t.Cleanup(ctrl.Finish) @@ -90,6 +91,10 @@ func invoke(t *testing.T, modifyMutableState mutableStateModifier) func() (*hist }, } + for _, modify := range modifyRequest { + modify(request) + } + mockNamespaceRegistry := namespace.NewMockRegistry(ctrl) mockNamespaceRegistry.EXPECT().GetNamespaceByID(testNamespaceID).Return(tests.GlobalNamespaceEntry, nil).AnyTimes() mockClusterMetadata := cluster.NewMockMetadata(ctrl) @@ -121,3 +126,42 @@ func invoke(t *testing.T, modifyMutableState mutableStateModifier) func() (*hist return Invoke(ctx, request, shardContext, config, nil, nil, consistencyChecker) } } + +/* +TestRecordWorkflowTaskStartedSingleSessionRejection tests destination checks on duplicate starts: +1. Bind the workflow to exclusive worker queue S and mark its workflow task already started. +2. Retry the start without single-session options or with another stable identity. +3. Reject the request as obsolete, even when the sticky queue mirror names the other identity. +*/ +func TestRecordWorkflowTaskStartedSingleSessionRejection(t *testing.T) { + for _, tc := range []struct { + name, stableIdentity, stickyQueue string + singleSession bool + }{ + {name: "normal poll cannot start bound workflow", stickyQueue: "S"}, + {name: "different session cannot start bound workflow", singleSession: true, stableIdentity: "other", stickyQueue: "S"}, + {name: "sticky mirror cannot override binding", singleSession: true, stableIdentity: "other", stickyQueue: "other"}, + } { + t.Run(tc.name, func(t *testing.T) { + resp, err := invoke(t, func(ms *historyi.MockMutableState) { + ms.EXPECT().GetExecutionInfo().Return(&persistencespb.WorkflowExecutionInfo{ + SessionState: &persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "S"}, + StickyTaskQueue: tc.stickyQueue, + }).AnyTimes() + // Validate the destination even for a duplicate task-start request. + ms.EXPECT().GetWorkflowTaskByID(gomock.Any()).Return(&historyi.WorkflowTaskInfo{ + ScheduledEventID: 42, StartedEventID: 43, RequestID: "test-request", + }) + }, func(req *historyservice.RecordWorkflowTaskStartedRequest) { + if tc.singleSession { + req.PollRequest.SessionOptions = &sessionpb.WorkerSessionOptions{ + Admission: &sessionpb.WorkerSessionOptions_SingleSession{SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: tc.stableIdentity}}, + } + } + })() + require.Nil(t, resp) + var obsolete *serviceerrors.ObsoleteMatchingTask + require.ErrorAs(t, err, &obsolete) + }) + } +} diff --git a/service/history/api/respondworkflowtaskcompleted/api.go b/service/history/api/respondworkflowtaskcompleted/api.go index b941fc82f56..f10b69ef7cf 100644 --- a/service/history/api/respondworkflowtaskcompleted/api.go +++ b/service/history/api/respondworkflowtaskcompleted/api.go @@ -171,7 +171,8 @@ func (handler *WorkflowTaskCompletedHandler) Invoke( // Otherwise, leave it nil here to avoid clearing the workflow context (but still return error to the caller). errForRelease = retError } - if retError != nil && currentWorkflowTask != nil && currentWorkflowTask.Type == enumsspb.WORKFLOW_TASK_TYPE_SPECULATIVE && ms.IsStickyTaskQueueSet() { + if retError != nil && currentWorkflowTask != nil && currentWorkflowTask.Type == enumsspb.WORKFLOW_TASK_TYPE_SPECULATIVE && ms.IsStickyTaskQueueSet() && + !ms.GetExecutionInfo().GetSessionState().GetExclusiveWorker() { // If, while completing WFT, error is occurred and returned to the worker then worker will clear its cache. // New WFT will also be created on sticky task queue and sent to worker. // Because worker doesn't have a workflow in cache, it needs to replay it from the beginning, @@ -183,6 +184,9 @@ func (handler *WorkflowTaskCompletedHandler) Invoke( // and will have full history attached to it. // This is NOT 100% bulletproof solution because this write operation may also fail. // TODO: remove this call when GetWorkflowExecutionHistory includes speculative WFT events. + // + // TODO(Shivam): This piece of code seems stale given that history.sendTransientOrSpeculativeWorkflowTaskEvents is true by default. + // Revisit this if exclusive workers in the future need added support for this. if clearStickyErr := handler.clearStickyTaskQueue(ctx, workflowLease.GetContext()); clearStickyErr != nil { handler.logger.Error("Failed to clear stickiness after speculative workflow task failed to complete.", tag.NewErrorTag("clear-sticky-error", clearStickyErr), @@ -212,6 +216,16 @@ func (handler *WorkflowTaskCompletedHandler) Invoke( return nil, serviceerror.NewNotFound("Workflow task not found.") } + executionInfo := ms.GetExecutionInfo() + sessionState := executionInfo.GetSessionState() + if sessionState.GetExclusiveWorker() { + requestedStickyQueue := request.GetStickyAttributes().GetWorkerTaskQueue() + if requestedStickyQueue != nil && requestedStickyQueue.GetName() != sessionState.GetWorkerQueue() { + releaseLeaseWithError = false + return nil, serviceerror.NewInvalidArgument("workflow is bound to another single-session destination") + } + } + // We don't accept the request to create a new workflow task if the workflow is paused. if ms.IsWorkflowExecutionStatusPaused() && request.GetForceCreateNewWorkflowTask() { // Mutable state wasn't changed yet and doesn't have to be cleared. diff --git a/service/history/api/respondworkflowtaskcompleted/api_test.go b/service/history/api/respondworkflowtaskcompleted/api_test.go index bd46aa970cf..169bc62d51a 100644 --- a/service/history/api/respondworkflowtaskcompleted/api_test.go +++ b/service/history/api/respondworkflowtaskcompleted/api_test.go @@ -1479,3 +1479,41 @@ func (s *WorkflowTaskCompletedHandlerSuite) createStartedWorkflowWithStartedWFT( return wfContext, serializedTaskToken } + +/* +TestSingleSessionCompletionDestination tests sticky attributes on an exclusive workflow's completion: +1. Start a workflow task and bind the workflow to worker queue S. +2. Accept a completion without sticky attributes, but reject one requesting another destination. +3. Preserve the exclusive binding and sticky destination S in both cases. +*/ +func (s *WorkflowTaskCompletedHandlerSuite) TestSingleSessionCompletionDestination() { + for _, destination := range []string{"S", "other"} { + s.Run(destination, func() { + tv := testvars.New(s.T()) + tv = tv.WithRunID(tv.Any().RunID()) + s.mockNamespaceCache.EXPECT().GetNamespaceByID(tv.NamespaceID()).Return(tv.Namespace(), nil).AnyTimes() + wfContext := s.createStartedWorkflow(tv) + _, _, token := s.createSentUpdate(tv, wfContext) + ms, err := wfContext.LoadMutableState(context.Background(), s.mockShard) + s.Require().NoError(err) + ms.SetSessionState(&persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "S"}) + request := &workflowservice.RespondWorkflowTaskCompletedRequest{TaskToken: token, Identity: "worker"} + if destination == "other" { + request.StickyAttributes = tv.StickyExecutionAttributes(time.Second) + request.StickyAttributes.WorkerTaskQueue.Name = destination + } + _, err = s.workflowTaskCompletedHandler.Invoke(context.Background(), &historyservice.RespondWorkflowTaskCompletedRequest{ + NamespaceId: tv.NamespaceID().String(), CompleteRequest: request, + }) + if destination == "other" { + var invalid *serviceerror.InvalidArgument + s.ErrorAs(err, &invalid) + } else { + s.Require().NoError(err) + } + s.True(ms.GetExecutionInfo().GetSessionState().GetExclusiveWorker()) + s.Equal("S", ms.GetExecutionInfo().GetSessionState().GetWorkerQueue()) + s.Equal("S", ms.GetExecutionInfo().GetStickyTaskQueue()) + }) + } +} diff --git a/service/history/api/updateworkflow/api_test.go b/service/history/api/updateworkflow/api_test.go index 50f7130ead7..820901e48d0 100644 --- a/service/history/api/updateworkflow/api_test.go +++ b/service/history/api/updateworkflow/api_test.go @@ -9,15 +9,23 @@ import ( enumspb "go.temporal.io/api/enums/v1" failurepb "go.temporal.io/api/failure/v1" "go.temporal.io/api/serviceerror" + taskqueuepb "go.temporal.io/api/taskqueue/v1" updatepb "go.temporal.io/api/update/v1" "go.temporal.io/api/workflowservice/v1" + clockspb "go.temporal.io/server/api/clock/v1" "go.temporal.io/server/api/historyservice/v1" + "go.temporal.io/server/api/matchingservice/v1" + "go.temporal.io/server/api/matchingservicemock/v1" persistencespb "go.temporal.io/server/api/persistence/v1" "go.temporal.io/server/common" "go.temporal.io/server/common/definition" + "go.temporal.io/server/common/log" + "go.temporal.io/server/common/namespace" + serviceerrors "go.temporal.io/server/common/serviceerror" historyi "go.temporal.io/server/service/history/interfaces" "go.temporal.io/server/service/history/workflow/update" "go.uber.org/mock/gomock" + "google.golang.org/grpc" ) func TestRequestIDLinkSelectionForDuplicates(t *testing.T) { @@ -196,3 +204,40 @@ func createUpdateRequest(updateID string) *historyservice.UpdateWorkflowExecutio }, } } + +/* +TestSpeculativeDispatchFallsBackWhenStickyWorkerUnavailable tests ordinary speculative dispatch fallback: +1. Dispatch the task to sticky queue S and return StickyWorkerUnavailable from Matching. +2. Retry dispatch on the original normal queue foo and accept the task. +3. Reach the later namespace lookup, whose injected error confirms dispatch continued past fallback. +*/ +func TestSpeculativeDispatchFallsBackWhenStickyWorkerUnavailable(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + client := matchingservicemock.NewMockMatchingServiceClient(ctrl) + shard := historyi.NewMockShardContext(ctrl) + registry := namespace.NewMockRegistry(ctrl) + namespaceErr := serviceerror.NewNamespaceNotFound("namespace") + registry.EXPECT().GetNamespaceByID(gomock.Any()).Return(nil, namespaceErr) + shard.EXPECT().GetNamespaceRegistry().Return(registry) + shard.EXPECT().NewVectorClock().Return(&clockspb.VectorClock{}, nil).AnyTimes() + shard.EXPECT().GetLogger().Return(log.NewNoopLogger()).AnyTimes() + count := 0 + client.EXPECT().AddWorkflowTask(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, req *matchingservice.AddWorkflowTaskRequest, _ ...grpc.CallOption) (*matchingservice.AddWorkflowTaskResponse, error) { + count++ + if count == 1 { + require.Equal(t, "S", req.GetTaskQueue().GetName()) + return nil, &serviceerrors.StickyWorkerUnavailable{} + } + require.Equal(t, "foo", req.GetTaskQueue().GetName()) + return &matchingservice.AddWorkflowTaskResponse{}, nil + }).Times(2) + u := &Updater{ + req: createUpdateRequest("update"), shardCtx: shard, matchingClient: client, + scheduledEventID: 5, taskQueue: &taskqueuepb.TaskQueue{Name: "S", Kind: enumspb.TASK_QUEUE_KIND_STICKY}, + normalTaskQueueName: "foo", + } + _, err := u.OnSuccess(context.Background()) + require.ErrorIs(t, err, namespaceErr) +} diff --git a/service/history/history_engine2_test.go b/service/history/history_engine2_test.go index aa74957ca74..544f5451327 100644 --- a/service/history/history_engine2_test.go +++ b/service/history/history_engine2_test.go @@ -18,6 +18,7 @@ import ( nexuspb "go.temporal.io/api/nexus/v1" querypb "go.temporal.io/api/query/v1" "go.temporal.io/api/serviceerror" + sessionpb "go.temporal.io/api/session/v1" taskqueuepb "go.temporal.io/api/taskqueue/v1" updatepb "go.temporal.io/api/update/v1" workflowpb "go.temporal.io/api/workflow/v1" @@ -574,99 +575,119 @@ func (s *engine2Suite) TestRecordWorkflowTaskStarted_NoMessages() { } func (s *engine2Suite) TestRecordWorkflowTaskStarted_DirtySpeculativeWorkflowTask() { - // Verify that RecordWorkflowTaskStarted persists and converts a speculative Workflow Task when - // starting it makes mutable state dirty. Here, assigning the initial Build ID creates persistent - // visibility work, so History must not return Noop. - const ( - taskQueue = "testTaskQueue" - identity = "testIdentity" - updateID = "update-id" - buildID = "1.0.0" - ) - workflowExecution := &commonpb.WorkflowExecution{ - WorkflowId: "wId", - RunId: tests.RunID, - } - - s.mockClusterMetadata.EXPECT().ClusterNameForFailoverVersion(false, tests.Version). - Return(cluster.TestCurrentClusterName).AnyTimes() - ms := s.createExecutionStartedState(workflowExecution, taskQueue, identity, false, false) - // Keep the workflow at the active failover version so failover does not independently convert - // the speculative Workflow Task before the Build ID assignment is applied. - err := ms.UpdateCurrentVersion(tests.GlobalNamespaceEntry.FailoverVersion(workflowExecution.GetWorkflowId()), true) - s.NoError(err) + for _, tc := range []struct { + name string + singleSession bool + }{ + {name: "initial build ID"}, + {name: "initial session binding", singleSession: true}, + } { + s.Run(tc.name, func() { + const ( + taskQueue = "testTaskQueue" + identity = "testIdentity" + updateID = "update-id" + buildID = "1.0.0" + ) + workflowExecution := &commonpb.WorkflowExecution{ + WorkflowId: "wId", + RunId: tests.RunID, + } - s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), gomock.Any()).Return( - &persistence.GetWorkflowExecutionResponse{State: workflow.TestCloneToProto(context.Background(), ms)}, - nil, - ) - // Capture the write made by RecordWorkflowTaskStarted. If the handler incorrectly returns Noop, - // this expected persistence call is never made and the test fails. - var persistedExecutionInfo *persistencespb.WorkflowExecutionInfo - s.mockExecutionMgr.EXPECT().UpdateWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, request *persistence.UpdateWorkflowExecutionRequest) (*persistence.UpdateWorkflowExecutionResponse, error) { - persistedExecutionInfo = request.UpdateWorkflowMutation.ExecutionInfo - return tests.UpdateWorkflowExecutionResponse, nil - }, - ) - s.mockExecutionMgr.EXPECT().ReadHistoryBranch(gomock.Any(), gomock.Any()).Return( - &persistence.ReadHistoryBranchResponse{}, - nil, - ) - s.mockShard.Resource.SearchAttributesProvider.EXPECT().GetSearchAttributes(gomock.Any(), false). - Return(searchattribute.TestNameTypeMap(), nil) - s.mockShard.Resource.SearchAttributesMapperProvider.EXPECT().GetMapper(tests.Namespace). - Return(&searchattribute.TestMapper{Namespace: tests.Namespace.String()}, nil).AnyTimes() + s.mockClusterMetadata.EXPECT().ClusterNameForFailoverVersion(false, tests.Version). + Return(cluster.TestCurrentClusterName).AnyTimes() + ms := s.createExecutionStartedState(workflowExecution, taskQueue, identity, false, false) + // Failover must not independently force persistence of the speculative task. + err := ms.UpdateCurrentVersion(tests.GlobalNamespaceEntry.FailoverVersion(workflowExecution.GetWorkflowId()), true) + s.NoError(err) - // Admit an Update and schedule the speculative Workflow Task that will carry it to the worker. - wfCtx, release, err := s.workflowCache.GetOrCreateWorkflowExecution( - metrics.AddMetricsContext(context.Background()), - s.mockShard, - tests.NamespaceID, - workflowExecution, - locks.PriorityHigh, - ) - s.NoError(err) - loadedMS, err := wfCtx.LoadMutableState(context.Background(), s.mockShard) - s.NoError(err) - upd, _, err := wfCtx.UpdateRegistry(context.Background()).FindOrCreate(context.Background(), updateID) - s.NoError(err) - s.NoError(upd.Admit( - &updatepb.Request{ - Meta: &updatepb.Meta{UpdateId: updateID}, - Input: &updatepb.Input{Name: "update"}, - }, - workflow.WithEffects(effect.Immediate(context.Background()), loadedMS), - )) - wft, err := loadedMS.AddWorkflowTaskScheduledEvent(false, enumsspb.WORKFLOW_TASK_TYPE_SPECULATIVE) - s.NoError(err) - release(nil) + s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), gomock.Any()).Return( + &persistence.GetWorkflowExecutionResponse{State: workflow.TestCloneToProto(context.Background(), ms)}, + nil, + ) + // Capture the write made by RecordWorkflowTaskStarted. If the handler incorrectly returns Noop, + // this expected persistence call is never made and the test fails. + var persistedExecutionInfo *persistencespb.WorkflowExecutionInfo + s.mockExecutionMgr.EXPECT().UpdateWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, request *persistence.UpdateWorkflowExecutionRequest) (*persistence.UpdateWorkflowExecutionResponse, error) { + persistedExecutionInfo = request.UpdateWorkflowMutation.ExecutionInfo + return tests.UpdateWorkflowExecutionResponse, nil + }, + ) + s.mockExecutionMgr.EXPECT().ReadHistoryBranch(gomock.Any(), gomock.Any()).Return( + &persistence.ReadHistoryBranchResponse{}, + nil, + ) + s.mockShard.Resource.SearchAttributesProvider.EXPECT().GetSearchAttributes(gomock.Any(), false). + Return(searchattribute.TestNameTypeMap(), nil) + s.mockShard.Resource.SearchAttributesMapperProvider.EXPECT().GetMapper(tests.Namespace). + Return(&searchattribute.TestMapper{Namespace: tests.Namespace.String()}, nil).AnyTimes() + + // Admit an Update and schedule the speculative Workflow Task that will carry it to the worker. + wfCtx, release, err := s.workflowCache.GetOrCreateWorkflowExecution( + metrics.AddMetricsContext(context.Background()), + s.mockShard, + tests.NamespaceID, + workflowExecution, + locks.PriorityHigh, + ) + s.NoError(err) + loadedMS, err := wfCtx.LoadMutableState(context.Background(), s.mockShard) + s.NoError(err) + upd, _, err := wfCtx.UpdateRegistry(context.Background()).FindOrCreate(context.Background(), updateID) + s.NoError(err) + s.NoError(upd.Admit( + &updatepb.Request{ + Meta: &updatepb.Meta{UpdateId: updateID}, + Input: &updatepb.Input{Name: "update"}, + }, + workflow.WithEffects(effect.Immediate(context.Background()), loadedMS), + )) + wft, err := loadedMS.AddWorkflowTaskScheduledEvent(false, enumsspb.WORKFLOW_TASK_TYPE_SPECULATIVE) + s.NoError(err) + s.False(loadedMS.IsDirty()) + release(nil) - // Matching starts the speculative task with an initial Build ID assignment, which generates a - // visibility task and makes mutable state dirty. - response, err := s.historyEngine.RecordWorkflowTaskStarted( - metrics.AddMetricsContext(context.Background()), - &historyservice.RecordWorkflowTaskStartedRequest{ - NamespaceId: tests.NamespaceID.String(), - WorkflowExecution: workflowExecution, - ScheduledEventId: wft.ScheduledEventID, - RequestId: "request-id", - PollRequest: &workflowservice.PollWorkflowTaskQueueRequest{ - TaskQueue: &taskqueuepb.TaskQueue{Name: taskQueue}, - Identity: identity, - WorkerVersionCapabilities: &commonpb.WorkerVersionCapabilities{ //nolint:staticcheck // SA1019: required to exercise old Build ID-based versioning. - UseVersioning: true, - BuildId: buildID, + pollRequest := &workflowservice.PollWorkflowTaskQueueRequest{ + TaskQueue: &taskqueuepb.TaskQueue{Name: taskQueue}, Identity: identity, + } + if tc.singleSession { + pollRequest.SessionOptions = &sessionpb.WorkerSessionOptions{ + Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "S"}, + }, + } + } else { + //nolint:staticcheck // SA1019: required to exercise old Build ID-based versioning. + pollRequest.WorkerVersionCapabilities = &commonpb.WorkerVersionCapabilities{UseVersioning: true, BuildId: buildID} + } + response, err := s.historyEngine.RecordWorkflowTaskStarted( + metrics.AddMetricsContext(context.Background()), + &historyservice.RecordWorkflowTaskStartedRequest{ + NamespaceId: tests.NamespaceID.String(), + WorkflowExecution: workflowExecution, + ScheduledEventId: wft.ScheduledEventID, + RequestId: "request-id", + PollRequest: pollRequest, }, - }, - }, - ) - s.NoError(err) - s.NotNil(response) - // Transaction close must use the existing conversion path and persist the task as normal. - s.Require().NotNil(persistedExecutionInfo) - s.Equal(enumsspb.WORKFLOW_TASK_TYPE_NORMAL, persistedExecutionInfo.GetWorkflowTaskType()) - s.Equal(buildID, persistedExecutionInfo.GetAssignedBuildId()) + ) + s.NoError(err) + s.NotNil(response) + // Transaction close must use the existing conversion path and persist the task as normal. + s.Require().NotNil(persistedExecutionInfo) + s.Equal(enumsspb.WORKFLOW_TASK_TYPE_NORMAL, persistedExecutionInfo.GetWorkflowTaskType()) + s.Equal(taskQueue, persistedExecutionInfo.GetTaskQueue()) + s.Equal(response.GetStartedEventId(), persistedExecutionInfo.GetWorkflowTaskStartedEventId()) + if tc.singleSession { + s.True(persistedExecutionInfo.GetSessionState().GetExclusiveWorker()) + s.Equal("S", persistedExecutionInfo.GetSessionState().GetWorkerQueue()) + s.Equal("S", persistedExecutionInfo.GetStickyTaskQueue()) + s.Empty(persistedExecutionInfo.GetAssignedBuildId()) + } else { + s.Equal(buildID, persistedExecutionInfo.GetAssignedBuildId()) + } + }) + } } func (s *engine2Suite) TestRecordWorkflowTaskStartedIfGetExecutionFailed() { @@ -803,98 +824,141 @@ func (s *engine2Suite) TestRecordWorkflowTaskStartedConflictOnUpdate() { } func (s *engine2Suite) TestRecordWorkflowTaskStartedSuccess() { - fakeHistory := []*historypb.HistoryEvent{ - { - EventId: int64(1), - EventType: enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED, - }, - { - EventId: int64(2), - EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED, - Attributes: &historypb.HistoryEvent_WorkflowExecutionStartedEventAttributes{ - WorkflowExecutionStartedEventAttributes: &historypb.WorkflowExecutionStartedEventAttributes{ - SearchAttributes: &commonpb.SearchAttributes{ - IndexedFields: map[string]*commonpb.Payload{ - "Keyword01": sadefs.MustEncodeValue("random-keyword", enumspb.INDEXED_VALUE_TYPE_KEYWORD), - "TemporalChangeVersion": sadefs.MustEncodeValue("random-data", enumspb.INDEXED_VALUE_TYPE_KEYWORD), + for _, name := range []string{"ordinary", "acquire", "replacement"} { + exclusiveWorker := name != "ordinary" + s.Run(name, func() { + fakeHistory := []*historypb.HistoryEvent{ + { + EventId: int64(1), + EventType: enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED, + }, + { + EventId: int64(2), + EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED, + Attributes: &historypb.HistoryEvent_WorkflowExecutionStartedEventAttributes{ + WorkflowExecutionStartedEventAttributes: &historypb.WorkflowExecutionStartedEventAttributes{ + SearchAttributes: &commonpb.SearchAttributes{ + IndexedFields: map[string]*commonpb.Payload{ + "Keyword01": sadefs.MustEncodeValue("random-keyword", enumspb.INDEXED_VALUE_TYPE_KEYWORD), + "TemporalChangeVersion": sadefs.MustEncodeValue("random-data", enumspb.INDEXED_VALUE_TYPE_KEYWORD), + }, + }, }, }, }, - }, - }, - { - EventId: int64(3), - EventType: enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED, - }, - } - - s.mockExecutionMgr.EXPECT().ReadHistoryBranch(gomock.Any(), gomock.Any()).Return(&persistence.ReadHistoryBranchResponse{ - HistoryEvents: fakeHistory, - NextPageToken: []byte{}, - Size: 1, - }, nil) - s.mockShard.Resource.SearchAttributesProvider.EXPECT().GetSearchAttributes(gomock.Any(), false).Return(searchattribute.TestNameTypeMap(), nil) - s.mockShard.Resource.SearchAttributesMapperProvider.EXPECT().GetMapper(tests.Namespace). - Return(&searchattribute.TestMapper{Namespace: tests.Namespace.String()}, nil).AnyTimes() + { + EventId: int64(3), + EventType: enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED, + }, + } - namespaceID := tests.NamespaceID - workflowExecution := &commonpb.WorkflowExecution{ - WorkflowId: "wId", - RunId: tests.RunID, - } + historyReads := 1 + if exclusiveWorker { + historyReads++ + } + s.mockExecutionMgr.EXPECT().ReadHistoryBranch(gomock.Any(), gomock.Any()).Return(&persistence.ReadHistoryBranchResponse{ + HistoryEvents: fakeHistory, + NextPageToken: []byte{}, + Size: 1, + }, nil).Times(historyReads) + s.mockShard.Resource.SearchAttributesProvider.EXPECT().GetSearchAttributes(gomock.Any(), false).Return(searchattribute.TestNameTypeMap(), nil).Times(historyReads) + s.mockShard.Resource.SearchAttributesMapperProvider.EXPECT().GetMapper(tests.Namespace). + Return(&searchattribute.TestMapper{Namespace: tests.Namespace.String()}, nil).AnyTimes() + + namespaceID := tests.NamespaceID + workflowExecution := &commonpb.WorkflowExecution{ + WorkflowId: "wId", + RunId: tests.RunID, + } - tl := "testTaskQueue" - identity := "testIdentity" + tl := "testTaskQueue" + identity := "testIdentity" - ms := s.createExecutionStartedState(workflowExecution, tl, identity, true, false) - wfMs := workflow.TestCloneToProto(context.Background(), ms) - gwmsResponse := &persistence.GetWorkflowExecutionResponse{State: wfMs} - s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), gomock.Any()).Return(gwmsResponse, nil) - s.mockExecutionMgr.EXPECT().UpdateWorkflowExecution(gomock.Any(), gomock.Any()).Return(tests.UpdateWorkflowExecutionResponse, nil) - - // load mutable state such that it already exists in memory when respond workflow task is called - // this enables us to set query registry on it - ctx, release, err := s.workflowCache.GetOrCreateWorkflowExecution( - metrics.AddMetricsContext(context.Background()), - s.mockShard, - tests.NamespaceID, - workflowExecution, - locks.PriorityHigh, - ) - s.NoError(err) - loadedMS, err := ctx.LoadMutableState(context.Background(), s.mockShard) - s.NoError(err) - qr := workflow.NewQueryRegistry() - id1, _ := qr.BufferQuery(&querypb.WorkflowQuery{}) - id2, _ := qr.BufferQuery(&querypb.WorkflowQuery{}) - id3, _ := qr.BufferQuery(&querypb.WorkflowQuery{}) - loadedMS.(*workflow.MutableStateImpl).QueryRegistry = qr - release(nil) + ms := s.createExecutionStartedState(workflowExecution, tl, identity, true, false) + if name == "replacement" { + ms.SetSessionState(&persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "S"}) + } + wfMs := workflow.TestCloneToProto(context.Background(), ms) + gwmsResponse := &persistence.GetWorkflowExecutionResponse{State: wfMs} + s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), gomock.Any()).Return(gwmsResponse, nil) + s.mockExecutionMgr.EXPECT().UpdateWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, request *persistence.UpdateWorkflowExecutionRequest) (*persistence.UpdateWorkflowExecutionResponse, error) { + info := request.UpdateWorkflowMutation.ExecutionInfo + s.Equal(exclusiveWorker, info.GetSessionState().GetExclusiveWorker()) + s.Equal(tl, info.GetTaskQueue()) + s.Equal(int64(3), info.GetWorkflowTaskStartedEventId()) + if exclusiveWorker { + s.Equal("S", info.GetStickyTaskQueue()) + s.Equal("S", info.GetSessionState().GetWorkerQueue()) + } + return tests.UpdateWorkflowExecutionResponse, nil + }) - response, err := s.historyEngine.RecordWorkflowTaskStarted(metrics.AddMetricsContext(context.Background()), &historyservice.RecordWorkflowTaskStartedRequest{ - NamespaceId: namespaceID.String(), - WorkflowExecution: workflowExecution, - ScheduledEventId: 2, - RequestId: "reqId", - PollRequest: &workflowservice.PollWorkflowTaskQueueRequest{ - TaskQueue: &taskqueuepb.TaskQueue{ - Name: tl, - }, - Identity: identity, - }, - }) + // load mutable state such that it already exists in memory when respond workflow task is called + // this enables us to set query registry on it + ctx, release, err := s.workflowCache.GetOrCreateWorkflowExecution( + metrics.AddMetricsContext(context.Background()), + s.mockShard, + tests.NamespaceID, + workflowExecution, + locks.PriorityHigh, + ) + s.NoError(err) + loadedMS, err := ctx.LoadMutableState(context.Background(), s.mockShard) + s.NoError(err) + qr := workflow.NewQueryRegistry() + id1, _ := qr.BufferQuery(&querypb.WorkflowQuery{}) + id2, _ := qr.BufferQuery(&querypb.WorkflowQuery{}) + id3, _ := qr.BufferQuery(&querypb.WorkflowQuery{}) + loadedMS.(*workflow.MutableStateImpl).QueryRegistry = qr + release(nil) + + var sessionOptions *sessionpb.WorkerSessionOptions + if exclusiveWorker { + sessionOptions = &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "S"}}} + } + request := &historyservice.RecordWorkflowTaskStartedRequest{ + NamespaceId: namespaceID.String(), + WorkflowExecution: workflowExecution, + ScheduledEventId: 2, + RequestId: "reqId", + PollRequest: &workflowservice.PollWorkflowTaskQueueRequest{ + TaskQueue: &taskqueuepb.TaskQueue{ + Name: tl, + }, + Identity: identity, + SessionOptions: sessionOptions, + }, + } + if name == "replacement" { + request.PollRequest.TaskQueue = &taskqueuepb.TaskQueue{Name: "S", Kind: enumspb.TASK_QUEUE_KIND_STICKY} + request.PollRequest.Identity = "replacement-process" + } + response, err := s.historyEngine.RecordWorkflowTaskStarted(metrics.AddMetricsContext(context.Background()), request) - s.Nil(err) - s.NotNil(response) - s.Equal("wType", response.WorkflowType.Name) - s.True(response.PreviousStartedEventId == 0) - s.Equal(int64(3), response.StartedEventId) - expectedQueryMap := map[string]*querypb.WorkflowQuery{ - id1: {}, - id2: {}, - id3: {}, + s.NoError(err) + s.NotNil(response) + s.Equal("wType", response.GetWorkflowType().GetName()) + s.Zero(response.GetPreviousStartedEventId()) + s.Equal(int64(3), response.GetStartedEventId()) + expectedQueryMap := map[string]*querypb.WorkflowQuery{ + id1: {}, + id2: {}, + id3: {}, + } + s.Equal(expectedQueryMap, response.GetQueries()) + if exclusiveWorker { + s.True(response.GetStickyExecutionEnabled()) + s.True(loadedMS.GetExecutionInfo().GetSessionState().GetExclusiveWorker()) + s.Equal("S", loadedMS.CurrentTaskQueue().GetName()) + retry := common.CloneProto(request) + retry.PollRequest.Identity = "retry-process" + retryResponse, err := s.historyEngine.RecordWorkflowTaskStarted(metrics.AddMetricsContext(context.Background()), retry) + s.Require().NoError(err) + s.Equal(response.GetStartedEventId(), retryResponse.GetStartedEventId()) + s.True(retryResponse.GetStickyExecutionEnabled()) + } + }) } - s.Equal(expectedQueryMap, response.Queries) } func (s *engine2Suite) TestRecordWorkflowTaskStartedSuccessWithInternalRawHistory() { diff --git a/service/history/history_engine_test.go b/service/history/history_engine_test.go index e97084c4431..9e8509321a4 100644 --- a/service/history/history_engine_test.go +++ b/service/history/history_engine_test.go @@ -58,6 +58,7 @@ import ( "go.temporal.io/server/common/rpc/interceptor" "go.temporal.io/server/common/searchattribute" "go.temporal.io/server/common/searchattribute/sadefs" + serviceerrors "go.temporal.io/server/common/serviceerror" "go.temporal.io/server/common/tasktoken" "go.temporal.io/server/common/testing/protorequire" "go.temporal.io/server/common/testing/testhooks" @@ -269,33 +270,51 @@ func (s *engineSuite) TearDownTest() { } func (s *engineSuite) TestGetMutableStateSync() { - ctx := context.Background() + for _, exclusiveWorker := range []bool{false, true} { + name := "ordinary" + if exclusiveWorker { + name = "exclusive worker" + } + s.Run(name, func() { + ctx := s.T().Context() + execution := commonpb.WorkflowExecution{ + WorkflowId: s.T().Name(), + RunId: tests.RunID, + } + taskqueue := "testTaskQueue" + identity := "testIdentity" + + ms := workflow.TestLocalMutableState(s.historyEngine.shardContext, s.eventsCache, tests.LocalNamespaceEntry, + execution.GetWorkflowId(), execution.GetRunId(), log.NewTestLogger()) + addWorkflowExecutionStartedEvent(ms, &execution, "wType", taskqueue, payloads.EncodeString("input"), 100*time.Second, 50*time.Second, 200*time.Second, identity) + wt := addWorkflowTaskScheduledEvent(ms) + addWorkflowTaskStartedEvent(ms, wt.ScheduledEventID, taskqueue, identity) + var expectedSessionState *persistencespb.SessionState + if exclusiveWorker { + expectedSessionState = &persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "S"} + ms.SetSessionState(common.CloneProto(expectedSessionState)) + } + wfMs := workflow.TestCloneToProto(ctx, ms) + gweResponse := &persistence.GetWorkflowExecutionResponse{State: wfMs} + s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), gomock.Any()).Return(gweResponse, nil) - execution := commonpb.WorkflowExecution{ - WorkflowId: "test-get-workflow-execution-event-id", - RunId: tests.RunID, + request := &historyservice.GetMutableStateRequest{ + NamespaceId: tests.NamespaceID.String(), + Execution: &execution, + } + response, err := s.historyEngine.GetMutableState(ctx, request) + s.Require().NoError(err) + s.Equal(int64(4), response.GetNextEventId()) + s.Equal(tests.RunID, response.GetFirstExecutionRunId()) + protorequire.ProtoEqual(s.T(), expectedSessionState, response.GetSessionState()) + if exclusiveWorker { + response.GetSessionState().WorkerQueue = "other" + response, err = s.historyEngine.GetMutableState(ctx, request) + s.Require().NoError(err) + protorequire.ProtoEqual(s.T(), expectedSessionState, response.GetSessionState()) + } + }) } - taskqueue := "testTaskQueue" - identity := "testIdentity" - - ms := workflow.TestLocalMutableState(s.historyEngine.shardContext, s.eventsCache, tests.LocalNamespaceEntry, - execution.GetWorkflowId(), execution.GetRunId(), log.NewTestLogger()) - addWorkflowExecutionStartedEvent(ms, &execution, "wType", taskqueue, payloads.EncodeString("input"), 100*time.Second, 50*time.Second, 200*time.Second, identity) - wt := addWorkflowTaskScheduledEvent(ms) - addWorkflowTaskStartedEvent(ms, wt.ScheduledEventID, taskqueue, identity) - wfMs := workflow.TestCloneToProto(context.Background(), ms) - gweResponse := &persistence.GetWorkflowExecutionResponse{State: wfMs} - // right now the next event ID is 4 - s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), gomock.Any()).Return(gweResponse, nil) - - // test get the next event ID instantly - response, err := s.historyEngine.GetMutableState(ctx, &historyservice.GetMutableStateRequest{ - NamespaceId: tests.NamespaceID.String(), - Execution: &execution, - }) - s.Nil(err) - s.Equal(int64(4), response.GetNextEventId()) - s.Equal(tests.RunID, response.GetFirstExecutionRunId()) } func (s *engineSuite) TestGetMutableState_IntestRunID() { @@ -710,6 +729,72 @@ func (s *engineSuite) TestQueryWorkflow_DirectlyThroughMatching() { s.Equal(taskqueue, contextTaskQueue) } +/* +TestSingleSessionQueryDoesNotFallback tests queries for a workflow bound to exclusive worker S: +1. Load the persisted workflow binding and issue a query with a caller deadline. +2. Send exactly one query to sticky queue S while preserving that deadline. +3. Return the result or Matching error without retrying on the normal queue. +*/ +func (s *engineSuite) TestSingleSessionQueryDoesNotFallback() { + s.historyEngine.matchingClient = s.mockMatchingClient + s.historyEngine.rawMatchingClient = s.mockMatchingClient + for _, tc := range []struct { + name string + err error + }{ + {name: "success"}, + {name: "worker unavailable", err: &serviceerrors.StickyWorkerUnavailable{}}, + {name: "deadline", err: context.DeadlineExceeded}, + {name: "cancelled", err: context.Canceled}, + {name: "internal", err: serviceerror.NewInternal("failure")}, + } { + s.Run(tc.name, func() { + t := s.T() + execution := &commonpb.WorkflowExecution{WorkflowId: t.Name(), RunId: tests.RunID} + ms := workflow.TestLocalMutableState(s.mockShard, s.eventsCache, tests.LocalNamespaceEntry, + execution.GetWorkflowId(), execution.GetRunId(), log.NewTestLogger()) + addWorkflowExecutionStartedEvent(ms, execution, "wType", "foo", payloads.EncodeString("input"), + 100*time.Second, 50*time.Second, 200*time.Second, "worker") + wt := addWorkflowTaskScheduledEvent(ms) + started := addWorkflowTaskStartedEvent(ms, wt.ScheduledEventID, "foo", "worker") + addWorkflowTaskCompletedEvent(&s.Suite, ms, wt.ScheduledEventID, started.GetEventId(), "worker") + ms.SetStickyTaskQueue("S", nil) + ms.GetExecutionInfo().SessionState = &persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "S"} + s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), gomock.Any()).Return( + &persistence.GetWorkflowExecutionResponse{State: workflow.TestCloneToProto(t.Context(), ms)}, nil) + + ctx, cancel := context.WithTimeout(contextutil.WithMetadataContext(t.Context()), time.Minute) + defer cancel() + expectedResult := payloads.EncodeBytes([]byte{1, 2, 3}) + s.mockMatchingClient.EXPECT().QueryWorkflow(gomock.Any(), gomock.Any()).DoAndReturn( + func(callCtx context.Context, req *matchingservice.QueryWorkflowRequest, _ ...grpc.CallOption) (*matchingservice.QueryWorkflowResponse, error) { + require.NoError(t, callCtx.Err()) + require.Equal(t, "S", req.GetTaskQueue().GetName()) + require.Equal(t, enumspb.TASK_QUEUE_KIND_STICKY, req.GetTaskQueue().GetKind()) + deadline, ok := callCtx.Deadline() + require.True(t, ok) + callerDeadline, _ := ctx.Deadline() + require.Equal(t, callerDeadline, deadline) + return &matchingservice.QueryWorkflowResponse{QueryResult: expectedResult}, tc.err + }).Times(1) + response, err := s.historyEngine.QueryWorkflow(ctx, &historyservice.QueryWorkflowRequest{ + NamespaceId: tests.NamespaceID.String(), + Request: &workflowservice.QueryWorkflowRequest{ + Execution: execution, + Query: &querypb.WorkflowQuery{}, + }, + }) + if tc.err != nil { + require.ErrorIs(t, err, tc.err) + require.Nil(t, response) + } else { + require.NoError(t, err) + protorequire.ProtoEqual(t, expectedResult, response.GetResponse().GetQueryResult()) + } + }) + } +} + func (s *engineSuite) TestQueryWorkflow_WorkflowTaskDispatch_Timeout() { execution := commonpb.WorkflowExecution{ WorkflowId: "TestQueryWorkflow_WorkflowTaskDispatch_Timeout", diff --git a/service/history/interfaces/mutable_state.go b/service/history/interfaces/mutable_state.go index ffb7a686226..ceb41a6ac80 100644 --- a/service/history/interfaces/mutable_state.go +++ b/service/history/interfaces/mutable_state.go @@ -229,6 +229,7 @@ type ( GetApproximatePersistedSize() int CurrentTaskQueue() *taskqueuepb.TaskQueue + SetSessionState(state *persistencespb.SessionState) SetStickyTaskQueue(name string, scheduleToStartTimeout *durationpb.Duration) ClearStickyTaskQueue() IsStickyTaskQueueSet() bool diff --git a/service/history/interfaces/mutable_state_mock.go b/service/history/interfaces/mutable_state_mock.go index c4131a7cae9..8a490e1de86 100644 --- a/service/history/interfaces/mutable_state_mock.go +++ b/service/history/interfaces/mutable_state_mock.go @@ -3629,6 +3629,18 @@ func (mr *MockMutableStateMockRecorder) SetReplayEventBatchID(batchID any) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReplayEventBatchID", reflect.TypeOf((*MockMutableState)(nil).SetReplayEventBatchID), batchID) } +// SetSessionState mocks base method. +func (m *MockMutableState) SetSessionState(state *persistence.SessionState) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetSessionState", state) +} + +// SetSessionState indicates an expected call of SetSessionState. +func (mr *MockMutableStateMockRecorder) SetSessionState(state any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSessionState", reflect.TypeOf((*MockMutableState)(nil).SetSessionState), state) +} + // SetSpeculativeWorkflowTaskTimeoutTask mocks base method. func (m *MockMutableState) SetSpeculativeWorkflowTaskTimeoutTask(task *tasks.WorkflowTaskTimeoutTask) error { m.ctrl.T.Helper() diff --git a/service/history/timer_queue_active_task_executor_test.go b/service/history/timer_queue_active_task_executor_test.go index c3a896e294c..48d0c572958 100644 --- a/service/history/timer_queue_active_task_executor_test.go +++ b/service/history/timer_queue_active_task_executor_test.go @@ -1095,59 +1095,90 @@ func (s *timerQueueActiveTaskExecutorSuite) TestProcessActivityTimeout_Heartbeat } func (s *timerQueueActiveTaskExecutorSuite) TestWorkflowTaskTimeout_Fire() { - execution := &commonpb.WorkflowExecution{ - WorkflowId: "some random workflow ID", - RunId: uuid.NewString(), - } - workflowKey := definition.NewWorkflowKey( - s.namespaceID.String(), - execution.GetWorkflowId(), - execution.GetRunId(), - ) - workflowType := "some random workflow type" - taskQueueName := "some random task queue" - - mutableState := workflow.TestGlobalMutableState(s.mockShard, s.mockShard.GetEventsCache(), s.logger, s.version, execution.GetWorkflowId(), execution.GetRunId()) - _, err := mutableState.AddWorkflowExecutionStartedEvent( - execution, - &historyservice.StartWorkflowExecutionRequest{ - Attempt: 1, - NamespaceId: s.namespaceID.String(), - StartRequest: &workflowservice.StartWorkflowExecutionRequest{ - WorkflowType: &commonpb.WorkflowType{Name: workflowType}, - TaskQueue: &taskqueuepb.TaskQueue{Name: taskQueueName}, - WorkflowRunTimeout: durationpb.New(200 * time.Second), - WorkflowTaskTimeout: durationpb.New(1 * time.Second), - }, - }, - ) - s.NoError(err) - - wt := addWorkflowTaskScheduledEvent(mutableState) - startedEvent := addWorkflowTaskStartedEvent(mutableState, wt.ScheduledEventID, taskQueueName, uuid.NewString()) + for _, bound := range []bool{false, true} { + name := "ordinary start-to-close" + if bound { + name = "single-session start-to-close" + } + s.Run(name, func() { + s.Assertions = require.New(s.T()) + execution := &commonpb.WorkflowExecution{ + WorkflowId: "some random workflow ID", + RunId: uuid.NewString(), + } + workflowKey := definition.NewWorkflowKey( + s.namespaceID.String(), + execution.GetWorkflowId(), + execution.GetRunId(), + ) + workflowType := "some random workflow type" + taskQueueName := "some random task queue" - timerTask := &tasks.WorkflowTaskTimeoutTask{ - WorkflowKey: workflowKey, - ScheduleAttempt: 1, - Version: s.version, - TaskID: s.mustGenerateTaskID(), - TimeoutType: enumspb.TIMEOUT_TYPE_START_TO_CLOSE, - VisibilityTimestamp: s.now, - EventID: wt.ScheduledEventID, - } + mutableState := workflow.TestGlobalMutableState(s.mockShard, s.mockShard.GetEventsCache(), s.logger, s.version, execution.GetWorkflowId(), execution.GetRunId()) + _, err := mutableState.AddWorkflowExecutionStartedEvent( + execution, + &historyservice.StartWorkflowExecutionRequest{ + Attempt: 1, + NamespaceId: s.namespaceID.String(), + StartRequest: &workflowservice.StartWorkflowExecutionRequest{ + WorkflowType: &commonpb.WorkflowType{Name: workflowType}, + TaskQueue: &taskqueuepb.TaskQueue{Name: taskQueueName}, + WorkflowRunTimeout: durationpb.New(200 * time.Second), + WorkflowTaskTimeout: durationpb.New(1 * time.Second), + }, + }, + ) + s.NoError(err) - persistenceMutableState := s.createPersistenceMutableState(mutableState, startedEvent.GetEventId(), startedEvent.GetVersion()) - s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), gomock.Any()).Return(&persistence.GetWorkflowExecutionResponse{State: persistenceMutableState}, nil) - s.mockExecutionMgr.EXPECT().UpdateWorkflowExecution(gomock.Any(), gomock.Any()).Return(tests.UpdateWorkflowExecutionResponse, nil) + if bound { + mutableState.SetStickyTaskQueue("S", nil) + mutableState.GetExecutionInfo().SessionState = &persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "S"} + } + wt := addWorkflowTaskScheduledEvent(mutableState) + startedEvent := addWorkflowTaskStartedEvent(mutableState, wt.ScheduledEventID, mutableState.CurrentTaskQueue().GetName(), uuid.NewString()) - resp := s.timerQueueActiveTaskExecutor.Execute(context.Background(), s.newTaskExecutable(timerTask)) - s.NoError(resp.ExecutionErr) + timerTask := &tasks.WorkflowTaskTimeoutTask{ + WorkflowKey: workflowKey, + ScheduleAttempt: 1, + Version: s.version, + TaskID: s.mustGenerateTaskID(), + TimeoutType: enumspb.TIMEOUT_TYPE_START_TO_CLOSE, + VisibilityTimestamp: s.now, + EventID: wt.ScheduledEventID, + } - workflowTask := s.getMutableStateFromCache(workflowKey).GetPendingWorkflowTask() - s.NotNil(workflowTask) - s.True(workflowTask.ScheduledEventID != common.EmptyEventID) - s.Equal(common.EmptyEventID, workflowTask.StartedEventID) - s.Equal(int32(2), workflowTask.Attempt) + persistenceMutableState := s.createPersistenceMutableState(mutableState, startedEvent.GetEventId(), startedEvent.GetVersion()) + s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), gomock.Any()).Return(&persistence.GetWorkflowExecutionResponse{State: persistenceMutableState}, nil) + s.mockExecutionMgr.EXPECT().UpdateWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, request *persistence.UpdateWorkflowExecutionRequest) (*persistence.UpdateWorkflowExecutionResponse, error) { + if bound { + info := request.UpdateWorkflowMutation.ExecutionInfo + s.True(info.GetSessionState().GetExclusiveWorker()) + s.Equal("S", info.GetSessionState().GetWorkerQueue()) + s.Equal(int32(1), info.GetWorkflowTaskAttemptsSinceLastSuccess()) + s.Equal("S", info.GetStickyTaskQueue()) + s.Equal(taskQueueName, info.GetTaskQueue()) + } + return tests.UpdateWorkflowExecutionResponse, nil + }) + + s.timeSource.Update(timerTask.VisibilityTimestamp) + resp := s.timerQueueActiveTaskExecutor.Execute(context.Background(), s.newTaskExecutable(timerTask)) + s.Require().NoError(resp.ExecutionErr) + + state := s.getMutableStateFromCache(workflowKey) + workflowTask := state.GetPendingWorkflowTask() + s.Require().NotNil(workflowTask) + s.Greater(workflowTask.ScheduledEventID, timerTask.EventID) + s.Equal(common.EmptyEventID, workflowTask.StartedEventID) + s.Equal(int32(2), workflowTask.Attempt) + if bound { + s.Equal("S", workflowTask.TaskQueue.GetName()) + s.Equal(enumspb.TASK_QUEUE_KIND_STICKY, workflowTask.TaskQueue.GetKind()) + s.Nil(workflowTask.ScheduleToStartTimeoutTask) + } + }) + } } func (s *timerQueueActiveTaskExecutorSuite) TestWorkflowTaskTimeout_Noop() { diff --git a/service/history/transfer_queue_active_task_executor.go b/service/history/transfer_queue_active_task_executor.go index 2d49b342f58..d3beca6a34e 100644 --- a/service/history/transfer_queue_active_task_executor.go +++ b/service/history/transfer_queue_active_task_executor.go @@ -355,7 +355,9 @@ func (t *transferQueueActiveTaskExecutor) processWorkflowTask( ) if _, ok := err.(*serviceerrors.StickyWorkerUnavailable); ok { - // sticky worker is unavailable, switch to original normal task queue + // sticky worker is unavailable, switch to original normal task queue. Note: Sending back to the normal task queue is done + // only if the workflow task is not assigned to an exclusive worker. In the case of an exclusive worker, matching does not + // send *serviceerrors.StickyWorkerUnavailable and this code path is not taken. taskQueue = &taskqueuepb.TaskQueue{ // do not use task.TaskQueue which is sticky, use original normal task queue from mutable state Name: normalTaskQueueName, diff --git a/service/history/transfer_queue_active_task_executor_test.go b/service/history/transfer_queue_active_task_executor_test.go index 6bf99199fe4..c48b55c1203 100644 --- a/service/history/transfer_queue_active_task_executor_test.go +++ b/service/history/transfer_queue_active_task_executor_test.go @@ -47,6 +47,7 @@ import ( "go.temporal.io/server/common/primitives" "go.temporal.io/server/common/primitives/timestamp" "go.temporal.io/server/common/searchattribute" + serviceerrors "go.temporal.io/server/common/serviceerror" "go.temporal.io/server/common/tasktoken" "go.temporal.io/server/common/telemetry" "go.temporal.io/server/common/testing/protomock" @@ -638,64 +639,102 @@ func (s *transferQueueActiveTaskExecutorSuite) TestProcessWorkflowTask_NonFirstW } func (s *transferQueueActiveTaskExecutorSuite) TestProcessWorkflowTask_Sticky_NonFirstWorkflowTask() { - execution := &commonpb.WorkflowExecution{ - WorkflowId: "some random workflow ID", - RunId: uuid.NewString(), - } - workflowType := "some random workflow type" - taskQueueName := "some random task queue" - stickyTaskQueueName := "some random sticky task queue" - stickyTaskQueueTimeout := timestamp.DurationFromSeconds(233) + for _, tc := range []struct { + name string + bound, unavailable bool + }{ + {name: "ordinary success"}, + {name: "ordinary fallback", unavailable: true}, + {name: "single session success", bound: true}, + } { + s.Run(tc.name, func() { + execution := &commonpb.WorkflowExecution{ + WorkflowId: "some random workflow ID", + RunId: uuid.NewString(), + } + workflowType := "some random workflow type" + taskQueueName := "some random task queue" + stickyTaskQueueName := "some random sticky task queue" + stickyTaskQueueTimeout := timestamp.DurationFromSeconds(233) + workflowRunTimeout := durationpb.New(2 * time.Second) - mutableState := workflow.TestGlobalMutableState(s.mockShard, s.mockShard.GetEventsCache(), s.logger, s.version, execution.GetWorkflowId(), execution.GetRunId()) - _, err := mutableState.AddWorkflowExecutionStartedEvent( - execution, - &historyservice.StartWorkflowExecutionRequest{ - Attempt: 1, - NamespaceId: s.namespaceID.String(), - StartRequest: &workflowservice.StartWorkflowExecutionRequest{ - WorkflowType: &commonpb.WorkflowType{Name: workflowType}, - TaskQueue: &taskqueuepb.TaskQueue{Name: taskQueueName}, - WorkflowExecutionTimeout: durationpb.New(2 * time.Second), - WorkflowTaskTimeout: durationpb.New(1 * time.Second), - }, - }, - ) - s.NoError(err) + mutableState := workflow.TestGlobalMutableState(s.mockShard, s.mockShard.GetEventsCache(), s.logger, s.version, execution.GetWorkflowId(), execution.GetRunId()) + _, err := mutableState.AddWorkflowExecutionStartedEvent( + execution, + &historyservice.StartWorkflowExecutionRequest{ + Attempt: 1, + NamespaceId: s.namespaceID.String(), + StartRequest: &workflowservice.StartWorkflowExecutionRequest{ + WorkflowType: &commonpb.WorkflowType{Name: workflowType}, + TaskQueue: &taskqueuepb.TaskQueue{Name: taskQueueName}, + WorkflowExecutionTimeout: durationpb.New(2 * time.Second), + WorkflowRunTimeout: workflowRunTimeout, + WorkflowTaskTimeout: durationpb.New(1 * time.Second), + }, + }, + ) + s.NoError(err) - wt := addWorkflowTaskScheduledEvent(mutableState) - event := addWorkflowTaskStartedEvent(mutableState, wt.ScheduledEventID, taskQueueName, uuid.NewString()) - wt.StartedEventID = event.GetEventId() - event = addWorkflowTaskCompletedEvent(&s.Suite, mutableState, wt.ScheduledEventID, wt.StartedEventID, "some random identity") - s.NotNil(event) - // set the sticky taskqueue attr - executionInfo := mutableState.GetExecutionInfo() - executionInfo.StickyTaskQueue = stickyTaskQueueName - executionInfo.StickyScheduleToStartTimeout = stickyTaskQueueTimeout + wt := addWorkflowTaskScheduledEvent(mutableState) + event := addWorkflowTaskStartedEvent(mutableState, wt.ScheduledEventID, taskQueueName, uuid.NewString()) + wt.StartedEventID = event.GetEventId() + event = addWorkflowTaskCompletedEvent(&s.Suite, mutableState, wt.ScheduledEventID, wt.StartedEventID, "some random identity") + s.NotNil(event) + // set the sticky taskqueue attr + executionInfo := mutableState.GetExecutionInfo() + executionInfo.StickyTaskQueue = stickyTaskQueueName + if tc.bound { + executionInfo.SessionState = &persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: stickyTaskQueueName} + } + executionInfo.StickyScheduleToStartTimeout = stickyTaskQueueTimeout - // make another round of workflow task - taskID := s.mustGenerateTaskID() - wt = addWorkflowTaskScheduledEvent(mutableState) + // make another round of workflow task + taskID := s.mustGenerateTaskID() + wt = addWorkflowTaskScheduledEvent(mutableState) - transferTask := &tasks.WorkflowTask{ - WorkflowKey: definition.NewWorkflowKey( - s.namespaceID.String(), - execution.GetWorkflowId(), - execution.GetRunId(), - ), - Version: s.version, - TaskID: taskID, - TaskQueue: stickyTaskQueueName, - ScheduledEventID: wt.ScheduledEventID, - VisibilityTimestamp: time.Now().UTC(), - } + transferTask := &tasks.WorkflowTask{ + WorkflowKey: definition.NewWorkflowKey( + s.namespaceID.String(), + execution.GetWorkflowId(), + execution.GetRunId(), + ), + Version: s.version, + TaskID: taskID, + TaskQueue: stickyTaskQueueName, + ScheduledEventID: wt.ScheduledEventID, + VisibilityTimestamp: time.Now().UTC(), + } - persistenceMutableState := s.createPersistenceMutableState(mutableState, wt.ScheduledEventID, wt.Version) - s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), gomock.Any()).Return(&persistence.GetWorkflowExecutionResponse{State: persistenceMutableState}, nil) - s.mockMatchingClient.EXPECT().AddWorkflowTask(gomock.Any(), s.createAddWorkflowTaskRequest(transferTask, mutableState), gomock.Any()).Return(&matchingservice.AddWorkflowTaskResponse{}, nil) + persistenceMutableState := s.createPersistenceMutableState(mutableState, wt.ScheduledEventID, wt.Version) + s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), gomock.Any()).Return(&persistence.GetWorkflowExecutionResponse{State: persistenceMutableState}, nil) + calls := 1 + if tc.unavailable { + calls = 2 + } + callCount := 0 + s.mockMatchingClient.EXPECT().AddWorkflowTask(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, request *matchingservice.AddWorkflowTaskRequest, _ ...grpc.CallOption) (*matchingservice.AddWorkflowTaskResponse, error) { + callCount++ + if callCount == 1 { + s.Equal(stickyTaskQueueName, request.GetTaskQueue().GetName()) + s.Equal(enumspb.TASK_QUEUE_KIND_STICKY, request.GetTaskQueue().GetKind()) + if tc.bound { + s.Equal(workflowRunTimeout.AsDuration(), request.GetScheduleToStartTimeout().AsDuration()) + } + if tc.unavailable { + return nil, &serviceerrors.StickyWorkerUnavailable{} + } + } else { + s.Equal(taskQueueName, request.GetTaskQueue().GetName()) + s.Equal(enumspb.TASK_QUEUE_KIND_NORMAL, request.GetTaskQueue().GetKind()) + } + return &matchingservice.AddWorkflowTaskResponse{}, nil + }).Times(calls) - resp := s.transferQueueActiveTaskExecutor.Execute(context.Background(), s.newTaskExecutable(transferTask)) - s.NoError(resp.ExecutionErr) + resp := s.transferQueueActiveTaskExecutor.Execute(context.Background(), s.newTaskExecutable(transferTask)) + s.Require().NoError(resp.ExecutionErr) + }) + } } func (s *transferQueueActiveTaskExecutorSuite) TestProcessWorkflowTask_WorkflowTaskNotSticky_MutableStateSticky() { diff --git a/service/history/workflow/mutable_state_impl.go b/service/history/workflow/mutable_state_impl.go index c0f858b039a..7d3ce890269 100644 --- a/service/history/workflow/mutable_state_impl.go +++ b/service/history/workflow/mutable_state_impl.go @@ -1468,14 +1468,37 @@ func (ms *MutableStateImpl) CurrentTaskQueue() *taskqueuepb.TaskQueue { } } +func (ms *MutableStateImpl) SetSessionState(state *persistencespb.SessionState) { + current := ms.executionInfo.GetSessionState() + if current.GetExclusiveWorker() && + (!state.GetExclusiveWorker() || state.GetWorkerQueue() != current.GetWorkerQueue()) { + softassert.Fail(ms.logger, "attempted to replace an exclusive worker binding", + tag.WorkflowTaskQueueName(current.GetWorkerQueue()), + tag.NewStringTag("requested-task-queue", state.GetWorkerQueue()), + ) + return + } + ms.executionInfo.SessionState = state + // Note: executionInfo.StickyTaskQueue and SessionState.WorkerQueue are mirror values of each other + ms.SetStickyTaskQueue(state.GetWorkerQueue(), nil) +} + func (ms *MutableStateImpl) SetStickyTaskQueue(name string, scheduleToStartTimeout *durationpb.Duration) { ms.executionInfo.StickyTaskQueue = name ms.executionInfo.StickyScheduleToStartTimeout = scheduleToStartTimeout } +// ClearStickyTaskQueue does not clear the sticky task queue if the workflow is bound to an exclusive worker. +// Currently, executionInfo.StickyTaskQueue and SessionState.WorkerQueue are mirrors of each other. func (ms *MutableStateImpl) ClearStickyTaskQueue() { + if ms.executionInfo.GetSessionState().GetExclusiveWorker() { + return + } ms.executionInfo.StickyTaskQueue = "" ms.executionInfo.StickyScheduleToStartTimeout = nil + if sessionState := ms.executionInfo.GetSessionState(); sessionState != nil { + sessionState.WorkerQueue = "" + } } func (ms *MutableStateImpl) IsStickyTaskQueueSet() bool { @@ -1487,6 +1510,12 @@ func (ms *MutableStateImpl) IsStickyTaskQueueSet() bool { // in mutable state and provided name. // ScheduleToStartTimeout is set based on queue kind and workflow task type. func (ms *MutableStateImpl) TaskQueueScheduleToStartTimeout(tqName string) (*taskqueuepb.TaskQueue, *durationpb.Duration) { + // If the workflow is bound to an exclusive worker, then the schedule to start timeout is + // the WorkflowRunTimeout. This is because unlike regular sticky tasks, exclusive worker tasks do not fall back + // on the normal task queue. + if ms.executionInfo.GetSessionState().GetExclusiveWorker() { + return ms.CurrentTaskQueue(), ms.executionInfo.WorkflowRunTimeout + } isStickyTq := ms.executionInfo.StickyTaskQueue == tqName if isStickyTq { return &taskqueuepb.TaskQueue{ diff --git a/service/history/workflow/mutable_state_impl_test.go b/service/history/workflow/mutable_state_impl_test.go index 8e6423a2583..e1b4b53d254 100644 --- a/service/history/workflow/mutable_state_impl_test.go +++ b/service/history/workflow/mutable_state_impl_test.go @@ -57,6 +57,7 @@ import ( serviceerror2 "go.temporal.io/server/common/serviceerror" "go.temporal.io/server/common/testing/fakedata" "go.temporal.io/server/common/testing/protorequire" + "go.temporal.io/server/common/testing/testlogger" "go.temporal.io/server/common/testing/testvars" "go.temporal.io/server/common/tqid" "go.temporal.io/server/common/worker_versioning" @@ -6280,6 +6281,7 @@ func (s *mutableStateSuite) verifyExecutionInfo(current, target, origin *persist s.True(proto.Equal(target.LastUpdateTime, current.LastUpdateTime), "LastUpdateTime mismatch") s.Equal(target.CancelRequested, current.CancelRequested, "CancelRequested mismatch") s.Equal(target.CancelRequestId, current.CancelRequestId, "CancelRequestId mismatch") + s.True(proto.Equal(target.GetSessionState(), current.GetSessionState()), "SessionState mismatch") s.Equal(target.StickyTaskQueue, current.StickyTaskQueue, "StickyTaskQueue mismatch") s.True(proto.Equal(target.StickyScheduleToStartTimeout, current.StickyScheduleToStartTimeout), "StickyScheduleToStartTimeout mismatch") s.Equal(target.Attempt, current.Attempt, "Attempt mismatch") @@ -10200,3 +10202,187 @@ func (s *mutableStateSuite) TestCloseTransactionInvalidateChasmTasksOnClose() { s.NoError(s.mutableState.closeTransactionInvalidateChasmTasksOnClose(context.Background(), historyi.TransactionPolicyActive)) }) } + +/* +TestSingleSessionBindingReloadAndFailure tests binding across a task failure and mutable-state reload: +1. Bind the workflow to S, start its workflow task, and record a start-to-close timeout. +2. Reload mutable state and verify clearing stickiness leaves the exclusive destination intact. +3. Schedule the transient retry on S with the increased attempt and no schedule-to-start timer. +4. Start the retry on a replacement worker and verify its start-to-close timer is created. +*/ +func (s *mutableStateSuite) TestSingleSessionBindingReloadAndFailure() { + s.mockEventsCache.EXPECT().PutEvent(gomock.Any(), gomock.Any()).AnyTimes() + ms := TestGlobalMutableState(s.mockShard, s.mockEventsCache, s.logger, tests.Version, tests.WorkflowID, tests.RunID) + _, err := ms.AddWorkflowExecutionStartedEvent( + &commonpb.WorkflowExecution{WorkflowId: tests.WorkflowID, RunId: tests.RunID}, + &historyservice.StartWorkflowExecutionRequest{ + NamespaceId: tests.NamespaceID.String(), + StartRequest: &workflowservice.StartWorkflowExecutionRequest{ + WorkflowId: tests.WorkflowID, WorkflowType: &commonpb.WorkflowType{Name: "workflow"}, + TaskQueue: &taskqueuepb.TaskQueue{Name: "foo"}, + WorkflowRunTimeout: durationpb.New(time.Hour), WorkflowTaskTimeout: durationpb.New(time.Second), + }, + }) + s.Require().NoError(err) + ms.SetSessionState(&persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "S"}) + ms.executionInfo.DefaultWorkflowTaskTimeout = durationpb.New(time.Second) + ms.executionInfo.WorkflowTaskAttempt = 1 + wt, err := ms.AddWorkflowTaskScheduledEvent(false, enumsspb.WORKFLOW_TASK_TYPE_NORMAL) + s.Require().NoError(err) + s.Equal("S", wt.TaskQueue.GetName()) + _, wt, err = ms.AddWorkflowTaskStartedEvent(wt.ScheduledEventID, "request", ms.CurrentTaskQueue(), "worker-1", nil, nil, nil, false, nil, 0) + s.Require().NoError(err) + _, err = ms.AddWorkflowTaskTimedOutEvent(wt) + s.Require().NoError(err) + s.Equal(int32(2), ms.GetExecutionInfo().GetWorkflowTaskAttempt()) + s.Equal("S", ms.CurrentTaskQueue().GetName()) + _, _, err = ms.CloseTransactionAsSnapshot(context.Background(), historyi.TransactionPolicyActive) + s.Require().NoError(err) + reloaded, err := NewMutableStateFromDB(s.mockShard, s.mockEventsCache, s.logger, tests.GlobalNamespaceEntry, ms.CloneToProto(), 123) + s.Require().NoError(err) + s.True(proto.Equal(ms.GetExecutionInfo().GetSessionState(), reloaded.GetExecutionInfo().GetSessionState())) + s.Require().NoError(reloaded.UpdateCurrentVersion(tests.Version, false)) + reloaded.ClearStickyTaskQueue() + s.Equal("S", reloaded.CurrentTaskQueue().GetName()) + s.Equal("S", reloaded.GetExecutionInfo().GetSessionState().GetWorkerQueue()) + wt, err = reloaded.AddWorkflowTaskScheduledEvent(false, enumsspb.WORKFLOW_TASK_TYPE_NORMAL) + s.Require().NoError(err) + s.Equal("S", wt.TaskQueue.GetName()) + s.Equal("foo", reloaded.executionInfo.GetTaskQueue()) + s.True(reloaded.IsTransientWorkflowTask()) + failureCount := reloaded.GetExecutionInfo().GetWorkflowTaskAttemptsSinceLastSuccess() + s.Equal(int32(1), failureCount) + + mutation, eventBatches, err := reloaded.CloseTransactionAsMutation(context.Background(), historyi.TransactionPolicyActive) + s.Require().NoError(err) + s.Empty(eventBatches) + s.Empty(mutation.Tasks[tasks.CategoryTimer]) + s.Require().Len(mutation.Tasks[tasks.CategoryTransfer], 1) + transferTask, ok := mutation.Tasks[tasks.CategoryTransfer][0].(*tasks.WorkflowTask) + s.Require().True(ok) + s.Equal("S", transferTask.TaskQueue) + s.Equal(wt.ScheduledEventID, transferTask.ScheduledEventID) + s.Equal(int32(2), wt.Attempt) + + _, resumed, err := reloaded.AddWorkflowTaskStartedEvent(wt.ScheduledEventID, "replacement-request", reloaded.CurrentTaskQueue(), "worker-2", nil, nil, nil, false, nil, 0) + s.Require().NoError(err) + s.True(reloaded.HasStartedWorkflowTask()) + s.Equal("S", resumed.TaskQueue.GetName()) + s.Equal(int32(2), resumed.Attempt) + s.Equal(failureCount, reloaded.GetExecutionInfo().GetWorkflowTaskAttemptsSinceLastSuccess()) + mutation, _, err = reloaded.CloseTransactionAsMutation(context.Background(), historyi.TransactionPolicyActive) + s.Require().NoError(err) + s.Require().Len(mutation.Tasks[tasks.CategoryTimer], 1) + startedTimeout, ok := mutation.Tasks[tasks.CategoryTimer][0].(*tasks.WorkflowTaskTimeoutTask) + s.Require().True(ok) + s.Equal(enumspb.TIMEOUT_TYPE_START_TO_CLOSE, startedTimeout.TimeoutType) +} + +/* +TestSetSessionStatePreservesExclusiveBinding tests updates to an existing exclusive binding: +1. Bind the workflow to worker queue S and its matching sticky destination. +2. Accept the same binding, but report and reject a different queue, nonexclusive state, or nil state. +3. Preserve destination S and the normal workflow task queue throughout. +*/ +func TestSetSessionStatePreservesExclusiveBinding(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + state *persistencespb.SessionState + rejected bool + }{ + {name: "same binding", state: &persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "S"}}, + {name: "different queue", state: &persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "other"}, rejected: true}, + {name: "nonexclusive state", state: &persistencespb.SessionState{WorkerQueue: "S"}, rejected: true}, + {name: "cleared state", rejected: true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + logger := testlogger.NewTestLogger(t, testlogger.FailOnAnyUnexpectedError) + ms := &MutableStateImpl{logger: logger, executionInfo: &persistencespb.WorkflowExecutionInfo{TaskQueue: "foo"}} + initial := &persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "S"} + ms.SetSessionState(initial) + var assertion *testlogger.Expectation + if tc.rejected { + assertion = logger.Expect(testlogger.Error, "failed assertion: attempted to replace an exclusive worker binding") + } + + ms.SetSessionState(tc.state) + + if tc.rejected { + require.EqualValues(t, 1, assertion.MatchCount()) + require.Same(t, initial, ms.GetExecutionInfo().GetSessionState()) + } else { + require.Same(t, tc.state, ms.GetExecutionInfo().GetSessionState()) + } + require.Equal(t, "S", ms.GetExecutionInfo().GetStickyTaskQueue()) + require.Equal(t, "foo", ms.GetExecutionInfo().GetTaskQueue()) + }) + } +} + +/* +TestSingleSessionStickyPolicy tests sticky state for ordinary and exclusive workflows: +1. Set an exclusive session and verify its worker queue initializes sticky destination S. +2. Resolve either queue name to S with the workflow run timeout for an exclusive workflow. +3. Keep S when stickiness is cleared; ordinary workflows still allow sticky updates and clearing. +4. Preserve the normal workflow task queue foo throughout. +*/ +func TestSingleSessionStickyPolicy(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + bound bool + stickyQueue string + }{ + {name: "ordinary", stickyQueue: "S"}, + {name: "exclusive session", bound: true, stickyQueue: "S"}, + {name: "session initializes sticky destination", bound: true, stickyQueue: "other"}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + ms := &MutableStateImpl{executionInfo: &persistencespb.WorkflowExecutionInfo{ + TaskQueue: "foo", StickyTaskQueue: tc.stickyQueue, + StickyScheduleToStartTimeout: durationpb.New(time.Second), + WorkflowRunTimeout: durationpb.New(time.Hour), + }} + if tc.bound { + state := &persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "S"} + ms.SetSessionState(state) + protorequire.ProtoEqual(t, state, ms.executionInfo.GetSessionState()) + require.Equal(t, "S", ms.executionInfo.GetStickyTaskQueue()) + require.Nil(t, ms.executionInfo.GetStickyScheduleToStartTimeout()) + ms.SetStickyTaskQueue("S", durationpb.New(3*time.Second)) + require.Equal(t, 3*time.Second, ms.executionInfo.GetStickyScheduleToStartTimeout().AsDuration()) + for _, queueName := range []string{"foo", "S"} { + queue, timeout := ms.TaskQueueScheduleToStartTimeout(queueName) + require.Equal(t, "S", queue.GetName()) + require.Equal(t, "foo", queue.GetNormalName()) + require.Equal(t, enumspb.TASK_QUEUE_KIND_STICKY, queue.GetKind()) + require.Equal(t, time.Hour, timeout.AsDuration()) + } + } else { + ms.SetStickyTaskQueue("other", durationpb.New(2*time.Second)) + require.Equal(t, "other", ms.executionInfo.GetStickyTaskQueue()) + } + if tc.bound { + require.Equal(t, ms.executionInfo.GetStickyTaskQueue(), ms.executionInfo.GetSessionState().GetWorkerQueue()) + } else { + require.Nil(t, ms.executionInfo.GetSessionState()) + } + ms.ClearStickyTaskQueue() + if tc.bound { + require.Equal(t, "S", ms.executionInfo.GetStickyTaskQueue()) + } else { + require.Empty(t, ms.executionInfo.GetStickyTaskQueue()) + require.Nil(t, ms.executionInfo.GetStickyScheduleToStartTimeout()) + } + if tc.bound { + require.Equal(t, ms.executionInfo.GetStickyTaskQueue(), ms.executionInfo.GetSessionState().GetWorkerQueue()) + } else { + require.Nil(t, ms.executionInfo.GetSessionState()) + } + require.Equal(t, "foo", ms.executionInfo.GetTaskQueue()) + }) + } +} diff --git a/service/history/workflow/task_generator.go b/service/history/workflow/task_generator.go index ed4158ffafd..0a5f26a3f96 100644 --- a/service/history/workflow/task_generator.go +++ b/service/history/workflow/task_generator.go @@ -432,8 +432,11 @@ func (r *TaskGeneratorImpl) GenerateScheduleWorkflowTaskTasks( return serviceerror.NewInternalf("it could be a bug, GenerateScheduleSpeculativeWorkflowTaskTasks must be called for speculative workflow task: %v", workflowTaskScheduledEventID) } - if r.mutableState.IsStickyTaskQueueSet() { - scheduleToStartTimeout := timestamp.DurationValue(r.mutableState.GetExecutionInfo().StickyScheduleToStartTimeout) + // Bound tasks wait for a replacement worker on the same queue, so an S2S timer would only recreate them which is + // why this timer is created only for non-exclusive workflow tasks. + if r.mutableState.IsStickyTaskQueueSet() && !r.mutableState.GetExecutionInfo().GetSessionState().GetExclusiveWorker() { + _, timeout := r.mutableState.TaskQueueScheduleToStartTimeout(workflowTask.TaskQueue.GetName()) + scheduleToStartTimeout := timeout.AsDuration() wttt := &tasks.WorkflowTaskTimeoutTask{ // TaskID is set by shard WorkflowKey: r.mutableState.GetWorkflowKey(), @@ -473,10 +476,11 @@ func (r *TaskGeneratorImpl) GenerateScheduleSpeculativeWorkflowTaskTasks( ) error { var scheduleToStartTimeout time.Duration - if r.mutableState.IsStickyTaskQueueSet() { - scheduleToStartTimeout = timestamp.DurationValue(r.mutableState.GetExecutionInfo().StickyScheduleToStartTimeout) + if r.mutableState.IsStickyTaskQueueSet() && !r.mutableState.GetExecutionInfo().GetSessionState().GetExclusiveWorker() { + _, timeout := r.mutableState.TaskQueueScheduleToStartTimeout(workflowTask.TaskQueue.GetName()) + scheduleToStartTimeout = timeout.AsDuration() } else { - // Speculative WT has ScheduleToStart timeout even on normal task queue. + // Speculative WT needs a finite recovery timeout even on normal and single-session queues. // Normally WT should be added to matching right after being created // (i.e. from UpdateWorkflowExecution API handler), but if this "add" operation failed, // there is no good way to handle the error. @@ -484,6 +488,8 @@ func (r *TaskGeneratorImpl) GenerateScheduleSpeculativeWorkflowTaskTasks( // and new normal WT will be created. // Note: this timer will also fire if workflow received an update, // but there is no workers available. Speculative WT will time out, and normal WT will be created. + // Note: For speculative workflow tasks attached to an exclusive worker, this block of code assigns a default of a 5 second + // S2S timer timeout. This is required so that it's retries can be executed effectively. scheduleToStartTimeout = tasks.SpeculativeWorkflowTaskScheduleToStartTimeout } diff --git a/service/history/workflow/task_generator_test.go b/service/history/workflow/task_generator_test.go index 38f2dff1fa2..ae945ab04ed 100644 --- a/service/history/workflow/task_generator_test.go +++ b/service/history/workflow/task_generator_test.go @@ -13,6 +13,7 @@ import ( commonpb "go.temporal.io/api/common/v1" enumspb "go.temporal.io/api/enums/v1" historypb "go.temporal.io/api/history/v1" + taskqueuepb "go.temporal.io/api/taskqueue/v1" workerpb "go.temporal.io/api/worker/v1" enumsspb "go.temporal.io/server/api/enums/v1" historyspb "go.temporal.io/server/api/history/v1" @@ -2101,3 +2102,83 @@ func TestTaskGeneratorImpl_RegenerateTimerTasksForTimeSkipping_AllFieldsPopulate require.Truef(t, seen[name], "expected RegenerateTimerTasksForTimeSkipping to emit a %s", name) } } + +/* +TestSingleSessionWorkflowTaskTimers tests ordinary and speculative workflow-task timeout generation: +1. Schedule tasks on S for ordinary and exclusive workflows with different run timeout settings. +2. Create a schedule-to-start timer only for the ordinary task, while both receive a transfer task. +3. Keep finite recovery timers for speculative tasks and their conversion to durable normal tasks. +*/ +func TestSingleSessionWorkflowTaskTimers(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + bound bool + runTimeout *durationpb.Duration + }{ + {name: "ordinary", runTimeout: durationpb.New(time.Hour)}, + {name: "single session with run timeout", bound: true, runTimeout: durationpb.New(time.Hour)}, + {name: "single session without run timeout", bound: true}, + {name: "single session with zero run timeout", bound: true, runTimeout: durationpb.New(0)}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + ms := historyi.NewMockMutableState(ctrl) + key := definition.NewWorkflowKey("namespace", "workflow", "run") + info := &persistencespb.WorkflowExecutionInfo{ + StickyScheduleToStartTimeout: durationpb.New(time.Second), + WorkflowRunTimeout: tc.runTimeout, + } + if tc.bound { + info.SessionState = &persistencespb.SessionState{ExclusiveWorker: true, WorkerQueue: "S"} + } + ms.EXPECT().GetExecutionInfo().Return(info).AnyTimes() + ms.EXPECT().IsStickyTaskQueueSet().Return(true).AnyTimes() + ms.EXPECT().GetWorkflowKey().Return(key).AnyTimes() + wt := &historyi.WorkflowTaskInfo{ + ScheduledEventID: 5, ScheduledTime: time.Now(), Attempt: 1, + TaskQueue: &taskqueuepb.TaskQueue{Name: "S", Kind: enumspb.TASK_QUEUE_KIND_STICKY}, + Type: enumsspb.WORKFLOW_TASK_TYPE_NORMAL, + } + ms.EXPECT().GetWorkflowTaskByID(int64(5)).Return(wt) + if !tc.bound { + ms.EXPECT().TaskQueueScheduleToStartTimeout("S").Return(wt.TaskQueue, info.GetStickyScheduleToStartTimeout()).Times(3) + ms.EXPECT().AddTasks(gomock.AssignableToTypeOf(&tasks.WorkflowTaskTimeoutTask{})).Do(func(task tasks.Task) { + timer := task.(*tasks.WorkflowTaskTimeoutTask) + require.Equal(t, wt.ScheduledTime.Add(time.Second), timer.VisibilityTimestamp) + require.Equal(t, enumspb.TIMEOUT_TYPE_SCHEDULE_TO_START, timer.TimeoutType) + }) + ms.EXPECT().SetWorkflowTaskScheduleToStartTimeoutTask(gomock.Any()) + } + ms.EXPECT().AddTasks(gomock.AssignableToTypeOf(&tasks.WorkflowTask{})).Do(func(task tasks.Task) { + require.Equal(t, "S", task.(*tasks.WorkflowTask).TaskQueue) + }) + generator := &TaskGeneratorImpl{mutableState: ms} + require.NoError(t, generator.GenerateScheduleWorkflowTaskTasks(5)) + + wt.Type = enumsspb.WORKFLOW_TASK_TYPE_SPECULATIVE + expectedRecoveryTimeout := time.Second + if tc.bound { + expectedRecoveryTimeout = tasks.SpeculativeWorkflowTaskScheduleToStartTimeout + } + ms.EXPECT().SetSpeculativeWorkflowTaskTimeoutTask(gomock.Any()).DoAndReturn(func(task *tasks.WorkflowTaskTimeoutTask) error { + require.Equal(t, wt.ScheduledTime.Add(expectedRecoveryTimeout), task.VisibilityTimestamp) + require.Equal(t, enumspb.TIMEOUT_TYPE_SCHEDULE_TO_START, task.TimeoutType) + require.True(t, task.InMemory) + return nil + }) + require.NoError(t, generator.GenerateScheduleSpeculativeWorkflowTaskTasks(wt)) + + wt.Type = enumsspb.WORKFLOW_TASK_TYPE_NORMAL + ms.EXPECT().AddTasks(gomock.AssignableToTypeOf(&tasks.WorkflowTaskTimeoutTask{})).Do(func(task tasks.Task) { + timer := task.(*tasks.WorkflowTaskTimeoutTask) + require.Equal(t, wt.ScheduledTime.Add(expectedRecoveryTimeout), timer.VisibilityTimestamp) + require.Equal(t, enumspb.TIMEOUT_TYPE_SCHEDULE_TO_START, timer.TimeoutType) + require.False(t, timer.InMemory) + }) + ms.EXPECT().SetWorkflowTaskScheduleToStartTimeoutTask(gomock.Any()) + require.NoError(t, generator.GenerateScheduleSpeculativeWorkflowTaskTasks(wt)) + }) + } +} diff --git a/service/history/workflow/workflow_task_state_machine.go b/service/history/workflow/workflow_task_state_machine.go index d7494abe1a8..85924ee891f 100644 --- a/service/history/workflow/workflow_task_state_machine.go +++ b/service/history/workflow/workflow_task_state_machine.go @@ -279,8 +279,6 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskScheduleToStartTimeoutEvent( if workflowTask.Type == enumsspb.WORKFLOW_TASK_TYPE_SPECULATIVE { m.ms.RemoveSpeculativeWorkflowTaskTimeoutTask() - - // Create corresponding WorkflowTaskScheduled event for speculative WT. scheduledEvent := m.ms.hBuilder.AddWorkflowTaskScheduledEvent( m.ms.CurrentTaskQueue(), durationpb.New(workflowTask.WorkflowTaskTimeout), @@ -1047,9 +1045,10 @@ func (m *workflowTaskStateMachine) failWorkflowTask( currentWorkflowTask := m.getWorkflowTaskInfo() m.recordTimeoutTasksForDeletion(currentWorkflowTask) - // Increment attempts only if workflow task is failing on non-sticky task queue. - // If it was sticky task queue, clear sticky task queue first and try again before creating transient workflow task. - if m.ms.IsStickyTaskQueueSet() { + // Ordinary sticky failures get one retry on the normal queue before creating a transient task. + // Single-session failures retain their destination and increment attempts. This is because single-session + // workflow and activity tasks, bar the very first one, are only placed on the sticky queue. + if m.ms.IsStickyTaskQueueSet() && !m.ms.executionInfo.GetSessionState().GetExclusiveWorker() { incrementAttempt = false m.ms.ClearStickyTaskQueue() } diff --git a/service/matching/backlog_manager_test.go b/service/matching/backlog_manager_test.go index bf9bf370623..aaa5f2d2922 100644 --- a/service/matching/backlog_manager_test.go +++ b/service/matching/backlog_manager_test.go @@ -14,11 +14,13 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" commonpb "go.temporal.io/api/common/v1" enumspb "go.temporal.io/api/enums/v1" persistencespb "go.temporal.io/server/api/persistence/v1" "go.temporal.io/server/common/dynamicconfig" + "go.temporal.io/server/common/log" "go.temporal.io/server/common/metrics" "go.temporal.io/server/common/metrics/metricstest" "go.temporal.io/server/common/persistence" @@ -27,10 +29,12 @@ import ( "go.temporal.io/server/common/testing/await" "go.temporal.io/server/common/testing/protorequire" "go.temporal.io/server/common/testing/testlogger" + "go.temporal.io/server/common/testing/testvars" "go.temporal.io/server/common/tqid" "go.temporal.io/server/common/util" "go.temporal.io/server/service/matching/counter" "go.uber.org/mock/gomock" + "google.golang.org/protobuf/encoding/protowire" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -1322,3 +1326,112 @@ func (s *BacklogManagerTestSuite) TestFairReaderReMergeOfCompletedWriteKeepsRead s.Require().Equal(2, s.capturedTasksLen(), "reader should still deliver new tasks") s.Require().Equal(1, tr.getLoadedTasks()) } + +/* +TestTaskQueueSingleSessionMetadata tests assignment metadata across repeated reloads: +1. Create assigned and unassigned sticky workflow queues with an expiry. +2. Reload each queue, update its ack level, and synchronize metadata three times. +3. Preserve assignment data, including unknown fields, and omit expiry only for assigned queues. +*/ +func TestTaskQueueSingleSessionMetadata(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + assigned bool + }{ + {name: "unassigned"}, + {name: "assigned", assigned: true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + tv := testvars.New(t) + logger := log.NewTestLogger() + store := newTestTaskManager(logger) + partition := newTestTaskQueue(tv.NamespaceID().String(), tv.TaskQueue().GetName(), enumspb.TASK_QUEUE_TYPE_WORKFLOW). + StickyPartition(tv.StickyTaskQueue().GetName()) + queue := UnversionedQueueKey(partition) + cfg := newTaskQueueConfig(partition.TaskQueue(), NewConfig(dynamicconfig.NewCollection(dynamicconfig.NewMemoryClient(), logger)), tv.NamespaceName()) + info := &persistencespb.TaskQueueInfo{ + NamespaceId: queue.NamespaceId(), + Name: queue.PersistenceName(), + TaskType: queue.TaskType(), + Kind: enumspb.TASK_QUEUE_KIND_STICKY, + LastUpdateTime: timestamppb.Now(), + ExpiryTime: timestamppb.New(time.Now().Add(time.Hour)), + } + if tc.assigned { + info.ExclusiveWorkerAssignment = &persistencespb.ExclusiveWorkerAssignment{} + unknownField := protowire.AppendTag(nil, 1, protowire.BytesType) + unknownField = protowire.AppendString(unknownField, "future-session-reference") + info.GetExclusiveWorkerAssignment().ProtoReflect().SetUnknown(unknownField) + } + _, err := store.CreateTaskQueue(t.Context(), &persistence.CreateTaskQueueRequest{ + RangeID: 1, TaskQueueInfo: info, + }) + require.NoError(t, err) + + for iteration := range 3 { + db := newTaskQueueDB(cfg, store, queue, logger, metrics.NoopMetricsHandler, false) + _, err = db.RenewLease(t.Context()) + require.NoError(t, err) + db.updateAckLevelAndBacklogStats(subqueueZero, int64(iteration+1), 0, time.Time{}) + require.NoError(t, db.SyncState(t.Context())) + + persisted, err := store.GetTaskQueue(t.Context(), &persistence.GetTaskQueueRequest{ + NamespaceID: queue.NamespaceId(), TaskQueue: queue.PersistenceName(), TaskType: queue.TaskType(), + }) + require.NoError(t, err) + require.Equal(t, tc.assigned, persisted.TaskQueueInfo.GetExclusiveWorkerAssignment() != nil) + require.Equal(t, int64(iteration+1), persisted.TaskQueueInfo.GetAckLevel()) + if tc.assigned { + protorequire.ProtoEqual(t, info.GetExclusiveWorkerAssignment(), persisted.TaskQueueInfo.GetExclusiveWorkerAssignment()) + require.Nil(t, persisted.TaskQueueInfo.GetExpiryTime()) + } else { + require.NotNil(t, persisted.TaskQueueInfo.GetExpiryTime()) + require.True(t, persisted.TaskQueueInfo.GetExpiryTime().AsTime().After(time.Now())) + } + } + }) + } +} + +/* +TestSingleSessionAssignmentPersists tests a durable assignment without a lease change: +1. Load an unassigned sticky workflow queue and persist its assignment. +2. Verify the assignment has no expiry and RangeID is unchanged, including when the assignment call is repeated. +3. Reload the queue and verify its assignment is still present. +*/ +func TestSingleSessionAssignmentPersists(t *testing.T) { + t.Parallel() + tv := testvars.New(t) + logger := log.NewTestLogger() + store := newTestTaskManager(logger) + partition := newTestTaskQueue(tv.NamespaceID().String(), tv.TaskQueue().GetName(), enumspb.TASK_QUEUE_TYPE_WORKFLOW).StickyPartition(tv.StickyTaskQueue().GetName()) + queue := UnversionedQueueKey(partition) + cfg := newTaskQueueConfig(partition.TaskQueue(), NewConfig(dynamicconfig.NewNoopCollection()), tv.NamespaceName()) + db := newTaskQueueDB(cfg, store, queue, logger, metrics.NoopMetricsHandler, false) + _, err := db.RenewLease(t.Context()) + require.NoError(t, err) + require.False(t, db.ExclusiveWorkerAssigned()) + rangeID := db.RangeID() + + err = db.assignExclusiveWorker(t.Context()) + require.NoError(t, err) + persisted, err := store.GetTaskQueue(t.Context(), &persistence.GetTaskQueueRequest{ + NamespaceID: queue.NamespaceId(), TaskQueue: queue.PersistenceName(), TaskType: queue.TaskType(), + }) + require.NoError(t, err) + require.NotNil(t, persisted.TaskQueueInfo.GetExclusiveWorkerAssignment()) + require.Nil(t, persisted.TaskQueueInfo.GetExpiryTime()) + require.Equal(t, rangeID, persisted.RangeID, "assignment preserves the writer's allocated ID block") + + err = db.assignExclusiveWorker(t.Context()) + require.NoError(t, err) + require.Equal(t, persisted.RangeID, db.RangeID()) + + reloaded := newTaskQueueDB(cfg, store, queue, logger, metrics.NoopMetricsHandler, false) + _, err = reloaded.RenewLease(t.Context()) + require.NoError(t, err) + require.True(t, reloaded.ExclusiveWorkerAssigned()) + require.NoError(t, reloaded.SyncState(t.Context())) +} diff --git a/service/matching/db.go b/service/matching/db.go index abc8f891c98..23d32ef6025 100644 --- a/service/matching/db.go +++ b/service/matching/db.go @@ -44,10 +44,11 @@ type ( // mutable sync.Mutex - rangeID int64 - subqueues []*dbSubqueue - otherHasTasks bool - scaleState *persistencespb.PartitionScaleState + rangeID int64 + subqueues []*dbSubqueue + otherHasTasks bool + scaleState *persistencespb.PartitionScaleState + exclusiveWorkerAssignment *persistencespb.ExclusiveWorkerAssignment // used to avoid unnecessary metadata writes: lastChange time.Time // updated when metadata is changed in memory @@ -110,6 +111,26 @@ func newTaskQueueDB( } } +func (db *taskQueueDB) ExclusiveWorkerAssigned() bool { + db.Lock() + defer db.Unlock() + return db.exclusiveWorkerAssignment != nil +} + +func (db *taskQueueDB) assignExclusiveWorker(ctx context.Context) error { + db.Lock() + defer db.Unlock() + if db.exclusiveWorkerAssignment != nil { + return nil + } + db.exclusiveWorkerAssignment = &persistencespb.ExclusiveWorkerAssignment{} + db.lastChange = time.Now() + // TODO (Shivam): Protect against a delayed scanner deleting this record using a pre-assignment + // snapshot with the same RangeID. Keep the range unchanged here so the writer retains its ID block. + // Not super urgent to fix since the probability of this happening is quite low. + return db.updateTaskQueueLocked(ctx, false) +} + // RangeID returns the current persistence view of rangeID func (db *taskQueueDB) RangeID() int64 { db.Lock() @@ -191,6 +212,7 @@ func (db *taskQueueDB) takeOverTaskQueueLocked( response.TaskQueueInfo.ApproximateBacklogCount, ) db.scaleState = response.TaskQueueInfo.PartitionScaleState + db.exclusiveWorkerAssignment = response.TaskQueueInfo.GetExclusiveWorkerAssignment() err := db.updateTaskQueueLocked(ctx, true) if err != nil { db.rangeID = 0 @@ -801,19 +823,26 @@ func (db *taskQueueDB) cachedQueueInfo() *persistencespb.TaskQueueInfo { for i := range db.subqueues { infos[i] = &db.subqueues[i].SubqueueInfo } - return &persistencespb.TaskQueueInfo{ - NamespaceId: db.queue.NamespaceId(), - Name: db.queue.PersistenceName(), - TaskType: db.queue.TaskType(), - Kind: db.queue.Partition().Kind(), - AckLevel: db.subqueues[subqueueZero].AckLevel, // backwards compatibility - ExpiryTime: db.expiryTime(), - LastUpdateTime: timestamp.TimeNowPtrUtc(), - ApproximateBacklogCount: db.subqueues[subqueueZero].ApproximateBacklogCount, // backwards compatibility - Subqueues: infos, - OtherHasTasks: db.otherHasTasks, - PartitionScaleState: db.scaleState, - } + info := &persistencespb.TaskQueueInfo{ + NamespaceId: db.queue.NamespaceId(), + Name: db.queue.PersistenceName(), + TaskType: db.queue.TaskType(), + Kind: db.queue.Partition().Kind(), + AckLevel: db.subqueues[subqueueZero].AckLevel, // backwards compatibility + LastUpdateTime: timestamp.TimeNowPtrUtc(), + ApproximateBacklogCount: db.subqueues[subqueueZero].ApproximateBacklogCount, // backwards compatibility + Subqueues: infos, + OtherHasTasks: db.otherHasTasks, + PartitionScaleState: db.scaleState, + ExclusiveWorkerAssignment: db.exclusiveWorkerAssignment, + } + // Retain metadata for assigned exclusive workers so queue reloads cannot allow + // the same destination to acquire another workflow, even after its workflow completes. + // TODO (Shivam) -- more thinking and discussion required on the scavenging of this task queue metadata. + if info.GetExclusiveWorkerAssignment() == nil { + info.ExpiryTime = db.expiryTime() + } + return info } // emitPhysicalBacklogGaugesLocked emits backlog gauges tagged by priority key, along with diff --git a/service/matching/forwarder_test.go b/service/matching/forwarder_test.go index 8c782dded85..6910d2064ad 100644 --- a/service/matching/forwarder_test.go +++ b/service/matching/forwarder_test.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/suite" enumspb "go.temporal.io/api/enums/v1" + sessionpb "go.temporal.io/api/session/v1" "go.temporal.io/api/workflowservice/v1" enumsspb "go.temporal.io/server/api/enums/v1" "go.temporal.io/server/api/matchingservice/v1" @@ -252,6 +253,48 @@ func (t *ForwarderTestSuite) TestForwardPollWorkflowTaskQueue() { t.Nil(task.pollActivityTaskQueueResponse()) } +/* +TestForwardPollWithTargetSource tests the source sent with forwarded polls: +1. Forward ordinary and acquisition polls from sticky and normal partitions. +2. Verify only sticky acquisition clears the source so the client can select a normal read partition. +3. Verify every request names the target queue and returns the received workflow task. +*/ +func (t *ForwarderTestSuite) TestForwardPollWithTargetSource() { + tq := t.partition.TaskQueue() + normal := tq.NormalPartition(1) + sticky := tq.StickyPartition("session") + for _, tc := range []struct { + name string + source tqid.Partition + acquisition bool + expectedSource string + }{ + {name: "ordinary sticky", source: sticky, expectedSource: sticky.RpcName()}, + {name: "sticky acquisition", source: sticky, acquisition: true}, + {name: "ordinary normal", source: normal, expectedSource: normal.RpcName()}, + {name: "normal acquisition", source: normal, acquisition: true, expectedSource: normal.RpcName()}, + } { + t.Run(tc.name, func() { + var request *matchingservice.PollWorkflowTaskQueueRequest + response := &matchingservice.PollWorkflowTaskQueueResponse{TaskToken: []byte("token")} + t.client.EXPECT().PollWorkflowTaskQueue(gomock.Any(), gomock.Any(), gomock.Any()).Do( + func(_ context.Context, req *matchingservice.PollWorkflowTaskQueueRequest, _ ...any) { + request = req + }, + ).Return(response, nil) + task, err := ForwardPollWithTarget(t.T().Context(), &pollMetadata{ + singleSessionAcquisitionInProgress: tc.acquisition, + }, t.client, tc.source, tq.RootPartition()) + t.Require().NoError(err) + t.Require().NotNil(task) + t.Require().NotNil(request) + t.Require().Equal(tc.expectedSource, request.GetForwardedSource()) + t.Require().Equal(tq.RootPartition().RpcName(), request.GetPollRequest().GetTaskQueue().GetName()) + t.Require().Same(response, task.pollWorkflowTaskQueueResponse()) + }) + } +} + func (t *ForwarderTestSuite) TestForwardPollWorkflowTaskQueuePreservesWorkerInstanceKey() { t.usingTaskqueuePartition(enumspb.TASK_QUEUE_TYPE_WORKFLOW) @@ -270,10 +313,15 @@ func (t *ForwarderTestSuite) TestForwardPollWorkflowTaskQueuePreservesWorkerInst }, ).Return(resp, nil) + sessionOptions := &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "session"}, + }} task, err := t.fwdr.ForwardPoll(ctx, &pollMetadata{ workerInstanceKey: workerInstanceKey, + sessionOptions: sessionOptions, }) t.Require().NoError(err) + t.Require().Same(sessionOptions, request.GetPollRequest().GetSessionOptions()) t.NotNil(task) t.NotNil(request) t.Equal(workerInstanceKey, request.GetPollRequest().GetWorkerInstanceKey(), @@ -328,10 +376,15 @@ func (t *ForwarderTestSuite) TestForwardPollForActivityPreservesWorkerInstanceKe }, ).Return(resp, nil) + sessionOptions := &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "session"}, + }} task, err := t.fwdr.ForwardPoll(ctx, &pollMetadata{ workerInstanceKey: workerInstanceKey, + sessionOptions: sessionOptions, }) t.Require().NoError(err) + t.Require().Same(sessionOptions, request.GetPollRequest().GetSessionOptions()) t.NotNil(task) t.NotNil(request) t.Equal(workerInstanceKey, request.GetPollRequest().GetWorkerInstanceKey(), diff --git a/service/matching/matcher_data.go b/service/matching/matcher_data.go index e07909f6c35..a0477ed9c7f 100644 --- a/service/matching/matcher_data.go +++ b/service/matching/matcher_data.go @@ -455,6 +455,15 @@ func (d *matcherData) findMatch(allowForwarding bool, now int64) (matchedTask *i var matched *waitingPoller for poller := d.pollers.head; poller != nil; poller = poller.next { + // Queries that are dispatched directly via matching do not call RecordWorkflowTaskStarted. + // For single session workflows, this would mean that we are not attaching an exclusive worker to the workflow + // on the history side in this code path. + // Accepting one would mark the destination assigned in Matching without establishing History's + // destination binding, so subsequent workflow tasks would not be routed to this session. + // For now, we skip this. + if task.isQuery() && poller.pollMetadata.isSingleSessionAcquisitionInProgress() { + continue + } // can't match cases: if poller.queryOnly && !task.isQuery() && !task.isPollForwarder() { // query-only poll only matches with query (but can match poll forwarder) diff --git a/service/matching/matcher_data_test.go b/service/matching/matcher_data_test.go index 804c13b43e9..52b5e3e3006 100644 --- a/service/matching/matcher_data_test.go +++ b/service/matching/matcher_data_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/suite" commonpb "go.temporal.io/api/common/v1" enumspb "go.temporal.io/api/enums/v1" + sessionpb "go.temporal.io/api/session/v1" "go.temporal.io/server/api/matchingservice/v1" persistencespb "go.temporal.io/server/api/persistence/v1" taskqueuespb "go.temporal.io/server/api/taskqueue/v1" @@ -1162,3 +1163,30 @@ func gosched(n int) { runtime.Gosched() } } + +/* +TestSingleSessionAcquisitionSkipsQuery tests that a query cannot acquire a session: +1. Queue a standalone query and verify an acquisition poll leaves it waiting. +2. Add a workflow task and verify the acquisition poll receives that task. +3. Clear the acquisition flag and verify an ordinary poll can receive the query. +*/ +func (s *MatcherDataSuite) TestSingleSessionAcquisitionSkipsQuery() { + query := s.newQueryTask("query") + s.md.tasks.Add(query) + metadata := &pollMetadata{ + sessionOptions: &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "session"}, + }}, + singleSessionAcquisitionInProgress: true, + } + s.Require().Nil(s.pollImmediately(metadata)) + workflow := s.newBacklogTask(1, 0, nil) + s.md.tasks.Add(workflow) + result := s.pollImmediately(metadata) + s.Require().NotNil(result) + s.Require().Same(workflow, result.task) + metadata.singleSessionAcquisitionInProgress = false + result = s.pollImmediately(metadata) + s.Require().NotNil(result) + s.Require().Same(query, result.task) +} diff --git a/service/matching/matching_engine.go b/service/matching/matching_engine.go index 2795646683a..8f446eb3966 100644 --- a/service/matching/matching_engine.go +++ b/service/matching/matching_engine.go @@ -21,6 +21,7 @@ import ( enumspb "go.temporal.io/api/enums/v1" historypb "go.temporal.io/api/history/v1" "go.temporal.io/api/serviceerror" + sessionpb "go.temporal.io/api/session/v1" taskqueuepb "go.temporal.io/api/taskqueue/v1" "go.temporal.io/api/workflowservice/v1" deploymentspb "go.temporal.io/server/api/deployment/v1" @@ -111,14 +112,16 @@ type ( } pollMetadata struct { - taskQueueMetadata *taskqueuepb.TaskQueueMetadata - workerVersionCapabilities *commonpb.WorkerVersionCapabilities - deploymentOptions *deploymentpb.WorkerDeploymentOptions - conditions *matchingservice.PollConditions - forwardedFrom string - localPollStartTime time.Time - workerInstanceKey string - workerControlTaskQueue string + sessionOptions *sessionpb.WorkerSessionOptions + singleSessionAcquisitionInProgress bool // This is set to true when a poller is *attempting to acquire* a single session for an exclusive worker identity + taskQueueMetadata *taskqueuepb.TaskQueueMetadata + workerVersionCapabilities *commonpb.WorkerVersionCapabilities + deploymentOptions *deploymentpb.WorkerDeploymentOptions + conditions *matchingservice.PollConditions + forwardedFrom string + localPollStartTime time.Time + workerInstanceKey string + workerControlTaskQueue string } userDataUpdate struct { @@ -210,6 +213,10 @@ type ( } ) +func (m *pollMetadata) isSingleSessionAcquisitionInProgress() bool { + return m != nil && m.singleSessionAcquisitionInProgress +} + // Add registers a poller for a worker instance. Thread-safe. func (t *workerPollerTracker) Add(workerKey, pollerID string, cancel context.CancelFunc) { t.lock.Lock() @@ -659,12 +666,29 @@ func (e *matchingEngineImpl) AddWorkflowTask( return "", false, serviceerror.NewInternal("AddWorkflowTask called with unexpected partition kind") } - // do not load sticky task queues if not already loaded, which means they have no poller. + // Ordinary sticky queues require recent pollers; assigned sessions accept offline backlog. pm, _, err := e.getTaskQueuePartitionManager(ctx, partition, !sticky, loadCauseTask) if err != nil { return "", false, err - } else if sticky && !stickyWorkerAvailable(pm) { - return "", false, serviceerrors.NewStickyWorkerUnavailable() + } + if sticky && !stickyWorkerAvailable(pm) { + // TODO(Shivam): Carry exclusive-session context from History so the first lookup can + // load the partition directly without this separate metadata read. + assigned, err := e.exclusiveWorkerAssigned(ctx, partition) + if err != nil { + return "", false, err + } + if !assigned { + // TODO (Shivam) -- Accept backlog even in the case of non-exclusive sessions. + return "", false, serviceerrors.NewStickyWorkerUnavailable() + } + if pm == nil { + // if this sticky task queue partition is dealing with an assigned session, we load it's partition manager. + pm, _, err = e.getTaskQueuePartitionManager(ctx, partition, true, loadCauseTask) + if err != nil { + return "", false, err + } + } } // This needs to move to history see - https://go.temporal.io/server/issues/181 @@ -755,6 +779,22 @@ func (e *matchingEngineImpl) AddActivityTask( }) } +func (e *matchingEngineImpl) exclusiveWorkerAssigned(ctx context.Context, partition tqid.Partition) (bool, error) { + queue := UnversionedQueueKey(partition) + response, err := e.taskManager.GetTaskQueue(ctx, &persistence.GetTaskQueueRequest{ + NamespaceID: queue.NamespaceId(), + TaskQueue: queue.PersistenceName(), + TaskType: queue.TaskType(), + }) + if _, notFound := errors.AsType[*serviceerror.NotFound](err); notFound { + return false, nil + } + if err != nil { + return false, err + } + return response.TaskQueueInfo.GetExclusiveWorkerAssignment() != nil, nil +} + func (e *matchingEngineImpl) GrantEagerDispatch( ctx context.Context, request *matchingservice.GrantEagerDispatchRequest, @@ -816,6 +856,9 @@ pollLoop: conditions: req.Conditions, workerInstanceKey: request.WorkerInstanceKey, workerControlTaskQueue: request.WorkerControlTaskQueue, + sessionOptions: request.GetSessionOptions(), + + singleSessionAcquisitionInProgress: partition.Kind() == enumspb.TASK_QUEUE_KIND_NORMAL && request.GetSessionOptions().GetSingleSession() != nil, } task, versionSetUsed, err := e.pollTask(pollerCtx, partition, pollMetadata) if err != nil { @@ -887,6 +930,9 @@ pollLoop: requestClone = common.CloneProto(request) requestClone.WorkerVersionCapabilities.BuildId = "" } + // TODO (Shivam): Consider the edge case in the case of exclusive workers where the call to recordWorkflowTaskStarted succeeded + // but the response never reached matching. You could have a world in where workflow execution A has a worker queue of S + // and so does workflow execution B. resp, err := e.recordWorkflowTaskStarted(ctx, requestClone, task) if err != nil { switch err := err.(type) { @@ -1093,6 +1139,7 @@ pollLoop: conditions: req.Conditions, workerInstanceKey: request.WorkerInstanceKey, workerControlTaskQueue: request.WorkerControlTaskQueue, + sessionOptions: request.GetSessionOptions(), } task, versionSetUsed, err := e.pollTask(pollerCtx, partition, pollMetadata) if err != nil { @@ -1233,12 +1280,25 @@ func (e *matchingEngineImpl) QueryWorkflow( "QueryWorkflow called with unexpected partition kind") { return nil, serviceerror.NewInternal("QueryWorkflow called with unexpected partition kind") } - // do not load sticky task queues if not already loaded, which means they have no poller. + // Assigned sessions accept queries even without recent pollers. pm, _, err := e.getTaskQueuePartitionManager(ctx, partition, !sticky, loadCauseQuery) if err != nil { return nil, err - } else if sticky && !stickyWorkerAvailable(pm) { - return nil, serviceerrors.NewStickyWorkerUnavailable() + } + if sticky && !stickyWorkerAvailable(pm) { + assigned, err := e.exclusiveWorkerAssigned(ctx, partition) + if err != nil { + return nil, err + } + if !assigned { + return nil, serviceerrors.NewStickyWorkerUnavailable() + } + if pm == nil { + pm, _, err = e.getTaskQueuePartitionManager(ctx, partition, true, loadCauseQuery) + if err != nil { + return nil, err + } + } } taskID := uuid.NewString() diff --git a/service/matching/matching_engine_test.go b/service/matching/matching_engine_test.go index 9f78c70387b..14f8377182d 100644 --- a/service/matching/matching_engine_test.go +++ b/service/matching/matching_engine_test.go @@ -28,6 +28,7 @@ import ( nexuspb "go.temporal.io/api/nexus/v1" querypb "go.temporal.io/api/query/v1" "go.temporal.io/api/serviceerror" + sessionpb "go.temporal.io/api/session/v1" taskqueuepb "go.temporal.io/api/taskqueue/v1" "go.temporal.io/api/workflowservice/v1" clockspb "go.temporal.io/server/api/clock/v1" @@ -1300,6 +1301,98 @@ func (s *matchingEngineSuite) TestQueryWorkflowDoesNotLoadSticky() { s.Empty(s.matchingEngine.partitions) } +/* +TestQueryWorkflowAssignedStickyWithoutPollers tests queries while the session worker is absent: +1. Prepare assigned and ordinary sticky queues, both loaded and unloaded, without pollers. +2. Verify ordinary sticky queues return StickyWorkerUnavailable. +3. Verify assigned queues load if needed and hold the query until a poller arrives. +4. Poll the query, answer it, and verify the waiting caller receives the answer. +*/ +func (s *matchingEngineSuite) TestQueryWorkflowAssignedStickyWithoutPollers() { + for _, tc := range []struct { + name string + assigned, loaded bool + }{ + {name: "assigned unloaded", assigned: true}, + {name: "assigned loaded", assigned: true, loaded: true}, + {name: "ordinary unloaded"}, + {name: "ordinary loaded", loaded: true}, + } { + s.Run(tc.name, func() { + t := s.T() + ctx := t.Context() + tv := testvars.New(t).WithNamespaceID(s.ns.ID()) + partition := newTestTaskQueue(tv.NamespaceID().String(), tv.TaskQueue().GetName(), enumspb.TASK_QUEUE_TYPE_WORKFLOW). + StickyPartition(tv.StickyTaskQueue().GetName()) + if tc.assigned { + pm, _, err := s.matchingEngine.getTaskQueuePartitionManager(ctx, partition, true, loadCauseUnspecified) + require.NoError(t, err) + queue := pm.(*taskQueuePartitionManagerImpl).defaultQueue().(*physicalTaskQueueManagerImpl) + require.NoError(t, queue.AssignExclusiveWorker()) + if !tc.loaded { + s.matchingEngine.unloadTaskQueuePartition(pm, unloadCauseUnspecified) + } + } + pm, _, err := s.matchingEngine.getTaskQueuePartitionManager(ctx, partition, tc.loaded, loadCauseUnspecified) + require.NoError(t, err) + require.Equal(t, tc.loaded, pm != nil) + require.False(t, stickyWorkerAvailable(pm)) + + request := &matchingservice.QueryWorkflowRequest{ + NamespaceId: tv.NamespaceID().String(), TaskQueue: tv.StickyTaskQueue(), + QueryRequest: &workflowservice.QueryWorkflowRequest{ + Namespace: s.ns.Name().String(), Execution: tv.WorkflowExecution(), + Query: &querypb.WorkflowQuery{QueryType: "q"}, + }, + } + if !tc.assigned { + _, err := s.matchingEngine.QueryWorkflow(ctx, request) + require.ErrorAs(t, err, new(*serviceerrors.StickyWorkerUnavailable)) + pm, _, err = s.matchingEngine.getTaskQueuePartitionManager(ctx, partition, false, loadCauseUnspecified) + require.NoError(t, err) + require.Equal(t, tc.loaded, pm != nil) + return + } + + queryResult := future.NewFuture[*matchingservice.QueryWorkflowResponse]() + go func() { + response, err := s.matchingEngine.QueryWorkflow(ctx, request) + queryResult.Set(response, err) + }() + await.Require(ctx, t, func(t *await.T) { + pm, _, err := s.matchingEngine.getTaskQueuePartitionManager(t.Context(), partition, false, loadCauseUnspecified) + require.NoError(t, err) + require.NotNil(t, pm) + require.False(t, stickyWorkerAvailable(pm)) + data := &pm.(*taskQueuePartitionManagerImpl).defaultQueue().(*physicalTaskQueueManagerImpl).matcher.data + data.lock.Lock() + defer data.lock.Unlock() + require.Equal(t, 1, data.tasks.Len()) + }, 5*time.Second, 10*time.Millisecond) + require.False(t, queryResult.Ready()) + + pm, _, err = s.matchingEngine.getTaskQueuePartitionManager(ctx, partition, false, loadCauseUnspecified) + require.NoError(t, err) + task, _, err := pm.PollTask(ctx, &pollMetadata{}) + require.NoError(t, err) + require.NotNil(t, task) + require.True(t, task.isQuery()) + protorequire.ProtoEqual(t, request, task.query.request) + task.finish(taskFinishResult{consumedToken: true}) + answer := payloads.EncodeString("answer") + require.NoError(t, s.matchingEngine.RespondQueryTaskCompleted(ctx, &matchingservice.RespondQueryTaskCompletedRequest{ + TaskId: task.query.taskID, + CompletedRequest: &workflowservice.RespondQueryTaskCompletedRequest{ + CompletedType: enumspb.QUERY_RESULT_TYPE_ANSWERED, QueryResult: answer, + }, + }, metrics.NoopMetricsHandler)) + response, err := queryResult.Get(ctx) + require.NoError(t, err) + protorequire.ProtoEqual(t, &matchingservice.QueryWorkflowResponse{QueryResult: answer}, response) + }) + } +} + func (s *matchingEngineSuite) TestAddThenConsumeActivities() { s.matchingEngine.config.LongPollExpirationInterval = dynamicconfig.GetDurationPropertyFnFilteredByTaskQueue(10 * time.Millisecond) @@ -7103,3 +7196,110 @@ func (s *matchingEngineSuite) testStickyActivityRouting(versioned, mismatchedDep s.Require().Equal("session-activity", response.GetActivityId()) s.Require().NotEmpty(response.GetTaskToken()) } + +/* +TestSingleSessionOfflineWorkflowAdmission tests workflow task admission without a session worker: +1. Prepare assigned, ordinary, and missing sticky queue metadata, with queues loaded or unloaded. +2. Add a workflow task without any poller and verify unassigned queues reject it. +3. Verify assigned queues load if needed and persist the task only in their sticky backlog. +4. Unload and reload the assigned queue, then verify a session poll receives the persisted task. +*/ +func (s *matchingEngineSuite) TestSingleSessionOfflineWorkflowAdmission() { + for _, tc := range []struct { + name string + assigned, loaded, missing bool + }{ + {name: "assigned unloaded", assigned: true}, + {name: "assigned loaded without pollers", assigned: true, loaded: true}, + {name: "ordinary unloaded"}, + {name: "ordinary loaded without pollers", loaded: true}, + {name: "metadata missing", missing: true}, + } { + s.Run(tc.name, func() { + t := s.T() + ctx := t.Context() + tv := testvars.New(t).WithNamespaceID(s.ns.ID()) + partition := newTestTaskQueue(tv.NamespaceID().String(), tv.TaskQueue().GetName(), enumspb.TASK_QUEUE_TYPE_WORKFLOW).StickyPartition(tv.StickyTaskQueue().GetName()) + queue := UnversionedQueueKey(partition) + if !tc.missing { + info := &persistencespb.TaskQueueInfo{ + NamespaceId: queue.NamespaceId(), Name: queue.PersistenceName(), TaskType: queue.TaskType(), + Kind: enumspb.TASK_QUEUE_KIND_STICKY, LastUpdateTime: timestamppb.Now(), + ExpiryTime: timestamppb.New(time.Now().Add(time.Hour)), + } + _, err := s.v1TaskManager.CreateTaskQueue(ctx, &persistence.CreateTaskQueueRequest{ + RangeID: 1, TaskQueueInfo: info, + }) + require.NoError(t, err) + if tc.assigned { + info = common.CloneProto(info) + info.ExclusiveWorkerAssignment = &persistencespb.ExclusiveWorkerAssignment{} + info.ExpiryTime = nil + _, err = s.v1TaskManager.UpdateTaskQueue(ctx, &persistence.UpdateTaskQueueRequest{ + RangeID: 2, PrevRangeID: 1, TaskQueueInfo: info, + }) + require.NoError(t, err) + } + } + pm, _, err := s.matchingEngine.getTaskQueuePartitionManager(ctx, partition, tc.loaded, loadCauseUnspecified) + require.NoError(t, err) + require.False(t, stickyWorkerAvailable(pm)) + + _, syncMatch, err := s.matchingEngine.AddWorkflowTask(ctx, &matchingservice.AddWorkflowTaskRequest{ + NamespaceId: tv.NamespaceID().String(), TaskQueue: tv.StickyTaskQueue(), Execution: tv.WorkflowExecution(), + ScheduledEventId: 7, ScheduleToStartTimeout: durationpb.New(time.Minute), + }) + require.False(t, syncMatch) + require.Zero(t, s.v1TaskManager.getTaskCount(newUnversionedRootQueueKey(tv.NamespaceID().String(), tv.TaskQueue().GetName(), enumspb.TASK_QUEUE_TYPE_WORKFLOW))) + if !tc.assigned { + require.ErrorAs(t, err, new(*serviceerrors.StickyWorkerUnavailable)) + require.Zero(t, s.v1TaskManager.getTaskCount(queue)) + pm, _, err = s.matchingEngine.getTaskQueuePartitionManager(ctx, partition, false, loadCauseUnspecified) + require.NoError(t, err) + require.Equal(t, tc.loaded, pm != nil) + return + } + require.NoError(t, err) + require.Equal(t, 1, s.v1TaskManager.getTaskCount(queue)) + pm, _, err = s.matchingEngine.getTaskQueuePartitionManager(ctx, partition, false, loadCauseUnspecified) + require.NoError(t, err) + require.NotNil(t, pm) + require.False(t, stickyWorkerAvailable(pm)) + s.matchingEngine.unloadTaskQueuePartition(pm, unloadCauseUnspecified) + pm, _, err = s.matchingEngine.getTaskQueuePartitionManager(ctx, partition, true, loadCauseUnspecified) + require.NoError(t, err) + task, _, err := pm.PollTask(ctx, &pollMetadata{sessionOptions: &sessionpb.WorkerSessionOptions{ + Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: tv.StickyTaskQueue().GetName()}, + }, + }}) + require.NoError(t, err) + require.NotNil(t, task) + require.Equal(t, tv.WorkflowID(), task.event.GetData().GetWorkflowId()) + require.Equal(t, int64(7), task.event.GetData().GetScheduledEventId()) + task.finish(taskFinishResult{consumedToken: true}) + }) + } +} + +/* +TestSingleSessionOfflineAdmissionReadFailure tests a failed assignment lookup: +1. Make the sticky queue metadata read fail while adding a workflow task. +2. Verify Matching returns that error without loading a partition or reporting a sync match. +*/ +func TestSingleSessionOfflineAdmissionReadFailure(t *testing.T) { + t.Parallel() + tv := testvars.New(t) + store := persistence.NewMockTaskManager(gomock.NewController(t)) + readErr := serviceerror.NewUnavailable("metadata read failed") + store.EXPECT().GetTaskQueue(gomock.Any(), &persistence.GetTaskQueueRequest{ + NamespaceID: tv.NamespaceID().String(), TaskQueue: tv.StickyTaskQueue().GetName(), TaskType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, + }).Return(nil, readErr) + engine := &matchingEngineImpl{taskManager: store, logger: log.NewTestLogger()} + _, syncMatch, err := engine.AddWorkflowTask(t.Context(), &matchingservice.AddWorkflowTaskRequest{ + NamespaceId: tv.NamespaceID().String(), TaskQueue: tv.StickyTaskQueue(), Execution: tv.WorkflowExecution(), + }) + require.ErrorIs(t, err, readErr) + require.False(t, syncMatch) + require.Empty(t, engine.partitions) +} diff --git a/service/matching/physical_task_queue_manager.go b/service/matching/physical_task_queue_manager.go index 525d4562ff2..cc189a92e82 100644 --- a/service/matching/physical_task_queue_manager.go +++ b/service/matching/physical_task_queue_manager.go @@ -228,6 +228,8 @@ func newPhysicalTaskQueueManager( partitionMgr.rateLimitManager, pqMgr.onRateLimited, pqMgr.MarkAlive, + pqMgr.ExclusiveWorkerAssigned, + pqMgr.AssignExclusiveWorker, ) return pqMgr, nil } @@ -950,3 +952,13 @@ func aggregateStats(stats map[int32]*taskqueuepb.TaskQueueStats) *taskqueuepb.Ta } return result } + +func (c *physicalTaskQueueManagerImpl) ExclusiveWorkerAssigned() bool { + return c.backlogMgr.getDB().ExclusiveWorkerAssigned() +} + +func (c *physicalTaskQueueManagerImpl) AssignExclusiveWorker() error { + ctx, cancel := context.WithTimeout(c.tqCtx, ioTimeout) + defer cancel() + return c.backlogMgr.getDB().assignExclusiveWorker(ctx) +} diff --git a/service/matching/physical_task_queue_manager_test.go b/service/matching/physical_task_queue_manager_test.go index 90169fda02d..e54f4a26644 100644 --- a/service/matching/physical_task_queue_manager_test.go +++ b/service/matching/physical_task_queue_manager_test.go @@ -11,8 +11,11 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" enumspb "go.temporal.io/api/enums/v1" + "go.temporal.io/api/serviceerror" + sessionpb "go.temporal.io/api/session/v1" taskqueuepb "go.temporal.io/api/taskqueue/v1" enumsspb "go.temporal.io/server/api/enums/v1" + "go.temporal.io/server/api/matchingservice/v1" "go.temporal.io/server/api/matchingservicemock/v1" persistencespb "go.temporal.io/server/api/persistence/v1" taskqueuespb "go.temporal.io/server/api/taskqueue/v1" @@ -28,8 +31,10 @@ import ( "go.temporal.io/server/common/quotas" "go.temporal.io/server/common/testing/await" "go.temporal.io/server/common/testing/testlogger" + "go.temporal.io/server/common/testing/testvars" "go.temporal.io/server/common/tqid" "go.uber.org/mock/gomock" + "google.golang.org/grpc" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" @@ -252,6 +257,137 @@ func (s *PhysicalTaskQueueManagerTestSuite) getTaskManager() *testTaskManager { return s.tqMgr.partitionMgr.engine.taskManager.(*testTaskManager) } +/* +TestSingleSessionAcquisitionPoll tests acquisition through the physical queue manager: +1. Poll an unassigned sticky queue and let the matcher forward to the normal queue. +2. Return a workflow task, an empty response, or an RPC error, including a NoWait poll. +3. Only a successful task acquisition records assignment; RangeID and the loaded queue stay unchanged. +4. The poll refreshes the physical queue's idle timer. +*/ +func (s *PhysicalTaskQueueManagerTestSuite) TestSingleSessionAcquisitionPoll() { + for _, tc := range []struct { + name string + empty bool + noWait bool + err error + }{ + {name: "workflow task"}, + {name: "empty poll", empty: true}, + {name: "no wait", noWait: true}, + {name: "rpc failure", err: serviceerror.NewUnavailable("matching unavailable")}, + } { + s.Run(tc.name, func() { + t := s.T() + tv := testvars.New(t) + queue := s.tqMgr + partition := queue.partitionMgr.partition.TaskQueue().StickyPartition(tv.StickyTaskQueue().GetName()) + queue.partitionMgr.partition = partition + queue.queue = UnversionedQueueKey(partition) + queue.backlogMgr.getDB().queue = queue.queue + _, err := queue.backlogMgr.getDB().RenewLease(t.Context()) + require.NoError(t, err) + initialRangeID := queue.backlogMgr.getDB().RangeID() + queue.partitionMgr.engine.partitions[partition.Key()] = queue.partitionMgr + client := matchingservicemock.NewMockMatchingServiceClient(s.controller) + queue.matchingClient = client + queue.matcher.partition = partition + queue.matcher.client = client + metadata := &pollMetadata{ + sessionOptions: &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: partition.RpcName()}, + }}, + deploymentOptions: tv.WorkerDeploymentOptions(true), + workerInstanceKey: tv.WorkerInstanceKey(), + workerControlTaskQueue: tv.Any().String(), + conditions: &matchingservice.PollConditions{NoWait: tc.noWait}, + } + ctx, cancel := context.WithTimeout(t.Context(), time.Minute) + defer cancel() + ctx = context.WithValue(ctx, identityKey, tv.WorkerIdentity()) + ctx = context.WithValue(ctx, pollerIDKey, tv.Any().String()) + response := &matchingservice.PollWorkflowTaskQueueResponse{TaskToken: []byte("token")} + if tc.empty { + response.TaskToken = nil + } + client.EXPECT().PollWorkflowTaskQueue(gomock.Any(), gomock.Any()).Return(response, tc.err) + clock := clock.NewEventTimeSource() + idle := false + queue.liveness = newLiveness(clock, func() time.Duration { return time.Minute }, func() { idle = true }) + queue.liveness.Start() + defer queue.liveness.Stop() + clock.Advance(30 * time.Second) + task, err := queue.PollTask(ctx, metadata) + switch { + case tc.err != nil: + require.ErrorIs(t, err, tc.err) + case tc.empty: + require.ErrorIs(t, err, errNoTasks) + default: + require.NoError(t, err) + require.Same(t, response, task.pollWorkflowTaskQueueResponse()) + } + require.Equal(t, initialRangeID, queue.backlogMgr.getDB().RangeID()) + require.Same(t, queue.partitionMgr, queue.partitionMgr.engine.partitions[partition.Key()], "assignment must not unload the partition") + require.NoError(t, queue.tqCtx.Err()) + require.Equal(t, !tc.empty && tc.err == nil, queue.ExclusiveWorkerAssigned()) + clock.Advance(30 * time.Second) + require.False(t, idle, "the acquisition poll must refresh the sticky queue's idle timer") + clock.Advance(30 * time.Second) + require.True(t, idle) + }) + } +} + +/* +TestSingleSessionAcquisitionPollCancellation tests cancellation through the physical queue manager: +1. Begin acquisition and block the forwarded normal-queue poll. +2. Cancel the caller or stop the sticky queue's context. +3. Verify the forwarded poll ends with cancellation. +*/ +// Caller cancellation or the sticky task queue shutting down should cancel the long poll +func (s *PhysicalTaskQueueManagerTestSuite) TestSingleSessionAcquisitionPollCancellation() { + for _, sourceStops := range []bool{false, true} { + name := "caller cancels" + if sourceStops { + name = "sticky queue stops" + } + s.Run(name, func() { + t := s.T() + queue := s.tqMgr + queue.partitionMgr.partition = queue.partitionMgr.partition.TaskQueue().StickyPartition("session") + queue.queue = UnversionedQueueKey(queue.partitionMgr.partition) + client := matchingservicemock.NewMockMatchingServiceClient(s.controller) + queue.matchingClient = client + queue.matcher.partition = queue.partitionMgr.partition + queue.matcher.client = client + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + entered := make(chan struct{}) + client.EXPECT().PollWorkflowTaskQueue(gomock.Any(), gomock.Any()).DoAndReturn( + func(callCtx context.Context, _ *matchingservice.PollWorkflowTaskQueueRequest, _ ...grpc.CallOption) (*matchingservice.PollWorkflowTaskQueueResponse, error) { + close(entered) + <-callCtx.Done() + return nil, callCtx.Err() + }) + done := make(chan error, 1) + go func() { + _, err := queue.PollTask(ctx, &pollMetadata{sessionOptions: &sessionpb.WorkerSessionOptions{ + Admission: &sessionpb.WorkerSessionOptions_SingleSession{SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "session"}}, + }}) + done <- err + }() + await.Rcv(t, entered) + if sourceStops { + queue.tqCtxCancel() + require.NoError(t, ctx.Err()) + } else { + cancel() + } + require.ErrorIs(t, await.Rcv(t, done), context.Canceled) + }) + } +} + func (s *PhysicalTaskQueueManagerTestSuite) TestCheckIdleTaskQueue() { // Idle s.tqMgr.Start() diff --git a/service/matching/pri_forwarder.go b/service/matching/pri_forwarder.go index b280ae58eb1..4e8593b7fe1 100644 --- a/service/matching/pri_forwarder.go +++ b/service/matching/pri_forwarder.go @@ -223,6 +223,15 @@ func ForwardPollWithTarget( ) (*internalTask, error) { pollerID, _ := ctx.Value(pollerIDKey).(string) // nolint:revive identity, _ := ctx.Value(identityKey).(string) // nolint:revive + forwardedSource := source.RpcName() + if source.Kind() == enumspb.TASK_QUEUE_KIND_STICKY && pollMetadata.isSingleSessionAcquisitionInProgress() { + // Initial session acquisition from a sticky queue partition needs an empty source to enable load balanced normal read-partition selection. + // Non-session based sticky pollers do not need this since they are forwarded to those task queue partitions that have advertised a backlog + // of tasks. + // TODO (Shivam): This is hacky and not the best way to do this. Come back to add a new variable that denotes if we need + // to load balance or not while keeping forwardedSource what it is. + forwardedSource = "" + } // nolint:exhaustive // there's a default clause switch target.TaskType() { @@ -240,8 +249,9 @@ func ForwardPollWithTarget( DeploymentOptions: pollMetadata.deploymentOptions, WorkerInstanceKey: pollMetadata.workerInstanceKey, WorkerControlTaskQueue: pollMetadata.workerControlTaskQueue, + SessionOptions: pollMetadata.sessionOptions, }, - ForwardedSource: source.RpcName(), + ForwardedSource: forwardedSource, Conditions: pollMetadata.conditions, }) if err != nil { @@ -265,8 +275,9 @@ func ForwardPollWithTarget( DeploymentOptions: pollMetadata.deploymentOptions, WorkerInstanceKey: pollMetadata.workerInstanceKey, WorkerControlTaskQueue: pollMetadata.workerControlTaskQueue, + SessionOptions: pollMetadata.sessionOptions, }, - ForwardedSource: source.RpcName(), + ForwardedSource: forwardedSource, Conditions: pollMetadata.conditions, }) if err != nil { @@ -290,7 +301,7 @@ func ForwardPollWithTarget( WorkerInstanceKey: pollMetadata.workerInstanceKey, // Namespace is ignored here. }, - ForwardedSource: source.RpcName(), + ForwardedSource: forwardedSource, Conditions: pollMetadata.conditions, }) if err != nil { diff --git a/service/matching/pri_matcher.go b/service/matching/pri_matcher.go index 5b8d63f2b49..e0f20948962 100644 --- a/service/matching/pri_matcher.go +++ b/service/matching/pri_matcher.go @@ -8,6 +8,7 @@ import ( "sync/atomic" "time" + enumspb "go.temporal.io/api/enums/v1" "go.temporal.io/api/serviceerror" enumsspb "go.temporal.io/server/api/enums/v1" "go.temporal.io/server/api/matchingservice/v1" @@ -15,6 +16,7 @@ import ( "go.temporal.io/server/common/backoff" "go.temporal.io/server/common/clock" "go.temporal.io/server/common/goro" + "go.temporal.io/server/common/locks" "go.temporal.io/server/common/log" "go.temporal.io/server/common/metrics" "go.temporal.io/server/common/primitives/timestamp" @@ -38,14 +40,21 @@ type priTaskMatcher struct { // Background context used for forwarding tasks. Closed when task queue is closed. tqCtx context.Context - partition tqid.Partition - fwdr *priForwarder - client matchingservice.MatchingServiceClient - validator taskValidator - rateLimitManager *rateLimitManager - metricsHandler metrics.Handler // namespace metric scope - logger log.Logger - markAlive func() // function to mark the physical task queue alive + partition tqid.Partition + fwdr *priForwarder + client matchingservice.MatchingServiceClient + validator taskValidator + rateLimitManager *rateLimitManager + metricsHandler metrics.Handler // namespace metric scope + logger log.Logger + markAlive func() // function to mark the physical task queue alive + isExclusiveWorkerAssigned func() bool + assignExclusiveWorker func() error + + // Single Session Acquisition knobs + singleSessionAcquisitionMutex sync.Mutex + singleSessionAcquisitionInProgress bool + singleSessionAcquisitionCV *locks.ConditionVariableImpl priorityBacklogForwarders *goro.KeyedSet[remotePriorityBacklog] } @@ -104,6 +113,8 @@ func newPriTaskMatcher( rateLimitManager *rateLimitManager, onRateLimited func(), markAlive func(), + isExclusiveWorkerAssigned func() bool, + assignExclusiveWorker func() error, ) *priTaskMatcher { tm := &priTaskMatcher{ config: config, @@ -117,6 +128,8 @@ func newPriTaskMatcher( validator: validator, rateLimitManager: rateLimitManager, markAlive: markAlive, + isExclusiveWorkerAssigned: isExclusiveWorkerAssigned, + assignExclusiveWorker: assignExclusiveWorker, priorityBacklogForwarders: goro.NewKeyedSet[remotePriorityBacklog](tqCtx), } @@ -566,9 +579,98 @@ func (tm *priTaskMatcher) emitDispatchLatency(task *internalTask, forwarded bool // On success, the returned task could be a query task or a regular task // Returns errNoTasks when context deadline is exceeded func (tm *priTaskMatcher) Poll(ctx context.Context, pollMetadata *pollMetadata) (*internalTask, error) { + // Assuming that session acquisition (ie for the very first time) via a WFT only. Might change this in the future. + if tm.partition.Kind() == enumspb.TASK_QUEUE_KIND_STICKY && + tm.partition.TaskType() == enumspb.TASK_QUEUE_TYPE_WORKFLOW && + pollMetadata.sessionOptions.GetSingleSession() != nil { + return tm.pollForSingleSessionWorker(ctx, pollMetadata) + } return tm.poll(ctx, pollMetadata, false) } +func (tm *priTaskMatcher) pollForSingleSessionWorker(ctx context.Context, pollMetadata *pollMetadata) (*internalTask, error) { + callCtx, cancel := context.WithCancel(ctx) + defer cancel() + stop := context.AfterFunc(tm.tqCtx, cancel) + defer stop() + if err := tm.tqCtx.Err(); err != nil { + return nil, err + } + ctx = callCtx + + shouldAcquire, err := tm.beginSingleSessionAcquisition(ctx, pollMetadata.conditions.GetNoWait()) + if err != nil { + return nil, err + } + if !shouldAcquire { + return tm.poll(ctx, pollMetadata, false) + } + + // TODO(Shivam): Reconcile uncertain starts after errors, cancellation, or timeouts + // before allowing another acquisition. Failure handling is deferred for now. + defer tm.finishSingleSessionAcquisition() + acquisitionMetadata := *pollMetadata + acquisitionMetadata.singleSessionAcquisitionInProgress = true + // This poll, with stable identity set, now has the opportunity to be forwarded to the normal task queue for matching with a task. + // Once matched, no more polls with a stable identity are forwarded to the normal task queue. + task, err := ForwardPollWithTarget(ctx, &acquisitionMetadata, tm.client, tm.partition, tm.partition.TaskQueue().RootPartition()) + if err != nil { + return nil, err + } + if err := tm.assignExclusiveWorker(); err != nil { + return nil, err + } + return task, nil +} + +func (tm *priTaskMatcher) beginSingleSessionAcquisition(ctx context.Context, noWait bool) (bool, error) { + // The main idea here is to have a sequential allowance of pollers that are polling with the + // same stable identity so that we do not have multiple pollers for the same identity acquiring tasks + // from multiple workflows at the same time + tm.singleSessionAcquisitionMutex.Lock() + defer tm.singleSessionAcquisitionMutex.Unlock() + + for { + if tm.isExclusiveWorkerAssigned() { + // An exclusive worker has already acquired the workflow that is on this sticky task queue. + // No new acquisitions are allowed and this poller should only poll the sticky task queue. + return false, nil + } + if ctx.Err() != nil { + return false, errNoTasks + } + if !tm.singleSessionAcquisitionInProgress { + break + } + if noWait { + return false, errNoTasks + } + tm.markAlive() + if tm.singleSessionAcquisitionCV == nil { + tm.singleSessionAcquisitionCV = locks.NewConditionVariable(&tm.singleSessionAcquisitionMutex) + } + // This poller shall wait until a previous poller completes its acquisition or returns with an empty poll. + // Note: Calling Wait here shall release singleSessionAcquisitionMutex so that some other poller can enter + // this method + tm.singleSessionAcquisitionCV.Wait(ctx.Done()) + } + if tm.partition.TaskQueue().Name() == "" { + return false, serviceerror.NewInvalidArgument("single-session workflow polls require a normal task queue name") + } + tm.singleSessionAcquisitionInProgress = true + return true, nil +} + +func (tm *priTaskMatcher) finishSingleSessionAcquisition() { + tm.singleSessionAcquisitionMutex.Lock() + defer tm.singleSessionAcquisitionMutex.Unlock() + tm.singleSessionAcquisitionInProgress = false + if tm.singleSessionAcquisitionCV != nil { + // Wake up all pollers that are waiting for this acquisition to complete + tm.singleSessionAcquisitionCV.Broadcast() + } +} + // PollForQuery blocks until a *query* task is found or context deadline is exceeded // Returns errNoTasks when context deadline is exceeded func (tm *priTaskMatcher) PollForQuery(ctx context.Context, pollMetadata *pollMetadata) (*internalTask, error) { @@ -653,6 +755,15 @@ func (tm *priTaskMatcher) poll( pollMetadata: pollMetadata, } + if pollMetadata.sessionOptions.GetSingleSession() != nil && + (pollMetadata.conditions.GetNoWait() || + (tm.partition.Kind() == enumspb.TASK_QUEUE_KIND_STICKY && !pollMetadata.isSingleSessionAcquisitionInProgress())) { + // Only an acquisition owner may forward. NoWait cannot await an asynchronous forward. In other words, + // if the poller is not the acquisition owner, it will not forward the poll, preventing multiple pollers + // from acquiring different workflow tasks at the same time for the same stable identity. + poller.forwardCtx = nil + } + var res *matchResult if pollMetadata.conditions.GetNoWait() { res = tm.data.MatchPollerImmediately(poller) diff --git a/service/matching/pri_matcher_test.go b/service/matching/pri_matcher_test.go index f3ea4be5c39..4fba8d527df 100644 --- a/service/matching/pri_matcher_test.go +++ b/service/matching/pri_matcher_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/suite" enumspb "go.temporal.io/api/enums/v1" "go.temporal.io/api/serviceerror" + sessionpb "go.temporal.io/api/session/v1" enumsspb "go.temporal.io/server/api/enums/v1" "go.temporal.io/server/api/matchingservice/v1" "go.temporal.io/server/api/matchingservicemock/v1" @@ -21,10 +22,13 @@ import ( "go.temporal.io/server/common/metrics" "go.temporal.io/server/common/payloads" "go.temporal.io/server/common/testing/await" + "go.temporal.io/server/common/testing/protorequire" "go.temporal.io/server/common/testing/testhooks" "go.temporal.io/server/common/testing/testlogger" + "go.temporal.io/server/common/testing/testvars" "go.temporal.io/server/common/tqid" "go.uber.org/mock/gomock" + "google.golang.org/grpc" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -91,6 +95,8 @@ func (s *PriMatcherSuite) newTestMatcher(ctx context.Context, opts testMatcherOp rateLimitManager, func() {}, // onRateLimited func() {}, // markAlive + func() bool { return true }, + func() error { return nil }, ) } @@ -781,6 +787,7 @@ func (s *PriMatcherSuite) TestValidatorRunsOnChildBehindForwardedHead() { tm := newPriTaskMatcher( ctx, cfg, childPartition, fwdr, mockClient, mockValidator, s.logger, metrics.NoopMetricsHandler, rateLimitManager, func() {}, func() {}, + func() bool { return true }, func() error { return nil }, ) tm.Start() defer tm.Stop() @@ -849,7 +856,8 @@ func (s *PriMatcherSuite) TestForwardTasksIndependentBackoff() { manager := newRateLimitManager(&mockUserDataManager{}, cfg, enumspb.TASK_QUEUE_TYPE_WORKFLOW) manager.Start() tm := newPriTaskMatcher(ctx, cfg, child, fwdr, client, nil, - s.logger, metrics.NoopMetricsHandler, manager, func() {}, func() {}) + s.logger, metrics.NoopMetricsHandler, manager, func() {}, func() {}, + func() bool { return true }, func() error { return nil }) defer tm.Stop() done := make(chan taskResponse, workers) addTasks := func(firstID int64) { @@ -889,3 +897,535 @@ func (s *PriMatcherSuite) TestForwardTasksIndependentBackoff() { synctest.Wait() }) } + +/* +TestSingleSessionStickyPollSkipsPriorityForwarder checks an already-assigned sticky queue: +1. Advertise backlog on a normal partition so a priority forwarder is waiting. +2. Start a regular session poll and verify it waits locally without forwarding. +3. Add a local sticky task and verify that poll receives it without a normal-queue RPC. +*/ +func (s *PriMatcherSuite) TestSingleSessionStickyPollSkipsPriorityForwarder() { + synctest.Test(s.T(), func(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + client := matchingservicemock.NewMockMatchingServiceClient(s.controller) + var forwardCalls atomic.Int32 + client.EXPECT().PollWorkflowTaskQueue(gomock.Any(), gomock.Any()).DoAndReturn( + func(context.Context, *matchingservice.PollWorkflowTaskQueueRequest, ...grpc.CallOption) (*matchingservice.PollWorkflowTaskQueueResponse, error) { + forwardCalls.Add(1) + return &matchingservice.PollWorkflowTaskQueueResponse{TaskToken: []byte("unexpected-forwarded-task")}, nil + }).AnyTimes() + tm := s.newTestMatcher(ctx, testMatcherOpts{client: client}) + tm.partition = testMatcherTaskQueue.StickyPartition("session") + tm.isExclusiveWorkerAssigned = func() bool { return true } + defer tm.Stop() + tm.UpdateRemotePriorityBacklogs(remotePriorityBacklogSet{ + {partition: 2, priority: tm.config.DefaultPriorityKey}: {}, + }) + synctest.Wait() + tm.data.lock.Lock() + forwarders := tm.data.tasks.Len() + tm.data.lock.Unlock() + require.Equal(t, 1, forwarders, "the backlog advertisement must create a waiting forwarder") + + metadata := &pollMetadata{sessionOptions: &sessionpb.WorkerSessionOptions{ + Admission: &sessionpb.WorkerSessionOptions_SingleSession{SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "session"}}, + }} + type result struct { + task *internalTask + err error + } + completed := make(chan result, 1) + go func() { + task, err := tm.Poll(ctx, metadata) + completed <- result{task: task, err: err} + }() + synctest.Wait() + require.Zero(t, forwardCalls.Load(), "an assigned session must not poll the normal queue") + select { + case <-completed: + t.Fatal("the assigned poll must wait for a local task") + default: + } + tm.data.lock.Lock() + waiting := tm.data.pollers.Len() + tm.data.lock.Unlock() + require.Equal(t, 1, waiting) + + local := s.newBacklogTask(1, make(chan taskResponse, 1)) + require.NoError(t, tm.AddTask(local)) + got := await.Rcv(t, completed) + require.NoError(t, got.err) + require.Same(t, local, got.task) + require.Zero(t, forwardCalls.Load()) + }) +} + +/* +TestSingleSessionNoWaitSkipsForwarder checks polls that must return immediately: +1. Put a parent or sticky-backlog forwarder in the matcher. +2. Issue a session poll with NoWait set. +3. Verify it returns empty instead of starting asynchronous forwarding. +*/ +func (s *PriMatcherSuite) TestSingleSessionNoWaitSkipsForwarder() { + for _, sticky := range []bool{false, true} { + name := "normal parent forwarding" + if sticky { + name = "sticky backlog forwarding" + } + s.Run(name, func() { + ctx := s.T().Context() + opts := testMatcherOpts{} + if !sticky { + opts.partition = testMatcherTaskQueue.NormalPartition(1) + } + tm := s.newTestMatcher(ctx, opts) + defer tm.Stop() + forwarder := parentPollForwarder + if sticky { + tm.partition = testMatcherTaskQueue.StickyPartition("session") + forwarder = priorityBacklogPollForwarder + } + tm.data.tasks.Add(newPollForwarderTask(1, forwarder)) + task, err := tm.Poll(ctx, &pollMetadata{ + sessionOptions: &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "session"}, + }}, + singleSessionAcquisitionInProgress: true, + conditions: &matchingservice.PollConditions{NoWait: true}, + }) + s.Require().ErrorIs(err, errNoTasks) + s.Require().Nil(task) + }) + } +} + +/* +TestSingleSessionAcquisitionPoll checks the initial acquisition request: +1. Forward an unassigned session's poll to the normal queue with its original metadata and deadline. +2. Persist assignment only when the forwarded poll returns a workflow task. +3. Verify success, empty response, NoWait, RPC failure, and assignment failure without mutating caller metadata. +*/ +func (s *PriMatcherSuite) TestSingleSessionAcquisitionPoll() { + for _, tc := range []struct { + name string + empty bool + noWait bool + forwardErr error + assignmentErr error + }{ + {name: "workflow task"}, + {name: "empty poll", empty: true}, + {name: "no wait", noWait: true}, + {name: "rpc failure", forwardErr: serviceerror.NewUnavailable("matching unavailable")}, + {name: "assignment failed", assignmentErr: serviceerror.NewUnavailable("assignment failed")}, + } { + s.Run(tc.name, func() { + t := s.T() + tv := testvars.New(t) + ctx, cancel := context.WithTimeout(t.Context(), time.Minute) + defer cancel() + ctx = context.WithValue(ctx, identityKey, tv.WorkerIdentity()) + ctx = context.WithValue(ctx, pollerIDKey, tv.Any().String()) + client := matchingservicemock.NewMockMatchingServiceClient(s.controller) + tm := s.newTestMatcher(ctx, testMatcherOpts{client: client}) + tm.partition = testMatcherTaskQueue.StickyPartition("session") + defer tm.Stop() + var assigned atomic.Bool + tm.isExclusiveWorkerAssigned = assigned.Load + assignmentCalls := 0 + tm.assignExclusiveWorker = func() error { + assignmentCalls++ + if tc.assignmentErr != nil { + return tc.assignmentErr + } + assigned.Store(true) + return nil + } + metadata := &pollMetadata{ + sessionOptions: &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: tm.partition.RpcName()}, + }}, + deploymentOptions: tv.WorkerDeploymentOptions(true), + workerInstanceKey: tv.WorkerInstanceKey(), + workerControlTaskQueue: tv.Any().String(), + conditions: &matchingservice.PollConditions{NoWait: tc.noWait}, + } + response := &matchingservice.PollWorkflowTaskQueueResponse{TaskToken: []byte("token")} + if tc.empty { + response.TaskToken = nil + } + client.EXPECT().PollWorkflowTaskQueue(gomock.Any(), gomock.Any()).DoAndReturn( + func(callCtx context.Context, request *matchingservice.PollWorkflowTaskQueueRequest, _ ...grpc.CallOption) (*matchingservice.PollWorkflowTaskQueueResponse, error) { + require.Empty(t, request.GetForwardedSource(), "normal read partition selection must remain enabled") + require.Equal(t, tm.partition.NamespaceId(), request.GetNamespaceId()) + require.Equal(t, tm.partition.TaskQueue().Name(), request.GetPollRequest().GetTaskQueue().GetName()) + require.Equal(t, enumspb.TASK_QUEUE_KIND_NORMAL, request.GetPollRequest().GetTaskQueue().GetKind()) + require.Equal(t, tv.WorkerIdentity(), request.GetPollRequest().GetIdentity()) + require.Equal(t, ctx.Value(pollerIDKey), request.GetPollerId()) + protorequire.ProtoEqual(t, metadata.sessionOptions, request.GetPollRequest().GetSessionOptions()) + protorequire.ProtoEqual(t, metadata.deploymentOptions, request.GetPollRequest().GetDeploymentOptions()) + require.Equal(t, metadata.workerInstanceKey, request.GetPollRequest().GetWorkerInstanceKey()) + require.Equal(t, metadata.workerControlTaskQueue, request.GetPollRequest().GetWorkerControlTaskQueue()) + protorequire.ProtoEqual(t, metadata.conditions, request.GetConditions()) + deadline, ok := callCtx.Deadline() + require.True(t, ok) + originalDeadline, _ := ctx.Deadline() + require.Equal(t, originalDeadline, deadline) + return response, tc.forwardErr + }) + task, err := tm.Poll(ctx, metadata) + switch { + case tc.forwardErr != nil: + require.ErrorIs(t, err, tc.forwardErr) + case tc.empty: + require.ErrorIs(t, err, errNoTasks) + case tc.assignmentErr != nil: + require.ErrorIs(t, err, tc.assignmentErr) + default: + require.NoError(t, err) + require.Same(t, response, task.pollWorkflowTaskQueueResponse()) + } + if tc.empty || tc.forwardErr != nil { + require.Zero(t, assignmentCalls) + } else { + require.Equal(t, 1, assignmentCalls) + } + require.Equal(t, !tc.empty && tc.forwardErr == nil && tc.assignmentErr == nil, assigned.Load()) + require.False(t, metadata.isSingleSessionAcquisitionInProgress(), "acquisition must not mutate caller metadata") + require.False(t, tm.singleSessionAcquisitionInProgress) + }) + } +} + +/* +TestSingleSessionConcurrentAcquisition tests the special poll forwarding mechanism for exclusive workers: +1. P1 forwards to the normal queue. +2. P2 and P3 arrive and must wait. +3. P1 receives a task and assignment succeeds. +4. P2 and P3 become local sticky-queue pollers. +5. The test adds two tasks locally and verifies those pollers receive them. + +The empty and timeout cases also check acquisition handoff: +1. P1 ends without acquiring a workflow while P2 and P3 are waiting. +2. Either P2 or P3 forwards next; the other continues waiting. +3. The new acquisition succeeds and assigns the session. +4. The remaining poller receives a task only from the local sticky queue. +*/ +func (s *PriMatcherSuite) TestSingleSessionConcurrentAcquisition() { + for _, tc := range []struct { + name string + persistBlocked bool + empty bool + timeout bool + }{ + {name: "forwarding"}, + {name: "persisting assignment", persistBlocked: true}, + {name: "empty acquisition hands off", empty: true}, + {name: "timed out acquisition hands off", timeout: true}, + } { + s.Run(tc.name, func() { + synctest.Test(s.T(), func(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + firstCtx := ctx + if tc.timeout { + var cancelFirst context.CancelFunc + firstCtx, cancelFirst = context.WithTimeout(ctx, 10*time.Second) + defer cancelFirst() + } + client := matchingservicemock.NewMockMatchingServiceClient(s.controller) + tm := s.newTestMatcher(ctx, testMatcherOpts{client: client}) + tm.partition = testMatcherTaskQueue.StickyPartition("session") + defer tm.Stop() + var assigned atomic.Bool + tm.isExclusiveWorkerAssigned = assigned.Load + metadata := &pollMetadata{sessionOptions: &sessionpb.WorkerSessionOptions{ + Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: tm.partition.RpcName()}, + }, + }} + entered := make(chan struct{}, 1) + release := make(chan struct{}) + block := func(callCtx context.Context) error { + entered <- struct{}{} + select { + case <-release: + return nil + case <-callCtx.Done(): + return callCtx.Err() + } + } + response := &matchingservice.PollWorkflowTaskQueueResponse{TaskToken: []byte("token")} + sessionPoll := gomock.Cond(func(request *matchingservice.PollWorkflowTaskQueueRequest) bool { + return request.GetPollRequest().GetSessionOptions().GetSingleSession().GetStableIdentity() == tm.partition.RpcName() + }) + firstForward := client.EXPECT().PollWorkflowTaskQueue(gomock.Any(), sessionPoll).DoAndReturn( + func(callCtx context.Context, _ *matchingservice.PollWorkflowTaskQueueRequest, _ ...grpc.CallOption) (*matchingservice.PollWorkflowTaskQueueResponse, error) { + if !tc.persistBlocked { + if err := block(callCtx); err != nil { + return nil, err + } + } + if tc.empty { + return &matchingservice.PollWorkflowTaskQueueResponse{}, nil + } + return response, nil + }) + retryEntered := make(chan struct{}) + releaseRetry := make(chan struct{}) + if tc.empty || tc.timeout { + client.EXPECT().PollWorkflowTaskQueue(gomock.Any(), sessionPoll).After(firstForward).DoAndReturn( + func(callCtx context.Context, _ *matchingservice.PollWorkflowTaskQueueRequest, _ ...grpc.CallOption) (*matchingservice.PollWorkflowTaskQueueResponse, error) { + close(retryEntered) + select { + case <-releaseRetry: + return response, nil + case <-callCtx.Done(): + return nil, callCtx.Err() + } + }) + } + var assignmentCalls atomic.Int32 + tm.assignExclusiveWorker = func() error { + if tc.persistBlocked { + if err := block(ctx); err != nil { + return err + } + } + assignmentCalls.Add(1) + assigned.Store(true) + return nil + } + type result struct { + task *internalTask + err error + } + completed := make(chan result, 1) + go func() { + task, err := tm.Poll(firstCtx, metadata) + completed <- result{task: task, err: err} + }() + await.Rcv(t, entered) + competing := make(chan result, 2) + for range 2 { + go func() { + task, err := tm.Poll(ctx, metadata) + competing <- result{task: task, err: err} + }() + } + synctest.Wait() + select { + case <-competing: + t.Fatal("competing polls must wait for acquisition to finish") + default: + } + select { + case <-retryEntered: + t.Fatal("a waiting poll must not forward before the first acquisition finishes") + default: + } + task, err := tm.Poll(ctx, &pollMetadata{sessionOptions: metadata.sessionOptions, conditions: &matchingservice.PollConditions{NoWait: true}}) + require.ErrorIs(t, err, errNoTasks) + require.Nil(t, task) + _, err = tm.Poll(ctx, &pollMetadata{conditions: &matchingservice.PollConditions{NoWait: true}}) + require.ErrorIs(t, err, errNoTasks, "ordinary polls do not enter the acquisition guard") + + other := s.newTestMatcher(ctx, testMatcherOpts{client: client}) + other.partition = testMatcherTaskQueue.StickyPartition("other-session") + defer other.Stop() + other.isExclusiveWorkerAssigned = func() bool { return false } + other.assignExclusiveWorker = func() error { return nil } + client.EXPECT().PollWorkflowTaskQueue(gomock.Any(), gomock.Any()).Return(response, nil) + task, err = other.Poll(ctx, &pollMetadata{sessionOptions: &sessionpb.WorkerSessionOptions{ + Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: other.partition.RpcName()}, + }, + }}) + require.NoError(t, err) + require.NotNil(t, task, "a different session acquires independently") + + if !tc.timeout { + close(release) + } + first := await.Rcv(t, completed) + localPollers := 2 + if tc.empty || tc.timeout { + if tc.timeout { + require.ErrorIs(t, first.err, context.DeadlineExceeded) + } else { + require.ErrorIs(t, first.err, errNoTasks) + } + require.Nil(t, first.task) + await.Rcv(t, retryEntered) + synctest.Wait() + require.False(t, assigned.Load()) + require.Zero(t, assignmentCalls.Load(), "an unsuccessful acquisition must not assign the session") + require.NoError(t, ctx.Err(), "the first poll's deadline must not cancel waiting polls") + select { + case <-competing: + t.Fatal("waiting polls must not finish before the replacement acquisition succeeds") + default: + } + close(releaseRetry) + next := await.Rcv(t, competing) + require.NoError(t, next.err) + require.Same(t, response, next.task.pollWorkflowTaskQueueResponse()) + localPollers = 1 + } else { + require.NoError(t, first.err) + require.Same(t, response, first.task.pollWorkflowTaskQueueResponse()) + } + require.True(t, assigned.Load()) + require.Equal(t, int32(1), assignmentCalls.Load()) + synctest.Wait() + tm.data.lock.Lock() + waiting := tm.data.pollers.Len() + tm.data.lock.Unlock() + require.Equal(t, localPollers, waiting, "assigned pollers wait on the local matcher") + for id := int64(1); id <= int64(localPollers); id++ { + local := s.newBacklogTask(id, make(chan taskResponse, 1)) + require.NoError(t, tm.AddTask(local)) + got := await.Rcv(t, competing) + require.NoError(t, got.err) + require.Same(t, local, got.task) + } + }) + }) + } +} + +/* +TestSingleSessionEmptyAcquisitionCanRetry checks sequential acquisition attempts: +1. Return an empty response to the first forwarded poll and leave the session unassigned. +2. Issue another poll and return a workflow task. +3. Verify the second poll acquires and assigns the session. +*/ +func (s *PriMatcherSuite) TestSingleSessionEmptyAcquisitionCanRetry() { + t := s.T() + ctx := t.Context() + client := matchingservicemock.NewMockMatchingServiceClient(s.controller) + tm := s.newTestMatcher(ctx, testMatcherOpts{client: client}) + tm.partition = testMatcherTaskQueue.StickyPartition("session") + defer tm.Stop() + var assigned atomic.Bool + tm.isExclusiveWorkerAssigned = assigned.Load + tm.assignExclusiveWorker = func() error { + assigned.Store(true) + return nil + } + metadata := &pollMetadata{sessionOptions: &sessionpb.WorkerSessionOptions{ + Admission: &sessionpb.WorkerSessionOptions_SingleSession{SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "session"}}, + }} + response := &matchingservice.PollWorkflowTaskQueueResponse{TaskToken: []byte("token")} + gomock.InOrder( + client.EXPECT().PollWorkflowTaskQueue(gomock.Any(), gomock.Any()).Return(&matchingservice.PollWorkflowTaskQueueResponse{}, nil), + client.EXPECT().PollWorkflowTaskQueue(gomock.Any(), gomock.Any()).Return(response, nil), + ) + task, err := tm.Poll(ctx, metadata) + require.ErrorIs(t, err, errNoTasks) + require.Nil(t, task) + require.False(t, assigned.Load()) + task, err = tm.Poll(ctx, metadata) + require.NoError(t, err) + require.Same(t, response, task.pollWorkflowTaskQueueResponse()) + require.True(t, assigned.Load()) +} + +/* +TestSingleSessionWaitingPollContext checks a competing poll's lifetime: +1. Keep the acquisition owner blocked and start a second poll waiting behind it. +2. Cancel or expire the waiting poll and verify the owner is unaffected. +3. Alternatively, complete assignment and verify local polling retains the waiting poll's original deadline. +*/ +func (s *PriMatcherSuite) TestSingleSessionWaitingPollContext() { + for _, tc := range []struct { + name string + cancel bool + assigned bool + }{ + {name: "cancelled while waiting", cancel: true}, + {name: "deadline while waiting"}, + {name: "deadline retained after assignment", assigned: true}, + } { + s.Run(tc.name, func() { + synctest.Test(s.T(), func(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + client := matchingservicemock.NewMockMatchingServiceClient(s.controller) + tm := s.newTestMatcher(ctx, testMatcherOpts{client: client}) + tm.partition = testMatcherTaskQueue.StickyPartition("session") + defer tm.Stop() + var assigned atomic.Bool + tm.isExclusiveWorkerAssigned = assigned.Load + tm.assignExclusiveWorker = func() error { + assigned.Store(true) + return nil + } + metadata := &pollMetadata{sessionOptions: &sessionpb.WorkerSessionOptions{ + Admission: &sessionpb.WorkerSessionOptions_SingleSession{SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: "session"}}, + }} + entered := make(chan struct{}) + release := make(chan struct{}) + client.EXPECT().PollWorkflowTaskQueue(gomock.Any(), gomock.Any()).DoAndReturn( + func(callCtx context.Context, _ *matchingservice.PollWorkflowTaskQueueRequest, _ ...grpc.CallOption) (*matchingservice.PollWorkflowTaskQueueResponse, error) { + close(entered) + select { + case <-release: + return &matchingservice.PollWorkflowTaskQueueResponse{TaskToken: []byte("token")}, nil + case <-callCtx.Done(): + return nil, callCtx.Err() + } + }) + owner := make(chan error, 1) + go func() { + _, err := tm.Poll(ctx, metadata) + owner <- err + }() + await.Rcv(t, entered) + deadline := time.Now().Add(10 * time.Second) + waiterCtx, cancelWaiter := context.WithDeadline(ctx, deadline) + defer cancelWaiter() + waiter := make(chan error, 1) + go func() { + _, err := tm.Poll(waiterCtx, metadata) + waiter <- err + }() + synctest.Wait() + select { + case <-waiter: + t.Fatal("competing poll must wait for acquisition or its context") + default: + } + if tc.assigned { + timer := time.NewTimer(3 * time.Second) + defer timer.Stop() + await.Rcv(t, timer.C) + close(release) + require.NoError(t, await.Rcv(t, owner)) + synctest.Wait() + tm.data.lock.Lock() + waiting := tm.data.pollers.Len() + tm.data.lock.Unlock() + require.Equal(t, 1, waiting) + require.ErrorIs(t, await.Rcv(t, waiter), errNoTasks) + require.Equal(t, deadline, time.Now(), "local polling must retain the original deadline") + require.ErrorIs(t, waiterCtx.Err(), context.DeadlineExceeded) + return + } + if tc.cancel { + cancelWaiter() + } + require.ErrorIs(t, await.Rcv(t, waiter), errNoTasks) + require.NoError(t, ctx.Err(), "a waiting poll must not cancel the acquisition owner") + select { + case <-owner: + t.Fatal("acquisition must remain pending when a waiting poll ends") + default: + } + close(release) + require.NoError(t, await.Rcv(t, owner)) + }) + }) + } +} diff --git a/service/matching/task_queue_partition_manager.go b/service/matching/task_queue_partition_manager.go index 590fcac15b5..14512346d8b 100644 --- a/service/matching/task_queue_partition_manager.go +++ b/service/matching/task_queue_partition_manager.go @@ -1755,9 +1755,10 @@ func (pm *taskQueuePartitionManagerImpl) ephemeralDataChanged(data *taskqueuespb if pm.partition.Kind() != enumspb.TASK_QUEUE_KIND_STICKY { return } - dbq, err := pm.defaultQueueFuture.GetIfReady() + // Wait so initial backlog notifications are not lost during queue initialization. + dbq, err := pm.defaultQueueFuture.Get(context.Background()) if err != nil { - return // not initialized yet + return // initialization failed } // transpose map to more useful form @@ -1793,6 +1794,7 @@ func (pm *taskQueuePartitionManagerImpl) ephemeralDataChanged(data *taskqueuespb pm.versionedQueuesLock.RLock() defer pm.versionedQueuesLock.RUnlock() + // TODO (Shivam): seems to be a gap for sticky queues that are using the stable identity over here! for key, pqm := range pm.versionedQueues { update(key, pqm) } @@ -2236,6 +2238,12 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( if wfBehavior == enumspb.VERSIONING_BEHAVIOR_PINNED { if pm.partition.Kind() == enumspb.TASK_QUEUE_KIND_STICKY { // TODO (shahab): we can verify the passed deployment matches the last poller's deployment + // TODO (Shivam): we need to add the aforementioned deployment validation logic here since right now, + // we do not do versioning checks if a worker is using a single session option with a stable identity. + // What this means is that if another worker polls with the same stable identity but a different deployment + // version, then matching shall not respect versioning directives. + // For WFT's, this is fine since history invalidates such a match in RecordWorkflowTaskStarted. However, + // for activities, this is not the case and the task shall be dispatched thinking as an independent activity. return dbq, dbq, userDataChanged, targetDeploymentRevisionNumber, targetDeploymentVersion, nil } @@ -2279,6 +2287,7 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( } } + // TODO (Shivam): Add logic to handle the case of exclusive workers with AutoUpgrade workloads var targetDeploymentQueue physicalTaskQueueManager if directive.GetAssignedBuildId() == "" && targetDeployment != nil { if pm.partition.Kind() == enumspb.TASK_QUEUE_KIND_STICKY { diff --git a/service/matching/task_queue_partition_manager_test.go b/service/matching/task_queue_partition_manager_test.go index f512d001694..b845369a1fa 100644 --- a/service/matching/task_queue_partition_manager_test.go +++ b/service/matching/task_queue_partition_manager_test.go @@ -7,6 +7,7 @@ import ( "sync" "sync/atomic" "testing" + "testing/synctest" "time" "github.com/stretchr/testify/require" @@ -16,6 +17,7 @@ import ( enumspb "go.temporal.io/api/enums/v1" nexuspb "go.temporal.io/api/nexus/v1" "go.temporal.io/api/serviceerror" + sessionpb "go.temporal.io/api/session/v1" taskqueuepb "go.temporal.io/api/taskqueue/v1" "go.temporal.io/api/workflowservice/v1" deploymentspb "go.temporal.io/server/api/deployment/v1" @@ -34,6 +36,7 @@ import ( "go.temporal.io/server/common/testing/await" "go.temporal.io/server/common/testing/protorequire" "go.temporal.io/server/common/testing/testlogger" + "go.temporal.io/server/common/testing/testvars" "go.temporal.io/server/common/tqid" "go.temporal.io/server/common/worker_versioning" "go.temporal.io/server/service/matching/hooks" @@ -3061,3 +3064,89 @@ func (s *PartitionManagerTestSuite) newRateLimitedEagerDispatchPartitionManager( partitionMgr.rateLimitManager = limiter return partitionMgr } + +/* +TestSingleSessionPollRouting tests delegation to the physical queue manager: +1. Submit single-session workflow and activity polls, plus an ordinary sticky poll. +2. Verify the partition manager passes the original metadata to the physical queue. +3. Return its task and preserve the caller's acquisition flag. +*/ +func (s *PartitionManagerTestSuite) TestSingleSessionPollRouting() { + for _, tc := range []struct { + name string + activity bool + ordinary bool + }{ + {name: "single session workflow"}, + {name: "single session activity", activity: true}, + {name: "ordinary sticky poll", ordinary: true}, + } { + s.Run(tc.name, func() { + t := s.T() + tv := testvars.New(t) + pm := s.newEagerDispatchPartitionManager(0, nil) + taskType := enumspb.TASK_QUEUE_TYPE_WORKFLOW + if tc.activity { + taskType = enumspb.TASK_QUEUE_TYPE_ACTIVITY + } + pm.partition = newTestTaskQueue(namespaceID, tv.TaskQueue().GetName(), taskType).StickyPartition(tv.StickyTaskQueue().GetName()) + queue := pm.defaultQueue().(*MockphysicalTaskQueueManager) + metadata := &pollMetadata{} + if !tc.ordinary { + metadata.sessionOptions = &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: tv.StickyTaskQueue().GetName()}, + }} + } + response := &matchingservice.PollWorkflowTaskQueueResponse{TaskToken: []byte("token")} + expected := newInternalStartedTask(&startedTaskInfo{workflowTaskInfo: response}) + queue.EXPECT().PollTask(gomock.Any(), metadata).Return(expected, nil) + queue.EXPECT().MakePollerScalingDecision(gomock.Any(), gomock.Any(), expected).Return(nil) + ctx := context.WithValue(t.Context(), identityKey, tv.WorkerIdentity()) + queue.EXPECT().UpdatePollerInfo(pollerIdentity(tv.WorkerIdentity()), metadata).Times(2) + task, _, err := pm.PollTask(ctx, metadata) + require.NoError(t, err) + require.Same(t, expected, task) + require.False(t, metadata.isSingleSessionAcquisitionInProgress()) + }) + } +} + +/* +TestStickyBacklogNotificationBeforeQueueInitialization tests an early backlog advertisement: +1. Deliver a remote backlog update before the sticky queue finishes initialization. +2. Verify notification handling waits rather than discarding the update. +3. Finish initialization and verify the queue receives the advertised partition and priority. +*/ +func (s *PartitionManagerTestSuite) TestStickyBacklogNotificationBeforeQueueInitialization() { + synctest.Test(s.T(), func(t *testing.T) { + tv := testvars.New(t) + pm := s.newEagerDispatchPartitionManager(0, nil) + pm.partition = newTestTaskQueue(namespaceID, tv.TaskQueue().GetName(), enumspb.TASK_QUEUE_TYPE_WORKFLOW). + StickyPartition(tv.StickyTaskQueue().GetName()) + queue := pm.defaultQueue().(*MockphysicalTaskQueueManager) + pm.defaultQueueFuture = future.NewFuture[physicalTaskQueueManager]() + queue.EXPECT().UpdateRemotePriorityBacklogs(remotePriorityBacklogSet{ + {partition: 2, priority: 3}: {}, + }) + done := make(chan struct{}, 1) + go func() { + pm.ephemeralDataChanged(&taskqueuespb.EphemeralData{ + Partition: []*taskqueuespb.EphemeralData_ByPartition{{ + Partition: 2, + Version: []*taskqueuespb.EphemeralData_ByVersion{{ + BacklogPriorityLevels: 1 << 3, + }}, + }}, + }) + done <- struct{}{} + }() + synctest.Wait() + select { + case <-done: + t.Fatal("backlog notification must wait for queue initialization") + default: + } + pm.defaultQueueFuture.Set(queue, nil) + await.Rcv(t, done) + }) +} diff --git a/service/worker/scanner/taskqueue/handler.go b/service/worker/scanner/taskqueue/handler.go index 8c26ed7f836..4041fef704f 100644 --- a/service/worker/scanner/taskqueue/handler.go +++ b/service/worker/scanner/taskqueue/handler.go @@ -78,8 +78,8 @@ func (s *Scavenger) deleteHandler(key *p.TaskQueueKey, state *taskQueueState) ha } func (s *Scavenger) tryDeleteTaskQueue(key *p.TaskQueueKey, state *taskQueueState) { - if strings.HasPrefix(key.TaskQueueName, scannerTaskQueuePrefix) { - return // avoid deleting our own task queue + if state.retainMetadata || strings.HasPrefix(key.TaskQueueName, scannerTaskQueuePrefix) { + return } delta := time.Now().UTC().Sub(state.lastUpdated) diff --git a/service/worker/scanner/taskqueue/scavenger.go b/service/worker/scanner/taskqueue/scavenger.go index 1857976a984..688a3d84e15 100644 --- a/service/worker/scanner/taskqueue/scavenger.go +++ b/service/worker/scanner/taskqueue/scavenger.go @@ -32,8 +32,9 @@ type ( } taskQueueState struct { - rangeID int64 - lastUpdated time.Time + rangeID int64 + lastUpdated time.Time + retainMetadata bool } stats struct { @@ -198,8 +199,9 @@ func (s *Scavenger) newTask(info *p.PersistedTaskQueueInfo) executor.Task { TaskQueueType: info.Data.TaskType, }, taskQueueState: taskQueueState{ - rangeID: info.RangeID, - lastUpdated: info.Data.LastUpdateTime.AsTime(), + rangeID: info.RangeID, + lastUpdated: info.Data.LastUpdateTime.AsTime(), + retainMetadata: info.Data.GetExclusiveWorkerAssignment() != nil, }, scvg: s, } diff --git a/service/worker/scanner/taskqueue/scavenger_test.go b/service/worker/scanner/taskqueue/scavenger_test.go index fb1ca666691..79345d110fb 100644 --- a/service/worker/scanner/taskqueue/scavenger_test.go +++ b/service/worker/scanner/taskqueue/scavenger_test.go @@ -7,11 +7,16 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + enumspb "go.temporal.io/api/enums/v1" + persistencespb "go.temporal.io/server/api/persistence/v1" "go.temporal.io/server/common/log" "go.temporal.io/server/common/metrics" p "go.temporal.io/server/common/persistence" + "go.temporal.io/server/common/testing/testvars" "go.uber.org/mock/gomock" + "google.golang.org/protobuf/types/known/timestamppb" ) type ( @@ -188,3 +193,77 @@ func (s *ScavengerTestSuite) setupTaskMgrMocksWithErrors() { s.taskMgr.EXPECT().DeleteTaskQueue(gomock.Any(), gomock.Any()).Return(errTest) s.setupTaskMgrMocks() } + +/* +TestSingleSessionMetadataCleanup tests cleanup with a captured assignment value: +1. Scan old queue records with either an empty backlog or one expired task. +2. Remove expired tasks while retaining assigned sticky workflow queue metadata. +3. Delete eligible unassigned queue records using the captured RangeID. +*/ +// The existing scanner suite mutates package-level job limits. +// +//parallelize:ignore +func TestSingleSessionMetadataCleanup(t *testing.T) { + for _, tc := range []struct { + name string + assigned bool + kind enumspb.TaskQueueKind + taskType enumspb.TaskQueueType + retained bool + }{ + {name: "assigned workflow sticky", assigned: true, kind: enumspb.TASK_QUEUE_KIND_STICKY, taskType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, retained: true}, + {name: "unassigned workflow sticky", kind: enumspb.TASK_QUEUE_KIND_STICKY, taskType: enumspb.TASK_QUEUE_TYPE_WORKFLOW}, + {name: "activity sticky", kind: enumspb.TASK_QUEUE_KIND_STICKY, taskType: enumspb.TASK_QUEUE_TYPE_ACTIVITY}, + {name: "normal workflow", kind: enumspb.TASK_QUEUE_KIND_NORMAL, taskType: enumspb.TASK_QUEUE_TYPE_WORKFLOW}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + for _, expiredTask := range []bool{false, true} { + name := "empty queue" + if expiredTask { + name = "expired task" + } + t.Run(name, func(t *testing.T) { + t.Parallel() + tv := testvars.New(t) + manager := p.NewMockTaskManager(gomock.NewController(t)) + scavenger := NewScavenger(manager, metrics.NoopMetricsHandler, log.NewTestLogger()) + t.Cleanup(scavenger.lifecycleCancel) + info := &p.PersistedTaskQueueInfo{ + RangeID: 7, + Data: &persistencespb.TaskQueueInfo{ + NamespaceId: tv.NamespaceID().String(), Name: tv.StickyTaskQueue().GetName(), + TaskType: tc.taskType, Kind: tc.kind, + LastUpdateTime: timestamppb.New(time.Now().Add(-72 * time.Hour)), + }, + } + if tc.assigned { + info.Data.ExclusiveWorkerAssignment = &persistencespb.ExclusiveWorkerAssignment{} + } + response := &p.GetTasksResponse{} + if expiredTask { + response.Tasks = []*persistencespb.AllocatedTaskInfo{{TaskId: 1, Data: &persistencespb.TaskInfo{ExpiryTime: timestamppb.New(time.Now().Add(-time.Hour))}}} + manager.EXPECT().CompleteTasksLessThan(gomock.Any(), &p.CompleteTasksLessThanRequest{ + NamespaceID: info.Data.GetNamespaceId(), TaskQueueName: info.Data.GetName(), TaskType: tc.taskType, + ExclusiveMaxTaskID: 2, Limit: 1, + }).Return(1, nil) + } + manager.EXPECT().GetTasks(gomock.Any(), gomock.Any()).Return(response, nil) + if !tc.retained { + manager.EXPECT().DeleteTaskQueue(gomock.Any(), &p.DeleteTaskQueueRequest{ + TaskQueue: &p.TaskQueueKey{NamespaceID: info.Data.GetNamespaceId(), TaskQueueName: info.Data.GetName(), TaskQueueType: tc.taskType}, + RangeID: 7, + }).Return(nil) + } + require.Equal(t, handlerStatusDone, scavenger.newTask(info).Run()) + var expectedDeleted int64 + if !tc.retained { + expectedDeleted = 1 + } + require.Equal(t, expectedDeleted, scavenger.stats.taskqueue.nDeleted) + require.Equal(t, int64(len(response.Tasks)), scavenger.stats.task.nDeleted) + }) + } + }) + } +} diff --git a/tests/single_session_taskpoller_test.go b/tests/single_session_taskpoller_test.go new file mode 100644 index 00000000000..3ed666bca22 --- /dev/null +++ b/tests/single_session_taskpoller_test.go @@ -0,0 +1,533 @@ +package tests + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + commandpb "go.temporal.io/api/command/v1" + enumspb "go.temporal.io/api/enums/v1" + "go.temporal.io/api/serviceerror" + sessionpb "go.temporal.io/api/session/v1" + taskqueuepb "go.temporal.io/api/taskqueue/v1" + "go.temporal.io/api/workflowservice/v1" + deploymentspb "go.temporal.io/server/api/deployment/v1" + "go.temporal.io/server/api/matchingservice/v1" + taskqueuespb "go.temporal.io/server/api/taskqueue/v1" + "go.temporal.io/server/common/dynamicconfig" + "go.temporal.io/server/common/testing/await" + "go.temporal.io/server/common/testing/taskpoller" + "go.temporal.io/server/common/testing/testhooks" + "go.temporal.io/server/tests/testcore" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/emptypb" +) + +/* +TestSingleSessionAcquisitionAndReplacement tests admission, routing, and replacement: +1. Start A on the normal queue and reject a session poll while the admission flag is disabled. +2. Enable admission, acquire A through S, and schedule an activity in A's session. +3. Disable admission, signal A, and verify both sticky backlogs remain while both poll types are rejected. +4. Re-enable admission and deliver the original activity and workflow tasks at attempt 1. +5. Start B, unload S, and verify reloading S cannot acquire B while A remains running. +6. Resume A with a replacement poller using the same stable identity S, then let a fresh S2 acquire B. +7. Verify A keeps its original normal task queue and remains running. +*/ +func TestSingleSessionAcquisitionAndReplacement(t *testing.T) { + t.Parallel() + env := testcore.NewEnv(t, + testcore.WithDynamicConfig(dynamicconfig.EnableStickyActivityExecution, true), + testcore.WithDynamicConfig(dynamicconfig.MatchingNumTaskqueueReadPartitions, 1), + testcore.WithDynamicConfig(dynamicconfig.MatchingNumTaskqueueWritePartitions, 1), + ) + tv := env.Tv() + namespace := env.Namespace().String() + client := env.FrontendClient() + matching := env.GetTestCluster().MatchingClient() + poller := taskpoller.New(t, client, namespace) + session := &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: tv.StickyTaskQueue().GetName()}, + }} + _, err := client.StartWorkflowExecution(t.Context(), &workflowservice.StartWorkflowExecutionRequest{ + Namespace: namespace, WorkflowId: tv.WorkflowID(), RequestId: tv.Any().String(), + WorkflowType: tv.WorkflowType(), TaskQueue: tv.TaskQueue(), + WorkflowRunTimeout: durationpb.New(time.Minute), WorkflowTaskTimeout: durationpb.New(10 * time.Second), + }) + require.NoError(t, err) + workflowPollRequest := &workflowservice.PollWorkflowTaskQueueRequest{ + Namespace: namespace, TaskQueue: tv.StickyTaskQueue(), SessionOptions: session, + } + activityPollRequest := &workflowservice.PollActivityTaskQueueRequest{ + Namespace: namespace, TaskQueue: tv.StickyTaskQueue(), SessionOptions: session, + } + _, err = client.PollWorkflowTaskQueue(testcore.NewContext(t.Context()), workflowPollRequest) + var disabled *serviceerror.FailedPrecondition + require.ErrorAs(t, err, &disabled) + env.OverrideDynamicConfig(dynamicconfig.EnableExclusiveWorkerSessions, true) + + // With SessionOptions.StableIdentity set to the sticky task queue name, the worker will only poll the sticky task queue with poll forwarding to + // normal queue to only happen for the very first workflow task + stickyPoller := poller.PollWorkflowTask(workflowPollRequest) + _, err = stickyPoller.HandleTask(tv, func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + require.Equal(t, tv.WorkflowID(), task.GetWorkflowExecution().GetWorkflowId()) + return &workflowservice.RespondWorkflowTaskCompletedRequest{Commands: []*commandpb.Command{{ + CommandType: enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK, + Attributes: &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: &commandpb.ScheduleActivityTaskCommandAttributes{ + ActivityId: tv.ActivityID(), ActivityType: tv.ActivityType(), TaskQueue: tv.TaskQueue(), + StartToCloseTimeout: durationpb.New(10 * time.Second), + SessionOptions: &sessionpb.ActivitySessionOptions{Placement: &sessionpb.ActivitySessionOptions_WorkflowSession{WorkflowSession: &emptypb.Empty{}}}, + }}, + }}}, nil + }, taskpoller.WithContext(t.Context())) + require.NoError(t, err) + + env.OverrideDynamicConfig(dynamicconfig.EnableExclusiveWorkerSessions, false) + _, err = client.SignalWorkflowExecution(t.Context(), &workflowservice.SignalWorkflowExecutionRequest{ + Namespace: namespace, WorkflowExecution: tv.WorkflowExecution(), SignalName: tv.SignalName(), + }) + require.NoError(t, err) + await.Require(t.Context(), t, func(t *await.T) { + for _, taskType := range []enumspb.TaskQueueType{enumspb.TASK_QUEUE_TYPE_WORKFLOW, enumspb.TASK_QUEUE_TYPE_ACTIVITY} { + response, err := matching.DescribeTaskQueuePartition(t.Context(), &matchingservice.DescribeTaskQueuePartitionRequest{ + NamespaceId: env.NamespaceID().String(), + TaskQueuePartition: &taskqueuespb.TaskQueuePartition{ + TaskQueue: tv.TaskQueue().GetName(), TaskQueueType: taskType, + PartitionId: &taskqueuespb.TaskQueuePartition_StickyName{StickyName: tv.StickyTaskQueue().GetName()}, + }, + Versions: &taskqueuepb.TaskQueueVersionSelection{Unversioned: true}, ReportStats: true, OnlyIfLoaded: true, + }) + require.NoError(t, err) + require.Positive(t, response.GetVersionsInfoInternal()[""].GetPhysicalTaskQueueInfo().GetTaskQueueStats().GetApproximateBacklogCount()) + } + }, 10*time.Second, 10*time.Millisecond) + _, err = client.PollWorkflowTaskQueue(testcore.NewContext(t.Context()), workflowPollRequest) + require.ErrorAs(t, err, &disabled) + _, err = client.PollActivityTaskQueue(testcore.NewContext(t.Context()), activityPollRequest) + require.ErrorAs(t, err, &disabled) + env.OverrideDynamicConfig(dynamicconfig.EnableExclusiveWorkerSessions, true) + + _, err = poller.PollActivityTask(activityPollRequest).HandleTask(tv, func(task *workflowservice.PollActivityTaskQueueResponse) (*workflowservice.RespondActivityTaskCompletedRequest, error) { + require.Equal(t, tv.WorkflowID(), task.GetWorkflowExecution().GetWorkflowId()) + require.Equal(t, tv.ActivityID(), task.GetActivityId()) + require.Equal(t, int32(1), task.GetAttempt()) + return &workflowservice.RespondActivityTaskCompletedRequest{}, nil + }, taskpoller.WithContext(t.Context())) + require.NoError(t, err) + _, err = stickyPoller.HandleTask(tv, func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + require.Equal(t, tv.WorkflowID(), task.GetWorkflowExecution().GetWorkflowId()) + require.Equal(t, int32(1), task.GetAttempt()) + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + }, taskpoller.WithContext(t.Context())) + require.NoError(t, err) + + // Starting a new workflow execution with a different workflow ID. This is done to show that tasks from this workflow execution + // shall never be executed by the worker that is part of the first stable identity. The first task from this workflow execution + // shall be picked up from the normal queue, after which it shall be assigned a new stable identity. + other := tv.WithWorkflowIDNumber(2) + _, err = client.StartWorkflowExecution(t.Context(), &workflowservice.StartWorkflowExecutionRequest{ + Namespace: namespace, WorkflowId: other.WorkflowID(), RequestId: other.Any().String(), + WorkflowType: tv.WorkflowType(), TaskQueue: tv.TaskQueue(), + WorkflowRunTimeout: durationpb.New(time.Minute), WorkflowTaskTimeout: durationpb.New(10 * time.Second), + }) + require.NoError(t, err) + await.Require(t.Context(), t, func(t *await.T) { + response, err := client.DescribeTaskQueue(t.Context(), &workflowservice.DescribeTaskQueueRequest{ + Namespace: namespace, TaskQueue: tv.TaskQueue(), TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, + ReportStats: true, + }) + require.NoError(t, err) + require.Positive(t, response.GetStats().GetApproximateBacklogCount()) + }, 10*time.Second, 10*time.Millisecond) + + _, err = matching.ForceUnloadTaskQueuePartition(t.Context(), &matchingservice.ForceUnloadTaskQueuePartitionRequest{ + NamespaceId: env.NamespaceID().String(), + TaskQueuePartition: &taskqueuespb.TaskQueuePartition{ + TaskQueue: tv.TaskQueue().GetName(), TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, + PartitionId: &taskqueuespb.TaskQueuePartition_StickyName{StickyName: tv.StickyTaskQueue().GetName()}, + }, + }) + require.NoError(t, err) + empty, err := matching.PollWorkflowTaskQueue(t.Context(), &matchingservice.PollWorkflowTaskQueueRequest{ + NamespaceId: env.NamespaceID().String(), + PollRequest: &workflowservice.PollWorkflowTaskQueueRequest{ + TaskQueue: tv.StickyTaskQueue(), SessionOptions: session, Identity: tv.WorkerIdentity(), + }, + Conditions: &matchingservice.PollConditions{NoWait: true}, + }) + require.NoError(t, err) + require.Empty(t, empty.GetTaskToken(), "assigned S cannot acquire B after reload while A is still running") + + _, err = client.SignalWorkflowExecution(t.Context(), &workflowservice.SignalWorkflowExecutionRequest{ + Namespace: namespace, WorkflowExecution: tv.WorkflowExecution(), SignalName: tv.SignalName(), + }) + require.NoError(t, err) + _, err = poller.PollWorkflowTask(&workflowservice.PollWorkflowTaskQueueRequest{ + TaskQueue: tv.StickyTaskQueue(), SessionOptions: session, Identity: tv.WorkerIdentity() + "-replacement", + }).HandleTask(tv, func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + require.Equal(t, tv.WorkflowID(), task.GetWorkflowExecution().GetWorkflowId()) + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + }, taskpoller.WithContext(t.Context())) + require.NoError(t, err) + + freshQueue := tv.StickyTaskQueue() + freshQueue.Name = tv.StickyTaskQueue().GetName() + "-2" + // Changing the worker identity out here (synonymous to changing the ACS identity to something like "S2" + // to show that the worker identity is different from the original one, and is also able to acquire the new workflow's task) + _, err = poller.PollWorkflowTask(&workflowservice.PollWorkflowTaskQueueRequest{ + TaskQueue: freshQueue, + SessionOptions: &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: freshQueue.GetName()}, + }}, + }).HandleTask(other, func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + require.Equal(t, other.WorkflowID(), task.GetWorkflowExecution().GetWorkflowId()) + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + }, taskpoller.WithContext(t.Context())) + require.NoError(t, err) + description, err := client.DescribeWorkflowExecution(t.Context(), &workflowservice.DescribeWorkflowExecutionRequest{ + Namespace: namespace, Execution: tv.WorkflowExecution(), + }) + require.NoError(t, err) + require.Equal(t, tv.TaskQueue().GetName(), description.GetExecutionConfig().GetTaskQueue().GetName()) + require.Equal(t, enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, description.GetWorkflowExecutionInfo().GetStatus()) +} + +/* +TestSingleSessionConcurrentAcquisition tests exclusive acquisition with concurrent polls: +1. Start A and B on the same normal queue and wait for both tasks to reach its backlog. +2. Release eight Matching polls together, all using the same stable identity S. +3. Verify exactly one poll receives a workflow task and the other polls return empty. +4. Complete the winning workflow task and let a fresh S2 acquire the other execution. +*/ +func TestSingleSessionConcurrentAcquisition(t *testing.T) { + t.Parallel() + env := testcore.NewEnv(t, + testcore.WithDynamicConfig(dynamicconfig.EnableExclusiveWorkerSessions, true), + testcore.WithDynamicConfig(dynamicconfig.MatchingNumTaskqueueReadPartitions, 1), + testcore.WithDynamicConfig(dynamicconfig.MatchingNumTaskqueueWritePartitions, 1), + testcore.WithDynamicConfig(dynamicconfig.MatchingLongPollExpirationInterval, time.Second), + testcore.WithDynamicConfig(dynamicconfig.MatchingBacklogNegligibleAge, 20*time.Millisecond), + testcore.WithDynamicConfig(dynamicconfig.MatchingEphemeralDataUpdateInterval, 20*time.Millisecond), + ) + tv := env.Tv() + client := env.FrontendClient() + for _, execution := range []string{tv.WorkflowID(), tv.WithWorkflowIDNumber(2).WorkflowID()} { + _, err := client.StartWorkflowExecution(t.Context(), &workflowservice.StartWorkflowExecutionRequest{ + Namespace: env.Namespace().String(), WorkflowId: execution, RequestId: tv.Any().String(), + WorkflowType: tv.WorkflowType(), TaskQueue: tv.TaskQueue(), + WorkflowRunTimeout: durationpb.New(time.Minute), WorkflowTaskTimeout: durationpb.New(30 * time.Second), + }) + require.NoError(t, err) + } + await.Require(t.Context(), t, func(t *await.T) { + response, err := client.DescribeTaskQueue(t.Context(), &workflowservice.DescribeTaskQueueRequest{ + Namespace: env.Namespace().String(), TaskQueue: tv.TaskQueue(), + TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, ReportStats: true, + }) + require.NoError(t, err) + require.GreaterOrEqual(t, response.GetStats().GetApproximateBacklogCount(), int64(2)) + }, 10*time.Second, 10*time.Millisecond) + + session := &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: tv.StickyTaskQueue().GetName()}, + }} + type pollResult struct { + response *matchingservice.PollWorkflowTaskQueueResponse + err error + } + const pollers = 8 + results := make(chan pollResult, pollers) + start := make(chan struct{}) + matching := env.GetTestCluster().MatchingClient() + for range pollers { + go func() { + <-start + response, err := matching.PollWorkflowTaskQueue(t.Context(), &matchingservice.PollWorkflowTaskQueueRequest{ + NamespaceId: env.NamespaceID().String(), + PollRequest: &workflowservice.PollWorkflowTaskQueueRequest{ + TaskQueue: tv.StickyTaskQueue(), SessionOptions: session, Identity: tv.WorkerIdentity(), + }, + }) + results <- pollResult{response: response, err: err} + }() + } + close(start) + var acquired []*matchingservice.PollWorkflowTaskQueueResponse + for range pollers { + result := await.Rcv(t, results) + require.NoError(t, result.err) + if len(result.response.GetTaskToken()) > 0 { + acquired = append(acquired, result.response) + } + } + require.Len(t, acquired, 1, "only one initial acquisition wins for S") + winner := acquired[0] + expectedOther := tv.WithWorkflowIDNumber(2).WorkflowID() + if winner.GetWorkflowExecution().GetWorkflowId() == expectedOther { + expectedOther = tv.WorkflowID() + } else { + require.Equal(t, tv.WorkflowID(), winner.GetWorkflowExecution().GetWorkflowId()) + } + _, err := client.RespondWorkflowTaskCompleted(t.Context(), &workflowservice.RespondWorkflowTaskCompletedRequest{ + Namespace: env.Namespace().String(), TaskToken: winner.GetTaskToken(), Identity: tv.WorkerIdentity(), + }) + require.NoError(t, err) + + freshQueue := tv.StickyTaskQueue() + freshQueue.Name = freshQueue.GetName() + "-2" + _, err = taskpoller.New(t, client, env.Namespace().String()).PollWorkflowTask(&workflowservice.PollWorkflowTaskQueueRequest{ + Namespace: env.Namespace().String(), TaskQueue: freshQueue, Identity: tv.WorkerIdentity(), + SessionOptions: &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: freshQueue.GetName()}, + }}, + }).HandleTask(tv.WithWorkflowID(expectedOther), func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + require.Equal(t, expectedOther, task.GetWorkflowExecution().GetWorkflowId(), "the unrelated execution remains available to S2") + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + }, taskpoller.WithContext(t.Context())) + require.NoError(t, err) +} + +/* +TestSingleSessionWorkflowBacklogWithoutPollers tests offline backlog and queue reload: +1. Acquire A through S and complete its first workflow task. +2. Unload S and signal A without starting another poller. +3. Verify Matching reloads S with a backlog and no pollers, with no normal backlog or recorded schedule-to-start timeout. +4. Unload S again and poll it to resume the original workflow task at attempt 1. +*/ +func TestSingleSessionWorkflowBacklogWithoutPollers(t *testing.T) { + t.Parallel() + env := testcore.NewEnv(t, + testcore.WithDynamicConfig(dynamicconfig.EnableExclusiveWorkerSessions, true), + testcore.WithDynamicConfig(dynamicconfig.MatchingNumTaskqueueReadPartitions, 1), + testcore.WithDynamicConfig(dynamicconfig.MatchingNumTaskqueueWritePartitions, 1), + ) + tv := env.Tv() + client := env.FrontendClient() + namespace := env.Namespace().String() + _, err := client.StartWorkflowExecution(t.Context(), &workflowservice.StartWorkflowExecutionRequest{ + Namespace: namespace, WorkflowId: tv.WorkflowID(), RequestId: tv.Any().String(), + WorkflowType: tv.WorkflowType(), TaskQueue: tv.TaskQueue(), + WorkflowRunTimeout: durationpb.New(time.Minute), WorkflowTaskTimeout: durationpb.New(10 * time.Second), + }) + require.NoError(t, err) + session := &sessionpb.WorkerSessionOptions{Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: tv.StickyTaskQueue().GetName()}, + }} + poller := taskpoller.New(t, client, namespace).PollWorkflowTask(&workflowservice.PollWorkflowTaskQueueRequest{ + TaskQueue: tv.StickyTaskQueue(), SessionOptions: session, + }) + _, err = poller.HandleTask(tv, func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + require.Equal(t, tv.WorkflowID(), task.GetWorkflowExecution().GetWorkflowId()) + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + }, taskpoller.WithContext(t.Context())) + require.NoError(t, err) + matching := env.GetTestCluster().MatchingClient() + partition := &taskqueuespb.TaskQueuePartition{ + TaskQueue: tv.TaskQueue().GetName(), TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, + PartitionId: &taskqueuespb.TaskQueuePartition_StickyName{StickyName: tv.StickyTaskQueue().GetName()}, + } + // Unloading the task queue partition to simulate a worker was absent for a while. + _, err = matching.ForceUnloadTaskQueuePartition(t.Context(), &matchingservice.ForceUnloadTaskQueuePartitionRequest{ + NamespaceId: env.NamespaceID().String(), TaskQueuePartition: partition, + }) + require.NoError(t, err) + // Signaling the workflow execution to trigger a workflow task. + _, err = client.SignalWorkflowExecution(t.Context(), &workflowservice.SignalWorkflowExecutionRequest{ + Namespace: namespace, WorkflowExecution: tv.WorkflowExecution(), SignalName: tv.SignalName(), + }) + require.NoError(t, err) + + // After unloading the sticky task queue partition, the newly scheduled workflow task shall be spooled by loading the task queue partition again. + await.Require(t.Context(), t, func(t *await.T) { + history := env.SdkClient().GetWorkflowHistory(t.Context(), tv.WorkflowID(), "", false, enumspb.HISTORY_EVENT_FILTER_TYPE_ALL_EVENT) + var timeoutCount int + var signaled bool + for history.HasNext() { + event, err := history.Next() + require.NoError(t, err) + if event.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED { + signaled = true + } + if scheduled := event.GetWorkflowTaskScheduledEventAttributes(); scheduled != nil && signaled { + require.Equal(t, tv.StickyTaskQueue().GetName(), scheduled.GetTaskQueue().GetName()) + require.Equal(t, enumspb.TASK_QUEUE_KIND_STICKY, scheduled.GetTaskQueue().GetKind()) + } + if timedOut := event.GetWorkflowTaskTimedOutEventAttributes(); timedOut != nil { + require.Equal(t, enumspb.TIMEOUT_TYPE_SCHEDULE_TO_START, timedOut.GetTimeoutType()) + timeoutCount++ + } + } + require.Zero(t, timeoutCount, "the bound workflow task remains queued until a worker resumes") + response, err := matching.DescribeTaskQueuePartition(t.Context(), &matchingservice.DescribeTaskQueuePartitionRequest{ + NamespaceId: env.NamespaceID().String(), TaskQueuePartition: partition, + Versions: &taskqueuepb.TaskQueueVersionSelection{Unversioned: true}, + ReportStats: true, ReportPollers: true, OnlyIfLoaded: true, + }) + require.NoError(t, err) + physical := response.GetVersionsInfoInternal()[""].GetPhysicalTaskQueueInfo() + require.Empty(t, physical.GetPollers()) + require.Positive(t, physical.GetTaskQueueStats().GetApproximateBacklogCount()) + normal, err := client.DescribeTaskQueue(t.Context(), &workflowservice.DescribeTaskQueueRequest{ + Namespace: namespace, TaskQueue: tv.TaskQueue(), TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, ReportStats: true, + }) + require.NoError(t, err) + require.Zero(t, normal.GetStats().GetApproximateBacklogCount()) + }, 40*time.Second, 200*time.Millisecond) + + _, err = matching.ForceUnloadTaskQueuePartition(t.Context(), &matchingservice.ForceUnloadTaskQueuePartitionRequest{ + NamespaceId: env.NamespaceID().String(), TaskQueuePartition: partition, + }) + require.NoError(t, err) + _, err = poller.HandleTask(tv, func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + require.Equal(t, tv.WorkflowID(), task.GetWorkflowExecution().GetWorkflowId()) + require.Equal(t, int32(1), task.GetAttempt(), "the original workflow task resumes without a retry") + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + }, taskpoller.WithContext(t.Context())) + require.NoError(t, err) +} + +/* +TestSingleSessionAcquisitionUsesNormalPolling tests initial forwarding and subsequent sticky routing +with and without worker-versioning (Pinned only) +*/ +func TestSingleSessionAcquisitionUsesNormalPolling(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + readPartition int + writePartition int + pinned bool + }{ + {name: "root", readPartition: 0, writePartition: 0}, + {name: "non-root", readPartition: 2, writePartition: 2}, + {name: "pinned non-root", readPartition: 2, writePartition: 2, pinned: true}, + {name: "pinned parent forwarding", readPartition: 2, writePartition: 0, pinned: true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + env := newVersioning3TestEnv(t, + testcore.WithDynamicConfig(dynamicconfig.EnableExclusiveWorkerSessions, true), + testcore.WithDynamicConfig(dynamicconfig.MatchingNumTaskqueueReadPartitions, 3), + testcore.WithDynamicConfig(dynamicconfig.MatchingNumTaskqueueWritePartitions, 3), + testcore.WithDynamicConfig(dynamicconfig.MatchingForwarderMaxOutstandingTasks, 0), + testcore.WithDynamicConfig(dynamicconfig.MatchingPriorityBacklogForwarding, false), + ) + env.InjectHook(testhooks.NewHook(testhooks.MatchingLBForceReadPartition, tc.readPartition)) + env.InjectHook(testhooks.NewHook(testhooks.MatchingLBForceWritePartition, tc.writePartition)) + tv := env.Tv() + client := env.FrontendClient() + matching := env.GetTestCluster().MatchingClient() + stickyQueue := tv.StickyTaskQueue() + startRequest := &workflowservice.StartWorkflowExecutionRequest{ + Namespace: env.Namespace().String(), WorkflowId: tv.WorkflowID(), RequestId: tv.Any().String(), + WorkflowType: tv.WorkflowType(), TaskQueue: tv.TaskQueue(), + WorkflowRunTimeout: durationpb.New(time.Minute), WorkflowTaskTimeout: durationpb.New(30 * time.Second), + } + pollRequest := &workflowservice.PollWorkflowTaskQueueRequest{ + TaskQueue: stickyQueue, Identity: tv.WorkerIdentity(), + SessionOptions: &sessionpb.WorkerSessionOptions{ + Admission: &sessionpb.WorkerSessionOptions_SingleSession{ + SingleSession: &sessionpb.SingleSessionOptions{StableIdentity: stickyQueue.GetName()}, + }, + }, + } + versionKey := "" + if tc.pinned { + startRequest.VersioningOverride = tv.VersioningOverridePinned() + pollRequest.DeploymentOptions = tv.WorkerDeploymentOptions(true) + versionKey = tv.DeploymentVersionStringV32() + _, err := matching.SyncDeploymentUserData(t.Context(), &matchingservice.SyncDeploymentUserDataRequest{ + NamespaceId: env.NamespaceID().String(), TaskQueue: tv.TaskQueue().GetName(), + TaskQueueTypes: []enumspb.TaskQueueType{enumspb.TASK_QUEUE_TYPE_WORKFLOW}, + DeploymentName: tv.DeploymentSeries(), + UpsertVersionsData: map[string]*deploymentspb.WorkerDeploymentVersionData{ + tv.BuildID(): {Status: enumspb.WORKER_DEPLOYMENT_VERSION_STATUS_INACTIVE}, + }, + }) + require.NoError(t, err) + _, err = client.StartWorkflowExecution(t.Context(), startRequest) + require.NoError(t, err) + await.Require(t.Context(), t, func(t *await.T) { + response, err := matching.DescribeTaskQueuePartition(t.Context(), &matchingservice.DescribeTaskQueuePartitionRequest{ + NamespaceId: env.NamespaceID().String(), + TaskQueuePartition: &taskqueuespb.TaskQueuePartition{ + TaskQueue: tv.TaskQueue().GetName(), TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, + PartitionId: &taskqueuespb.TaskQueuePartition_NormalPartitionId{NormalPartitionId: int32(tc.writePartition)}, + }, + Versions: &taskqueuepb.TaskQueueVersionSelection{Unversioned: true, AllActive: true}, + ReportStats: true, ReportPollers: true, OnlyIfLoaded: true, + }) + require.NoError(t, err) + versions := response.GetVersionsInfoInternal() + require.Zero(t, versions[""].GetPhysicalTaskQueueInfo().GetTaskQueueStats().GetApproximateBacklogCount()) + require.Positive(t, versions[versionKey].GetPhysicalTaskQueueInfo().GetTaskQueueStats().GetApproximateBacklogCount()) + for _, version := range versions { + require.Empty(t, version.GetPhysicalTaskQueueInfo().GetPollers()) + } + }, 10*time.Second, 20*time.Millisecond) + } + + poller := taskpoller.New(t, client, env.Namespace().String()).PollWorkflowTask(pollRequest) + acquired := make(chan *workflowservice.PollWorkflowTaskQueueResponse, 2) + handle := func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + acquired <- task + response := &workflowservice.RespondWorkflowTaskCompletedRequest{} + if tc.pinned { + response.VersioningBehavior = enumspb.VERSIONING_BEHAVIOR_PINNED + response.DeploymentOptions = tv.WorkerDeploymentOptions(true) + } + return response, nil + } + completed := make(chan error, 1) + go func() { + _, err := poller.HandleTask(tv, handle, taskpoller.WithContext(t.Context())) + completed <- err + }() + + // Only the sticky poll is started; its acquisition reaches the normal read partition without advertisements. + // The main idea of this test is that for workers that have opted-in to Sessions and Worker-Versioning, their first poll should + // land on a normal task queue partition. After that, the poll metadata shall dictate if this poller is matched with a versioned task + // or a non-versioned task. + await.Require(t.Context(), t, func(t *await.T) { + response, err := matching.DescribeTaskQueuePartition(t.Context(), &matchingservice.DescribeTaskQueuePartitionRequest{ + NamespaceId: env.NamespaceID().String(), + TaskQueuePartition: &taskqueuespb.TaskQueuePartition{ + TaskQueue: tv.TaskQueue().GetName(), TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, + PartitionId: &taskqueuespb.TaskQueuePartition_NormalPartitionId{NormalPartitionId: int32(tc.readPartition)}, + }, + Versions: &taskqueuepb.TaskQueueVersionSelection{Unversioned: true, AllActive: true}, + ReportPollers: true, OnlyIfLoaded: true, + }) + require.NoError(t, err) + pollers := response.GetVersionsInfoInternal()[versionKey].GetPhysicalTaskQueueInfo().GetPollers() + require.NotEmpty(t, pollers) + require.Equal(t, tv.WorkerIdentity(), pollers[0].GetIdentity()) + }, 10*time.Second, 20*time.Millisecond) + if !tc.pinned { + _, err := client.StartWorkflowExecution(t.Context(), startRequest) + require.NoError(t, err) + } + require.NoError(t, await.Rcv(t, completed)) + require.Equal(t, tv.WorkflowID(), await.Rcv(t, acquired).GetWorkflowExecution().GetWorkflowId()) + + sticky, err := matching.DescribeTaskQueuePartition(t.Context(), &matchingservice.DescribeTaskQueuePartitionRequest{ + NamespaceId: env.NamespaceID().String(), + TaskQueuePartition: &taskqueuespb.TaskQueuePartition{ + TaskQueue: tv.TaskQueue().GetName(), TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW, + PartitionId: &taskqueuespb.TaskQueuePartition_StickyName{StickyName: stickyQueue.GetName()}, + }, + Versions: &taskqueuepb.TaskQueueVersionSelection{Unversioned: true, AllActive: true}, + }) + require.NoError(t, err) + require.Len(t, sticky.GetVersionsInfoInternal(), 1) + require.Contains(t, sticky.GetVersionsInfoInternal(), "", "S has no versioned physical queues") + + _, err = client.SignalWorkflowExecution(t.Context(), &workflowservice.SignalWorkflowExecutionRequest{ + Namespace: env.Namespace().String(), WorkflowExecution: tv.WorkflowExecution(), SignalName: tv.SignalName(), + }) + require.NoError(t, err) + _, err = poller.HandleTask(tv, handle, taskpoller.WithContext(t.Context())) + require.NoError(t, err) + require.Equal(t, tv.WorkflowID(), await.Rcv(t, acquired).GetWorkflowExecution().GetWorkflowId()) + }) + } +}