diff --git a/api/historyservice/v1/request_response.go-helpers.pb.go b/api/historyservice/v1/request_response.go-helpers.pb.go index b64e5c3c32e..68cf73e2c50 100644 --- a/api/historyservice/v1/request_response.go-helpers.pb.go +++ b/api/historyservice/v1/request_response.go-helpers.pb.go @@ -5998,3 +5998,77 @@ func (this *CancelNexusOperationResponse) Equal(that interface{}) bool { return proto.Equal(this, that1) } + +// Marshal an object of type PollWorkflowExecutionTimeSkippingRequest to the protobuf v3 wire format +func (val *PollWorkflowExecutionTimeSkippingRequest) Marshal() ([]byte, error) { + return proto.Marshal(val) +} + +// Unmarshal an object of type PollWorkflowExecutionTimeSkippingRequest from the protobuf v3 wire format +func (val *PollWorkflowExecutionTimeSkippingRequest) Unmarshal(buf []byte) error { + return proto.Unmarshal(buf, val) +} + +// Size returns the size of the object, in bytes, once serialized +func (val *PollWorkflowExecutionTimeSkippingRequest) Size() int { + return proto.Size(val) +} + +// Equal returns whether two PollWorkflowExecutionTimeSkippingRequest 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 *PollWorkflowExecutionTimeSkippingRequest) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + var that1 *PollWorkflowExecutionTimeSkippingRequest + switch t := that.(type) { + case *PollWorkflowExecutionTimeSkippingRequest: + that1 = t + case PollWorkflowExecutionTimeSkippingRequest: + that1 = &t + default: + return false + } + + return proto.Equal(this, that1) +} + +// Marshal an object of type PollWorkflowExecutionTimeSkippingResponse to the protobuf v3 wire format +func (val *PollWorkflowExecutionTimeSkippingResponse) Marshal() ([]byte, error) { + return proto.Marshal(val) +} + +// Unmarshal an object of type PollWorkflowExecutionTimeSkippingResponse from the protobuf v3 wire format +func (val *PollWorkflowExecutionTimeSkippingResponse) Unmarshal(buf []byte) error { + return proto.Unmarshal(buf, val) +} + +// Size returns the size of the object, in bytes, once serialized +func (val *PollWorkflowExecutionTimeSkippingResponse) Size() int { + return proto.Size(val) +} + +// Equal returns whether two PollWorkflowExecutionTimeSkippingResponse 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 *PollWorkflowExecutionTimeSkippingResponse) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + var that1 *PollWorkflowExecutionTimeSkippingResponse + switch t := that.(type) { + case *PollWorkflowExecutionTimeSkippingResponse: + that1 = t + case PollWorkflowExecutionTimeSkippingResponse: + that1 = &t + default: + return false + } + + return proto.Equal(this, that1) +} diff --git a/api/historyservice/v1/request_response.pb.go b/api/historyservice/v1/request_response.pb.go index f5508bc2e1c..e91eb145872 100644 --- a/api/historyservice/v1/request_response.pb.go +++ b/api/historyservice/v1/request_response.pb.go @@ -10300,6 +10300,102 @@ func (x *CancelNexusOperationResponse) GetResponse() *v121.CancelOperationRespon return nil } +type PollWorkflowExecutionTimeSkippingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NamespaceId string `protobuf:"bytes,1,opt,name=namespace_id,json=namespaceId,proto3" json:"namespace_id,omitempty"` + Request *v1.PollWorkflowExecutionTimeSkippingRequest `protobuf:"bytes,2,opt,name=request,proto3" json:"request,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PollWorkflowExecutionTimeSkippingRequest) Reset() { + *x = PollWorkflowExecutionTimeSkippingRequest{} + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[162] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PollWorkflowExecutionTimeSkippingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PollWorkflowExecutionTimeSkippingRequest) ProtoMessage() {} + +func (x *PollWorkflowExecutionTimeSkippingRequest) ProtoReflect() protoreflect.Message { + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[162] + 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 PollWorkflowExecutionTimeSkippingRequest.ProtoReflect.Descriptor instead. +func (*PollWorkflowExecutionTimeSkippingRequest) Descriptor() ([]byte, []int) { + return file_temporal_server_api_historyservice_v1_request_response_proto_rawDescGZIP(), []int{162} +} + +func (x *PollWorkflowExecutionTimeSkippingRequest) GetNamespaceId() string { + if x != nil { + return x.NamespaceId + } + return "" +} + +func (x *PollWorkflowExecutionTimeSkippingRequest) GetRequest() *v1.PollWorkflowExecutionTimeSkippingRequest { + if x != nil { + return x.Request + } + return nil +} + +type PollWorkflowExecutionTimeSkippingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Response *v1.PollWorkflowExecutionTimeSkippingResponse `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PollWorkflowExecutionTimeSkippingResponse) Reset() { + *x = PollWorkflowExecutionTimeSkippingResponse{} + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[163] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PollWorkflowExecutionTimeSkippingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PollWorkflowExecutionTimeSkippingResponse) ProtoMessage() {} + +func (x *PollWorkflowExecutionTimeSkippingResponse) ProtoReflect() protoreflect.Message { + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[163] + 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 PollWorkflowExecutionTimeSkippingResponse.ProtoReflect.Descriptor instead. +func (*PollWorkflowExecutionTimeSkippingResponse) Descriptor() ([]byte, []int) { + return file_temporal_server_api_historyservice_v1_request_response_proto_rawDescGZIP(), []int{163} +} + +func (x *PollWorkflowExecutionTimeSkippingResponse) GetResponse() *v1.PollWorkflowExecutionTimeSkippingResponse { + if x != nil { + return x.Response + } + return nil +} + type ExecuteMultiOperationRequest_Operation struct { state protoimpl.MessageState `protogen:"open.v1"` // Types that are valid to be assigned to Operation: @@ -10313,7 +10409,7 @@ type ExecuteMultiOperationRequest_Operation struct { func (x *ExecuteMultiOperationRequest_Operation) Reset() { *x = ExecuteMultiOperationRequest_Operation{} - mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[162] + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10325,7 +10421,7 @@ func (x *ExecuteMultiOperationRequest_Operation) String() string { func (*ExecuteMultiOperationRequest_Operation) ProtoMessage() {} func (x *ExecuteMultiOperationRequest_Operation) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[162] + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10397,7 +10493,7 @@ type ExecuteMultiOperationResponse_Response struct { func (x *ExecuteMultiOperationResponse_Response) Reset() { *x = ExecuteMultiOperationResponse_Response{} - mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[163] + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10409,7 +10505,7 @@ func (x *ExecuteMultiOperationResponse_Response) String() string { func (*ExecuteMultiOperationResponse_Response) ProtoMessage() {} func (x *ExecuteMultiOperationResponse_Response) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[163] + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10479,7 +10575,7 @@ type ListQueuesResponse_QueueInfo struct { func (x *ListQueuesResponse_QueueInfo) Reset() { *x = ListQueuesResponse_QueueInfo{} - mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[169] + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10491,7 +10587,7 @@ func (x *ListQueuesResponse_QueueInfo) String() string { func (*ListQueuesResponse_QueueInfo) ProtoMessage() {} func (x *ListQueuesResponse_QueueInfo) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[169] + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10542,7 +10638,7 @@ type AddTasksRequest_Task struct { func (x *AddTasksRequest_Task) Reset() { *x = AddTasksRequest_Task{} - mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[170] + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10554,7 +10650,7 @@ func (x *AddTasksRequest_Task) String() string { func (*AddTasksRequest_Task) ProtoMessage() {} func (x *AddTasksRequest_Task) ProtoReflect() protoreflect.Message { - mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[170] + mi := &file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11375,7 +11471,12 @@ const file_temporal_server_api_historyservice_v1_request_response_proto_rawDesc "\arequest\x18\x03 \x01(\v2-.temporal.api.nexus.v1.CancelOperationRequestR\arequest:\x0e\x92\xc4\x03\n" + "\x1a\bshard_id\"j\n" + "\x1cCancelNexusOperationResponse\x12J\n" + - "\bresponse\x18\x01 \x01(\v2..temporal.api.nexus.v1.CancelOperationResponseR\bresponse:t\n" + + "\bresponse\x18\x01 \x01(\v2..temporal.api.nexus.v1.CancelOperationResponseR\bresponse\"\xe0\x01\n" + + "(PollWorkflowExecutionTimeSkippingRequest\x12!\n" + + "\fnamespace_id\x18\x01 \x01(\tR\vnamespaceId\x12c\n" + + "\arequest\x18\x02 \x01(\v2I.temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingRequestR\arequest:,\x92\xc4\x03(*&request.workflow_execution.workflow_id\"\x93\x01\n" + + ")PollWorkflowExecutionTimeSkippingResponse\x12f\n" + + "\bresponse\x18\x01 \x01(\v2J.temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingResponseR\bresponse:t\n" + "\arouting\x12\x1f.google.protobuf.MessageOptions\x18\xc28 \x01(\v25.temporal.server.api.historyservice.v1.RoutingOptionsR\arouting\x88\x01\x01B temporal.api.workflowservice.v1.StartWorkflowExecutionRequest - 172, // 1: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.parent_execution_info:type_name -> temporal.server.api.workflow.v1.ParentExecutionInfo - 173, // 2: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.workflow_execution_expiration_time:type_name -> google.protobuf.Timestamp - 174, // 3: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.continue_as_new_initiator:type_name -> temporal.api.enums.v1.ContinueAsNewInitiator - 175, // 4: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.continued_failure:type_name -> temporal.api.failure.v1.Failure - 176, // 5: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.last_completion_result:type_name -> temporal.api.common.v1.Payloads - 177, // 6: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.first_workflow_task_backoff:type_name -> google.protobuf.Duration - 178, // 7: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.source_version_stamp:type_name -> temporal.api.common.v1.WorkerVersionStamp - 179, // 8: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.root_execution_info:type_name -> temporal.server.api.workflow.v1.RootExecutionInfo - 180, // 9: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.versioning_override:type_name -> temporal.api.workflow.v1.VersioningOverride - 181, // 10: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.inherited_pinned_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion - 182, // 11: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.inherited_auto_upgrade_info:type_name -> temporal.api.deployment.v1.InheritedAutoUpgradeInfo - 183, // 12: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.declined_target_version_upgrade:type_name -> temporal.api.history.v1.DeclinedTargetVersionUpgrade - 184, // 13: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.time_skipping_state_propagation:type_name -> temporal.api.common.v1.TimeSkippingStatePropagation - 185, // 14: temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 186, // 15: temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse.eager_workflow_task:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponse - 187, // 16: temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse.status:type_name -> temporal.api.enums.v1.WorkflowExecutionStatus - 188, // 17: temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse.link:type_name -> temporal.api.common.v1.Link - 189, // 18: temporal.server.api.historyservice.v1.GetMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 190, // 19: temporal.server.api.historyservice.v1.GetMutableStateRequest.version_history_item:type_name -> temporal.server.api.history.v1.VersionHistoryItem - 191, // 20: temporal.server.api.historyservice.v1.GetMutableStateRequest.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 189, // 21: temporal.server.api.historyservice.v1.GetMutableStateResponse.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 192, // 22: temporal.server.api.historyservice.v1.GetMutableStateResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 193, // 23: temporal.server.api.historyservice.v1.GetMutableStateResponse.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 193, // 24: temporal.server.api.historyservice.v1.GetMutableStateResponse.sticky_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 177, // 25: temporal.server.api.historyservice.v1.GetMutableStateResponse.sticky_task_queue_schedule_to_start_timeout:type_name -> google.protobuf.Duration - 194, // 26: temporal.server.api.historyservice.v1.GetMutableStateResponse.workflow_state:type_name -> temporal.server.api.enums.v1.WorkflowExecutionState - 187, // 27: temporal.server.api.historyservice.v1.GetMutableStateResponse.workflow_status:type_name -> temporal.api.enums.v1.WorkflowExecutionStatus - 195, // 28: temporal.server.api.historyservice.v1.GetMutableStateResponse.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories - 178, // 29: temporal.server.api.historyservice.v1.GetMutableStateResponse.most_recent_worker_version_stamp:type_name -> temporal.api.common.v1.WorkerVersionStamp - 191, // 30: temporal.server.api.historyservice.v1.GetMutableStateResponse.transition_history:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 196, // 31: temporal.server.api.historyservice.v1.GetMutableStateResponse.versioning_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionVersioningInfo - 197, // 32: temporal.server.api.historyservice.v1.GetMutableStateResponse.transient_or_speculative_tasks:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo - 189, // 33: temporal.server.api.historyservice.v1.PollMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 190, // 34: temporal.server.api.historyservice.v1.PollMutableStateRequest.version_history_item:type_name -> temporal.server.api.history.v1.VersionHistoryItem - 189, // 35: temporal.server.api.historyservice.v1.PollMutableStateResponse.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 192, // 36: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 193, // 37: temporal.server.api.historyservice.v1.PollMutableStateResponse.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 193, // 38: temporal.server.api.historyservice.v1.PollMutableStateResponse.sticky_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 177, // 39: temporal.server.api.historyservice.v1.PollMutableStateResponse.sticky_task_queue_schedule_to_start_timeout:type_name -> google.protobuf.Duration - 195, // 40: temporal.server.api.historyservice.v1.PollMutableStateResponse.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories - 194, // 41: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_state:type_name -> temporal.server.api.enums.v1.WorkflowExecutionState - 187, // 42: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_status:type_name -> temporal.api.enums.v1.WorkflowExecutionStatus - 189, // 43: temporal.server.api.historyservice.v1.ResetStickyTaskQueueRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 162, // 44: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.operations:type_name -> temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation - 163, // 45: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.responses:type_name -> temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response - 189, // 46: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 198, // 47: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.poll_request:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueRequest - 185, // 48: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 199, // 49: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.build_id_redirect_info:type_name -> temporal.server.api.taskqueue.v1.BuildIdRedirectInfo - 200, // 50: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.scheduled_deployment:type_name -> temporal.api.deployment.v1.Deployment - 201, // 51: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective - 181, // 52: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.target_deployment_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion - 192, // 53: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 197, // 54: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo - 193, // 55: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 173, // 56: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.scheduled_time:type_name -> google.protobuf.Timestamp - 173, // 57: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp - 164, // 58: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry - 185, // 59: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 202, // 60: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.messages:type_name -> temporal.api.protocol.v1.Message - 203, // 61: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.history:type_name -> temporal.api.history.v1.History - 203, // 62: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.raw_history:type_name -> temporal.api.history.v1.History - 192, // 63: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 197, // 64: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo - 193, // 65: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 173, // 66: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.scheduled_time:type_name -> google.protobuf.Timestamp - 173, // 67: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.started_time:type_name -> google.protobuf.Timestamp - 165, // 68: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry - 185, // 69: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 202, // 70: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.messages:type_name -> temporal.api.protocol.v1.Message - 203, // 71: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.history:type_name -> temporal.api.history.v1.History - 189, // 72: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 204, // 73: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.poll_request:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueRequest - 185, // 74: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 199, // 75: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.build_id_redirect_info:type_name -> temporal.server.api.taskqueue.v1.BuildIdRedirectInfo - 200, // 76: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.scheduled_deployment:type_name -> temporal.api.deployment.v1.Deployment - 201, // 77: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective - 205, // 78: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.scheduled_event:type_name -> temporal.api.history.v1.HistoryEvent - 173, // 79: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp - 173, // 80: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.current_attempt_scheduled_time:type_name -> google.protobuf.Timestamp - 176, // 81: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.heartbeat_details:type_name -> temporal.api.common.v1.Payloads - 192, // 82: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 185, // 83: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 206, // 84: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.priority:type_name -> temporal.api.common.v1.Priority - 207, // 85: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.retry_policy:type_name -> temporal.api.common.v1.RetryPolicy - 208, // 86: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskCompletedRequest + 173, // 0: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.start_request:type_name -> temporal.api.workflowservice.v1.StartWorkflowExecutionRequest + 174, // 1: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.parent_execution_info:type_name -> temporal.server.api.workflow.v1.ParentExecutionInfo + 175, // 2: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.workflow_execution_expiration_time:type_name -> google.protobuf.Timestamp + 176, // 3: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.continue_as_new_initiator:type_name -> temporal.api.enums.v1.ContinueAsNewInitiator + 177, // 4: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.continued_failure:type_name -> temporal.api.failure.v1.Failure + 178, // 5: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.last_completion_result:type_name -> temporal.api.common.v1.Payloads + 179, // 6: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.first_workflow_task_backoff:type_name -> google.protobuf.Duration + 180, // 7: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.source_version_stamp:type_name -> temporal.api.common.v1.WorkerVersionStamp + 181, // 8: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.root_execution_info:type_name -> temporal.server.api.workflow.v1.RootExecutionInfo + 182, // 9: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.versioning_override:type_name -> temporal.api.workflow.v1.VersioningOverride + 183, // 10: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.inherited_pinned_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion + 184, // 11: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.inherited_auto_upgrade_info:type_name -> temporal.api.deployment.v1.InheritedAutoUpgradeInfo + 185, // 12: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.declined_target_version_upgrade:type_name -> temporal.api.history.v1.DeclinedTargetVersionUpgrade + 186, // 13: temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest.time_skipping_state_propagation:type_name -> temporal.api.common.v1.TimeSkippingStatePropagation + 187, // 14: temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 188, // 15: temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse.eager_workflow_task:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponse + 189, // 16: temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse.status:type_name -> temporal.api.enums.v1.WorkflowExecutionStatus + 190, // 17: temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse.link:type_name -> temporal.api.common.v1.Link + 191, // 18: temporal.server.api.historyservice.v1.GetMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 192, // 19: temporal.server.api.historyservice.v1.GetMutableStateRequest.version_history_item:type_name -> temporal.server.api.history.v1.VersionHistoryItem + 193, // 20: temporal.server.api.historyservice.v1.GetMutableStateRequest.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 191, // 21: temporal.server.api.historyservice.v1.GetMutableStateResponse.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 194, // 22: temporal.server.api.historyservice.v1.GetMutableStateResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 195, // 23: temporal.server.api.historyservice.v1.GetMutableStateResponse.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 195, // 24: temporal.server.api.historyservice.v1.GetMutableStateResponse.sticky_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 179, // 25: temporal.server.api.historyservice.v1.GetMutableStateResponse.sticky_task_queue_schedule_to_start_timeout:type_name -> google.protobuf.Duration + 196, // 26: temporal.server.api.historyservice.v1.GetMutableStateResponse.workflow_state:type_name -> temporal.server.api.enums.v1.WorkflowExecutionState + 189, // 27: temporal.server.api.historyservice.v1.GetMutableStateResponse.workflow_status:type_name -> temporal.api.enums.v1.WorkflowExecutionStatus + 197, // 28: temporal.server.api.historyservice.v1.GetMutableStateResponse.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories + 180, // 29: temporal.server.api.historyservice.v1.GetMutableStateResponse.most_recent_worker_version_stamp:type_name -> temporal.api.common.v1.WorkerVersionStamp + 193, // 30: temporal.server.api.historyservice.v1.GetMutableStateResponse.transition_history:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 198, // 31: temporal.server.api.historyservice.v1.GetMutableStateResponse.versioning_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionVersioningInfo + 199, // 32: temporal.server.api.historyservice.v1.GetMutableStateResponse.transient_or_speculative_tasks:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo + 191, // 33: temporal.server.api.historyservice.v1.PollMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 192, // 34: temporal.server.api.historyservice.v1.PollMutableStateRequest.version_history_item:type_name -> temporal.server.api.history.v1.VersionHistoryItem + 191, // 35: temporal.server.api.historyservice.v1.PollMutableStateResponse.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 194, // 36: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 195, // 37: temporal.server.api.historyservice.v1.PollMutableStateResponse.task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 195, // 38: temporal.server.api.historyservice.v1.PollMutableStateResponse.sticky_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 179, // 39: temporal.server.api.historyservice.v1.PollMutableStateResponse.sticky_task_queue_schedule_to_start_timeout:type_name -> google.protobuf.Duration + 197, // 40: temporal.server.api.historyservice.v1.PollMutableStateResponse.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories + 196, // 41: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_state:type_name -> temporal.server.api.enums.v1.WorkflowExecutionState + 189, // 42: temporal.server.api.historyservice.v1.PollMutableStateResponse.workflow_status:type_name -> temporal.api.enums.v1.WorkflowExecutionStatus + 191, // 43: temporal.server.api.historyservice.v1.ResetStickyTaskQueueRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 164, // 44: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.operations:type_name -> temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation + 165, // 45: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.responses:type_name -> temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response + 191, // 46: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 200, // 47: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.poll_request:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueRequest + 187, // 48: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 201, // 49: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.build_id_redirect_info:type_name -> temporal.server.api.taskqueue.v1.BuildIdRedirectInfo + 202, // 50: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.scheduled_deployment:type_name -> temporal.api.deployment.v1.Deployment + 203, // 51: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective + 183, // 52: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.target_deployment_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion + 194, // 53: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 199, // 54: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo + 195, // 55: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 175, // 56: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.scheduled_time:type_name -> google.protobuf.Timestamp + 175, // 57: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp + 166, // 58: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry + 187, // 59: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 204, // 60: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.messages:type_name -> temporal.api.protocol.v1.Message + 205, // 61: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.history:type_name -> temporal.api.history.v1.History + 205, // 62: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.raw_history:type_name -> temporal.api.history.v1.History + 194, // 63: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 199, // 64: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo + 195, // 65: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 175, // 66: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.scheduled_time:type_name -> google.protobuf.Timestamp + 175, // 67: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.started_time:type_name -> google.protobuf.Timestamp + 167, // 68: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry + 187, // 69: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 204, // 70: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.messages:type_name -> temporal.api.protocol.v1.Message + 205, // 71: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.history:type_name -> temporal.api.history.v1.History + 191, // 72: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 206, // 73: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.poll_request:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueRequest + 187, // 74: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 201, // 75: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.build_id_redirect_info:type_name -> temporal.server.api.taskqueue.v1.BuildIdRedirectInfo + 202, // 76: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.scheduled_deployment:type_name -> temporal.api.deployment.v1.Deployment + 203, // 77: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective + 207, // 78: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.scheduled_event:type_name -> temporal.api.history.v1.HistoryEvent + 175, // 79: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp + 175, // 80: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.current_attempt_scheduled_time:type_name -> google.protobuf.Timestamp + 178, // 81: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.heartbeat_details:type_name -> temporal.api.common.v1.Payloads + 194, // 82: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 187, // 83: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 208, // 84: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.priority:type_name -> temporal.api.common.v1.Priority + 209, // 85: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.retry_policy:type_name -> temporal.api.common.v1.RetryPolicy + 210, // 86: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskCompletedRequest 12, // 87: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.started_response:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse - 209, // 88: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.activity_tasks:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueResponse - 186, // 89: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.new_workflow_task:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponse - 210, // 90: temporal.server.api.historyservice.v1.RespondWorkflowTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskFailedRequest - 189, // 91: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 185, // 92: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 211, // 93: temporal.server.api.historyservice.v1.RecordActivityTaskHeartbeatRequest.heartbeat_request:type_name -> temporal.api.workflowservice.v1.RecordActivityTaskHeartbeatRequest - 212, // 94: temporal.server.api.historyservice.v1.RespondActivityTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCompletedRequest - 213, // 95: temporal.server.api.historyservice.v1.RespondActivityTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskFailedRequest - 214, // 96: temporal.server.api.historyservice.v1.RespondActivityTaskCanceledRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCanceledRequest - 189, // 97: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 185, // 98: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 215, // 99: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.signal_request:type_name -> temporal.api.workflowservice.v1.SignalWorkflowExecutionRequest - 189, // 100: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 188, // 101: temporal.server.api.historyservice.v1.SignalWorkflowExecutionResponse.link:type_name -> temporal.api.common.v1.Link - 216, // 102: temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionRequest.signal_with_start_request:type_name -> temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest - 188, // 103: temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionResponse.signal_link:type_name -> temporal.api.common.v1.Link - 189, // 104: temporal.server.api.historyservice.v1.RemoveSignalMutableStateRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 217, // 105: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.terminate_request:type_name -> temporal.api.workflowservice.v1.TerminateWorkflowExecutionRequest - 189, // 106: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 189, // 107: temporal.server.api.historyservice.v1.DeleteWorkflowExecutionRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 218, // 108: temporal.server.api.historyservice.v1.ResetWorkflowExecutionRequest.reset_request:type_name -> temporal.api.workflowservice.v1.ResetWorkflowExecutionRequest - 219, // 109: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionRequest - 189, // 110: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 189, // 111: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 185, // 112: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.child_clock:type_name -> temporal.server.api.clock.v1.VectorClock - 185, // 113: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.parent_clock:type_name -> temporal.server.api.clock.v1.VectorClock - 189, // 114: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 185, // 115: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 189, // 116: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 189, // 117: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 205, // 118: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.completion_event:type_name -> temporal.api.history.v1.HistoryEvent - 185, // 119: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 189, // 120: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 189, // 121: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 185, // 122: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 220, // 123: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.DescribeWorkflowExecutionRequest - 221, // 124: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.execution_config:type_name -> temporal.api.workflow.v1.WorkflowExecutionConfig - 222, // 125: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_execution_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionInfo - 223, // 126: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_activities:type_name -> temporal.api.workflow.v1.PendingActivityInfo - 224, // 127: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_children:type_name -> temporal.api.workflow.v1.PendingChildExecutionInfo - 225, // 128: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_workflow_task:type_name -> temporal.api.workflow.v1.PendingWorkflowTaskInfo - 226, // 129: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.callbacks:type_name -> temporal.api.workflow.v1.CallbackInfo - 227, // 130: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_nexus_operations:type_name -> temporal.api.workflow.v1.PendingNexusOperationInfo - 228, // 131: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_extended_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionExtendedInfo - 189, // 132: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 190, // 133: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.version_history_items:type_name -> temporal.server.api.history.v1.VersionHistoryItem - 229, // 134: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.events:type_name -> temporal.api.common.v1.DataBlob - 229, // 135: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.new_run_events:type_name -> temporal.api.common.v1.DataBlob - 230, // 136: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo - 231, // 137: temporal.server.api.historyservice.v1.ReplicateWorkflowStateRequest.workflow_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState - 173, // 138: temporal.server.api.historyservice.v1.SyncShardStatusRequest.status_time:type_name -> google.protobuf.Timestamp - 173, // 139: temporal.server.api.historyservice.v1.SyncActivityRequest.scheduled_time:type_name -> google.protobuf.Timestamp - 173, // 140: temporal.server.api.historyservice.v1.SyncActivityRequest.started_time:type_name -> google.protobuf.Timestamp - 173, // 141: temporal.server.api.historyservice.v1.SyncActivityRequest.last_heartbeat_time:type_name -> google.protobuf.Timestamp - 176, // 142: temporal.server.api.historyservice.v1.SyncActivityRequest.details:type_name -> temporal.api.common.v1.Payloads - 175, // 143: temporal.server.api.historyservice.v1.SyncActivityRequest.last_failure:type_name -> temporal.api.failure.v1.Failure - 232, // 144: temporal.server.api.historyservice.v1.SyncActivityRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory - 230, // 145: temporal.server.api.historyservice.v1.SyncActivityRequest.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo - 173, // 146: temporal.server.api.historyservice.v1.SyncActivityRequest.first_scheduled_time:type_name -> google.protobuf.Timestamp - 173, // 147: temporal.server.api.historyservice.v1.SyncActivityRequest.last_attempt_complete_time:type_name -> google.protobuf.Timestamp - 177, // 148: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_initial_interval:type_name -> google.protobuf.Duration - 177, // 149: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_maximum_interval:type_name -> google.protobuf.Duration + 211, // 88: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.activity_tasks:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueResponse + 188, // 89: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.new_workflow_task:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponse + 212, // 90: temporal.server.api.historyservice.v1.RespondWorkflowTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskFailedRequest + 191, // 91: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 187, // 92: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 213, // 93: temporal.server.api.historyservice.v1.RecordActivityTaskHeartbeatRequest.heartbeat_request:type_name -> temporal.api.workflowservice.v1.RecordActivityTaskHeartbeatRequest + 214, // 94: temporal.server.api.historyservice.v1.RespondActivityTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCompletedRequest + 215, // 95: temporal.server.api.historyservice.v1.RespondActivityTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskFailedRequest + 216, // 96: temporal.server.api.historyservice.v1.RespondActivityTaskCanceledRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCanceledRequest + 191, // 97: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 187, // 98: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 217, // 99: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.signal_request:type_name -> temporal.api.workflowservice.v1.SignalWorkflowExecutionRequest + 191, // 100: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 190, // 101: temporal.server.api.historyservice.v1.SignalWorkflowExecutionResponse.link:type_name -> temporal.api.common.v1.Link + 218, // 102: temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionRequest.signal_with_start_request:type_name -> temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest + 190, // 103: temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionResponse.signal_link:type_name -> temporal.api.common.v1.Link + 191, // 104: temporal.server.api.historyservice.v1.RemoveSignalMutableStateRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 219, // 105: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.terminate_request:type_name -> temporal.api.workflowservice.v1.TerminateWorkflowExecutionRequest + 191, // 106: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 191, // 107: temporal.server.api.historyservice.v1.DeleteWorkflowExecutionRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 220, // 108: temporal.server.api.historyservice.v1.ResetWorkflowExecutionRequest.reset_request:type_name -> temporal.api.workflowservice.v1.ResetWorkflowExecutionRequest + 221, // 109: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionRequest + 191, // 110: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 191, // 111: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 187, // 112: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.child_clock:type_name -> temporal.server.api.clock.v1.VectorClock + 187, // 113: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.parent_clock:type_name -> temporal.server.api.clock.v1.VectorClock + 191, // 114: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 187, // 115: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 191, // 116: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 191, // 117: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 207, // 118: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.completion_event:type_name -> temporal.api.history.v1.HistoryEvent + 187, // 119: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 191, // 120: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 191, // 121: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 187, // 122: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 222, // 123: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.DescribeWorkflowExecutionRequest + 223, // 124: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.execution_config:type_name -> temporal.api.workflow.v1.WorkflowExecutionConfig + 224, // 125: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_execution_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionInfo + 225, // 126: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_activities:type_name -> temporal.api.workflow.v1.PendingActivityInfo + 226, // 127: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_children:type_name -> temporal.api.workflow.v1.PendingChildExecutionInfo + 227, // 128: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_workflow_task:type_name -> temporal.api.workflow.v1.PendingWorkflowTaskInfo + 228, // 129: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.callbacks:type_name -> temporal.api.workflow.v1.CallbackInfo + 229, // 130: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_nexus_operations:type_name -> temporal.api.workflow.v1.PendingNexusOperationInfo + 230, // 131: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_extended_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionExtendedInfo + 191, // 132: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 192, // 133: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.version_history_items:type_name -> temporal.server.api.history.v1.VersionHistoryItem + 231, // 134: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.events:type_name -> temporal.api.common.v1.DataBlob + 231, // 135: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.new_run_events:type_name -> temporal.api.common.v1.DataBlob + 232, // 136: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo + 233, // 137: temporal.server.api.historyservice.v1.ReplicateWorkflowStateRequest.workflow_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState + 175, // 138: temporal.server.api.historyservice.v1.SyncShardStatusRequest.status_time:type_name -> google.protobuf.Timestamp + 175, // 139: temporal.server.api.historyservice.v1.SyncActivityRequest.scheduled_time:type_name -> google.protobuf.Timestamp + 175, // 140: temporal.server.api.historyservice.v1.SyncActivityRequest.started_time:type_name -> google.protobuf.Timestamp + 175, // 141: temporal.server.api.historyservice.v1.SyncActivityRequest.last_heartbeat_time:type_name -> google.protobuf.Timestamp + 178, // 142: temporal.server.api.historyservice.v1.SyncActivityRequest.details:type_name -> temporal.api.common.v1.Payloads + 177, // 143: temporal.server.api.historyservice.v1.SyncActivityRequest.last_failure:type_name -> temporal.api.failure.v1.Failure + 234, // 144: temporal.server.api.historyservice.v1.SyncActivityRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory + 232, // 145: temporal.server.api.historyservice.v1.SyncActivityRequest.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo + 175, // 146: temporal.server.api.historyservice.v1.SyncActivityRequest.first_scheduled_time:type_name -> google.protobuf.Timestamp + 175, // 147: temporal.server.api.historyservice.v1.SyncActivityRequest.last_attempt_complete_time:type_name -> google.protobuf.Timestamp + 179, // 148: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_initial_interval:type_name -> google.protobuf.Duration + 179, // 149: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_maximum_interval:type_name -> google.protobuf.Duration 64, // 150: temporal.server.api.historyservice.v1.SyncActivitiesRequest.activities_info:type_name -> temporal.server.api.historyservice.v1.ActivitySyncInfo - 173, // 151: temporal.server.api.historyservice.v1.ActivitySyncInfo.scheduled_time:type_name -> google.protobuf.Timestamp - 173, // 152: temporal.server.api.historyservice.v1.ActivitySyncInfo.started_time:type_name -> google.protobuf.Timestamp - 173, // 153: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_heartbeat_time:type_name -> google.protobuf.Timestamp - 176, // 154: temporal.server.api.historyservice.v1.ActivitySyncInfo.details:type_name -> temporal.api.common.v1.Payloads - 175, // 155: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_failure:type_name -> temporal.api.failure.v1.Failure - 232, // 156: temporal.server.api.historyservice.v1.ActivitySyncInfo.version_history:type_name -> temporal.server.api.history.v1.VersionHistory - 173, // 157: temporal.server.api.historyservice.v1.ActivitySyncInfo.first_scheduled_time:type_name -> google.protobuf.Timestamp - 173, // 158: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp - 177, // 159: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_initial_interval:type_name -> google.protobuf.Duration - 177, // 160: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_maximum_interval:type_name -> google.protobuf.Duration - 189, // 161: temporal.server.api.historyservice.v1.DescribeMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 231, // 162: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.cache_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState - 231, // 163: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.database_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState - 189, // 164: temporal.server.api.historyservice.v1.DescribeHistoryHostRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 233, // 165: temporal.server.api.historyservice.v1.DescribeHistoryHostResponse.namespace_cache:type_name -> temporal.server.api.namespace.v1.NamespaceCacheInfo - 234, // 166: temporal.server.api.historyservice.v1.GetShardResponse.shard_info:type_name -> temporal.server.api.persistence.v1.ShardInfo - 173, // 167: temporal.server.api.historyservice.v1.RemoveTaskRequest.visibility_time:type_name -> google.protobuf.Timestamp - 235, // 168: temporal.server.api.historyservice.v1.GetReplicationMessagesRequest.tokens:type_name -> temporal.server.api.replication.v1.ReplicationToken - 166, // 169: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.shard_messages:type_name -> temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry - 236, // 170: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesRequest.task_infos:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo - 237, // 171: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask - 238, // 172: temporal.server.api.historyservice.v1.QueryWorkflowRequest.request:type_name -> temporal.api.workflowservice.v1.QueryWorkflowRequest - 239, // 173: temporal.server.api.historyservice.v1.QueryWorkflowResponse.response:type_name -> temporal.api.workflowservice.v1.QueryWorkflowResponse - 240, // 174: temporal.server.api.historyservice.v1.ReapplyEventsRequest.request:type_name -> temporal.server.api.adminservice.v1.ReapplyEventsRequest - 241, // 175: temporal.server.api.historyservice.v1.GetDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 241, // 176: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 237, // 177: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask - 236, // 178: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks_info:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo - 241, // 179: temporal.server.api.historyservice.v1.PurgeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 241, // 180: temporal.server.api.historyservice.v1.MergeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 242, // 181: temporal.server.api.historyservice.v1.RefreshWorkflowTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.RefreshWorkflowTasksRequest - 189, // 182: temporal.server.api.historyservice.v1.GenerateLastHistoryReplicationTasksRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 175, // 151: temporal.server.api.historyservice.v1.ActivitySyncInfo.scheduled_time:type_name -> google.protobuf.Timestamp + 175, // 152: temporal.server.api.historyservice.v1.ActivitySyncInfo.started_time:type_name -> google.protobuf.Timestamp + 175, // 153: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_heartbeat_time:type_name -> google.protobuf.Timestamp + 178, // 154: temporal.server.api.historyservice.v1.ActivitySyncInfo.details:type_name -> temporal.api.common.v1.Payloads + 177, // 155: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_failure:type_name -> temporal.api.failure.v1.Failure + 234, // 156: temporal.server.api.historyservice.v1.ActivitySyncInfo.version_history:type_name -> temporal.server.api.history.v1.VersionHistory + 175, // 157: temporal.server.api.historyservice.v1.ActivitySyncInfo.first_scheduled_time:type_name -> google.protobuf.Timestamp + 175, // 158: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp + 179, // 159: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_initial_interval:type_name -> google.protobuf.Duration + 179, // 160: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_maximum_interval:type_name -> google.protobuf.Duration + 191, // 161: temporal.server.api.historyservice.v1.DescribeMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 233, // 162: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.cache_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState + 233, // 163: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.database_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState + 191, // 164: temporal.server.api.historyservice.v1.DescribeHistoryHostRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 235, // 165: temporal.server.api.historyservice.v1.DescribeHistoryHostResponse.namespace_cache:type_name -> temporal.server.api.namespace.v1.NamespaceCacheInfo + 236, // 166: temporal.server.api.historyservice.v1.GetShardResponse.shard_info:type_name -> temporal.server.api.persistence.v1.ShardInfo + 175, // 167: temporal.server.api.historyservice.v1.RemoveTaskRequest.visibility_time:type_name -> google.protobuf.Timestamp + 237, // 168: temporal.server.api.historyservice.v1.GetReplicationMessagesRequest.tokens:type_name -> temporal.server.api.replication.v1.ReplicationToken + 168, // 169: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.shard_messages:type_name -> temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry + 238, // 170: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesRequest.task_infos:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo + 239, // 171: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask + 240, // 172: temporal.server.api.historyservice.v1.QueryWorkflowRequest.request:type_name -> temporal.api.workflowservice.v1.QueryWorkflowRequest + 241, // 173: temporal.server.api.historyservice.v1.QueryWorkflowResponse.response:type_name -> temporal.api.workflowservice.v1.QueryWorkflowResponse + 242, // 174: temporal.server.api.historyservice.v1.ReapplyEventsRequest.request:type_name -> temporal.server.api.adminservice.v1.ReapplyEventsRequest + 243, // 175: temporal.server.api.historyservice.v1.GetDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 243, // 176: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 239, // 177: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask + 238, // 178: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks_info:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo + 243, // 179: temporal.server.api.historyservice.v1.PurgeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 243, // 180: temporal.server.api.historyservice.v1.MergeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 244, // 181: temporal.server.api.historyservice.v1.RefreshWorkflowTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.RefreshWorkflowTasksRequest + 191, // 182: temporal.server.api.historyservice.v1.GenerateLastHistoryReplicationTasksRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution 96, // 183: temporal.server.api.historyservice.v1.GetReplicationStatusResponse.shards:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus - 173, // 184: temporal.server.api.historyservice.v1.ShardReplicationStatus.shard_local_time:type_name -> google.protobuf.Timestamp - 167, // 185: temporal.server.api.historyservice.v1.ShardReplicationStatus.remote_clusters:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry - 168, // 186: temporal.server.api.historyservice.v1.ShardReplicationStatus.handover_namespaces:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry - 173, // 187: temporal.server.api.historyservice.v1.ShardReplicationStatus.max_replication_task_visibility_time:type_name -> google.protobuf.Timestamp - 173, // 188: temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster.acked_task_visibility_time:type_name -> google.protobuf.Timestamp - 189, // 189: temporal.server.api.historyservice.v1.RebuildMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 189, // 190: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 229, // 191: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.history_batches:type_name -> temporal.api.common.v1.DataBlob - 232, // 192: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory - 189, // 193: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 173, // 194: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_start_time:type_name -> google.protobuf.Timestamp - 173, // 195: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_close_time:type_name -> google.protobuf.Timestamp - 243, // 196: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionRequest - 244, // 197: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse.response:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionResponse - 245, // 198: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesRequest.sync_replication_state:type_name -> temporal.server.api.replication.v1.SyncReplicationState - 246, // 199: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesResponse.messages:type_name -> temporal.server.api.replication.v1.WorkflowReplicationMessages - 247, // 200: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateRequest.request:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateRequest - 248, // 201: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateResponse.response:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateResponse - 249, // 202: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryRequest - 250, // 203: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse - 203, // 204: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.history:type_name -> temporal.api.history.v1.History - 250, // 205: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponseWithRaw.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse - 251, // 206: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseRequest - 252, // 207: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseResponse - 253, // 208: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Request.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Request - 254, // 209: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Response.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Response - 255, // 210: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryRequest.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryRequest - 256, // 211: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryResponse.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryResponse - 257, // 212: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionRequest.request:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionRequest - 258, // 213: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionResponse.response:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionResponse - 189, // 214: temporal.server.api.historyservice.v1.DeleteExecutionRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 259, // 215: temporal.server.api.historyservice.v1.GetDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey - 260, // 216: temporal.server.api.historyservice.v1.GetDLQTasksResponse.dlq_tasks:type_name -> temporal.server.api.common.v1.HistoryDLQTask - 259, // 217: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey - 261, // 218: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.inclusive_max_task_metadata:type_name -> temporal.server.api.common.v1.HistoryDLQTaskMetadata - 169, // 219: temporal.server.api.historyservice.v1.ListQueuesResponse.queues:type_name -> temporal.server.api.historyservice.v1.ListQueuesResponse.QueueInfo - 170, // 220: temporal.server.api.historyservice.v1.AddTasksRequest.tasks:type_name -> temporal.server.api.historyservice.v1.AddTasksRequest.Task - 262, // 221: temporal.server.api.historyservice.v1.ListTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksRequest - 263, // 222: temporal.server.api.historyservice.v1.ListTasksResponse.response:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksResponse - 264, // 223: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion - 265, // 224: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.success:type_name -> temporal.api.common.v1.Payload - 175, // 225: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.failure:type_name -> temporal.api.failure.v1.Failure - 173, // 226: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.close_time:type_name -> google.protobuf.Timestamp - 188, // 227: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.links:type_name -> temporal.api.common.v1.Link - 173, // 228: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.start_time:type_name -> google.protobuf.Timestamp - 264, // 229: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion - 265, // 230: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.success:type_name -> temporal.api.common.v1.Payload - 266, // 231: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.failure:type_name -> temporal.api.nexus.v1.Failure - 173, // 232: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.start_time:type_name -> google.protobuf.Timestamp - 188, // 233: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.links:type_name -> temporal.api.common.v1.Link - 267, // 234: temporal.server.api.historyservice.v1.InvokeStateMachineMethodRequest.ref:type_name -> temporal.server.api.persistence.v1.StateMachineRef - 268, // 235: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.state:type_name -> temporal.server.api.enums.v1.HealthState - 269, // 236: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.checks:type_name -> temporal.server.api.health.v1.HealthCheck - 189, // 237: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 191, // 238: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 195, // 239: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories - 270, // 240: temporal.server.api.historyservice.v1.SyncWorkflowStateResponse.versioned_transition_artifact:type_name -> temporal.server.api.replication.v1.VersionedTransitionArtifact - 271, // 241: temporal.server.api.historyservice.v1.UpdateActivityOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateActivityOptionsRequest - 272, // 242: temporal.server.api.historyservice.v1.UpdateActivityOptionsResponse.activity_options:type_name -> temporal.api.activity.v1.ActivityOptions - 273, // 243: temporal.server.api.historyservice.v1.PauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.PauseActivityRequest - 274, // 244: temporal.server.api.historyservice.v1.UnpauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.UnpauseActivityRequest - 275, // 245: temporal.server.api.historyservice.v1.ResetActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.ResetActivityRequest - 276, // 246: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionOptionsRequest - 277, // 247: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse.workflow_execution_options:type_name -> temporal.api.workflow.v1.WorkflowExecutionOptions - 173, // 248: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse.update_time:type_name -> google.protobuf.Timestamp - 278, // 249: temporal.server.api.historyservice.v1.PauseWorkflowExecutionRequest.pause_request:type_name -> temporal.api.workflowservice.v1.PauseWorkflowExecutionRequest - 279, // 250: temporal.server.api.historyservice.v1.UnpauseWorkflowExecutionRequest.unpause_request:type_name -> temporal.api.workflowservice.v1.UnpauseWorkflowExecutionRequest - 280, // 251: temporal.server.api.historyservice.v1.StartNexusOperationRequest.request:type_name -> temporal.api.nexus.v1.StartOperationRequest - 281, // 252: temporal.server.api.historyservice.v1.StartNexusOperationResponse.response:type_name -> temporal.api.nexus.v1.StartOperationResponse - 282, // 253: temporal.server.api.historyservice.v1.CancelNexusOperationRequest.request:type_name -> temporal.api.nexus.v1.CancelOperationRequest - 283, // 254: temporal.server.api.historyservice.v1.CancelNexusOperationResponse.response:type_name -> temporal.api.nexus.v1.CancelOperationResponse - 1, // 255: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest - 105, // 256: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest - 2, // 257: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse - 106, // 258: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse - 284, // 259: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery - 284, // 260: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery - 285, // 261: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry.value:type_name -> temporal.server.api.replication.v1.ReplicationMessages - 98, // 262: temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry.value:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster - 97, // 263: temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry.value:type_name -> temporal.server.api.historyservice.v1.HandoverNamespaceInfo - 229, // 264: temporal.server.api.historyservice.v1.AddTasksRequest.Task.blob:type_name -> temporal.api.common.v1.DataBlob - 286, // 265: temporal.server.api.historyservice.v1.routing:extendee -> google.protobuf.MessageOptions - 0, // 266: temporal.server.api.historyservice.v1.routing:type_name -> temporal.server.api.historyservice.v1.RoutingOptions - 267, // [267:267] is the sub-list for method output_type - 267, // [267:267] is the sub-list for method input_type - 266, // [266:267] is the sub-list for extension type_name - 265, // [265:266] is the sub-list for extension extendee - 0, // [0:265] is the sub-list for field type_name + 175, // 184: temporal.server.api.historyservice.v1.ShardReplicationStatus.shard_local_time:type_name -> google.protobuf.Timestamp + 169, // 185: temporal.server.api.historyservice.v1.ShardReplicationStatus.remote_clusters:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry + 170, // 186: temporal.server.api.historyservice.v1.ShardReplicationStatus.handover_namespaces:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry + 175, // 187: temporal.server.api.historyservice.v1.ShardReplicationStatus.max_replication_task_visibility_time:type_name -> google.protobuf.Timestamp + 175, // 188: temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster.acked_task_visibility_time:type_name -> google.protobuf.Timestamp + 191, // 189: temporal.server.api.historyservice.v1.RebuildMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 191, // 190: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 231, // 191: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.history_batches:type_name -> temporal.api.common.v1.DataBlob + 234, // 192: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory + 191, // 193: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 175, // 194: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_start_time:type_name -> google.protobuf.Timestamp + 175, // 195: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_close_time:type_name -> google.protobuf.Timestamp + 245, // 196: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionRequest + 246, // 197: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse.response:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionResponse + 247, // 198: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesRequest.sync_replication_state:type_name -> temporal.server.api.replication.v1.SyncReplicationState + 248, // 199: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesResponse.messages:type_name -> temporal.server.api.replication.v1.WorkflowReplicationMessages + 249, // 200: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateRequest.request:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateRequest + 250, // 201: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateResponse.response:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateResponse + 251, // 202: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryRequest + 252, // 203: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse + 205, // 204: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.history:type_name -> temporal.api.history.v1.History + 252, // 205: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponseWithRaw.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse + 253, // 206: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseRequest + 254, // 207: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseResponse + 255, // 208: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Request.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Request + 256, // 209: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Response.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Response + 257, // 210: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryRequest.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryRequest + 258, // 211: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryResponse.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryResponse + 259, // 212: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionRequest.request:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionRequest + 260, // 213: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionResponse.response:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionResponse + 191, // 214: temporal.server.api.historyservice.v1.DeleteExecutionRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 261, // 215: temporal.server.api.historyservice.v1.GetDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey + 262, // 216: temporal.server.api.historyservice.v1.GetDLQTasksResponse.dlq_tasks:type_name -> temporal.server.api.common.v1.HistoryDLQTask + 261, // 217: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey + 263, // 218: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.inclusive_max_task_metadata:type_name -> temporal.server.api.common.v1.HistoryDLQTaskMetadata + 171, // 219: temporal.server.api.historyservice.v1.ListQueuesResponse.queues:type_name -> temporal.server.api.historyservice.v1.ListQueuesResponse.QueueInfo + 172, // 220: temporal.server.api.historyservice.v1.AddTasksRequest.tasks:type_name -> temporal.server.api.historyservice.v1.AddTasksRequest.Task + 264, // 221: temporal.server.api.historyservice.v1.ListTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksRequest + 265, // 222: temporal.server.api.historyservice.v1.ListTasksResponse.response:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksResponse + 266, // 223: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion + 267, // 224: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.success:type_name -> temporal.api.common.v1.Payload + 177, // 225: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.failure:type_name -> temporal.api.failure.v1.Failure + 175, // 226: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.close_time:type_name -> google.protobuf.Timestamp + 190, // 227: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.links:type_name -> temporal.api.common.v1.Link + 175, // 228: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.start_time:type_name -> google.protobuf.Timestamp + 266, // 229: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion + 267, // 230: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.success:type_name -> temporal.api.common.v1.Payload + 268, // 231: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.failure:type_name -> temporal.api.nexus.v1.Failure + 175, // 232: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.start_time:type_name -> google.protobuf.Timestamp + 190, // 233: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.links:type_name -> temporal.api.common.v1.Link + 269, // 234: temporal.server.api.historyservice.v1.InvokeStateMachineMethodRequest.ref:type_name -> temporal.server.api.persistence.v1.StateMachineRef + 270, // 235: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.state:type_name -> temporal.server.api.enums.v1.HealthState + 271, // 236: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.checks:type_name -> temporal.server.api.health.v1.HealthCheck + 191, // 237: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 193, // 238: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 197, // 239: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories + 272, // 240: temporal.server.api.historyservice.v1.SyncWorkflowStateResponse.versioned_transition_artifact:type_name -> temporal.server.api.replication.v1.VersionedTransitionArtifact + 273, // 241: temporal.server.api.historyservice.v1.UpdateActivityOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateActivityOptionsRequest + 274, // 242: temporal.server.api.historyservice.v1.UpdateActivityOptionsResponse.activity_options:type_name -> temporal.api.activity.v1.ActivityOptions + 275, // 243: temporal.server.api.historyservice.v1.PauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.PauseActivityRequest + 276, // 244: temporal.server.api.historyservice.v1.UnpauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.UnpauseActivityRequest + 277, // 245: temporal.server.api.historyservice.v1.ResetActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.ResetActivityRequest + 278, // 246: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionOptionsRequest + 279, // 247: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse.workflow_execution_options:type_name -> temporal.api.workflow.v1.WorkflowExecutionOptions + 175, // 248: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse.update_time:type_name -> google.protobuf.Timestamp + 280, // 249: temporal.server.api.historyservice.v1.PauseWorkflowExecutionRequest.pause_request:type_name -> temporal.api.workflowservice.v1.PauseWorkflowExecutionRequest + 281, // 250: temporal.server.api.historyservice.v1.UnpauseWorkflowExecutionRequest.unpause_request:type_name -> temporal.api.workflowservice.v1.UnpauseWorkflowExecutionRequest + 282, // 251: temporal.server.api.historyservice.v1.StartNexusOperationRequest.request:type_name -> temporal.api.nexus.v1.StartOperationRequest + 283, // 252: temporal.server.api.historyservice.v1.StartNexusOperationResponse.response:type_name -> temporal.api.nexus.v1.StartOperationResponse + 284, // 253: temporal.server.api.historyservice.v1.CancelNexusOperationRequest.request:type_name -> temporal.api.nexus.v1.CancelOperationRequest + 285, // 254: temporal.server.api.historyservice.v1.CancelNexusOperationResponse.response:type_name -> temporal.api.nexus.v1.CancelOperationResponse + 286, // 255: temporal.server.api.historyservice.v1.PollWorkflowExecutionTimeSkippingRequest.request:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingRequest + 287, // 256: temporal.server.api.historyservice.v1.PollWorkflowExecutionTimeSkippingResponse.response:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingResponse + 1, // 257: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest + 105, // 258: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest + 2, // 259: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse + 106, // 260: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse + 288, // 261: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery + 288, // 262: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery + 289, // 263: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry.value:type_name -> temporal.server.api.replication.v1.ReplicationMessages + 98, // 264: temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry.value:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster + 97, // 265: temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry.value:type_name -> temporal.server.api.historyservice.v1.HandoverNamespaceInfo + 231, // 266: temporal.server.api.historyservice.v1.AddTasksRequest.Task.blob:type_name -> temporal.api.common.v1.DataBlob + 290, // 267: temporal.server.api.historyservice.v1.routing:extendee -> google.protobuf.MessageOptions + 0, // 268: temporal.server.api.historyservice.v1.routing:type_name -> temporal.server.api.historyservice.v1.RoutingOptions + 269, // [269:269] is the sub-list for method output_type + 269, // [269:269] is the sub-list for method input_type + 268, // [268:269] is the sub-list for extension type_name + 267, // [267:268] is the sub-list for extension extendee + 0, // [0:267] is the sub-list for field type_name } func init() { file_temporal_server_api_historyservice_v1_request_response_proto_init() } @@ -11974,11 +12081,11 @@ func file_temporal_server_api_historyservice_v1_request_response_proto_init() { (*CompleteNexusOperationRequest_Success)(nil), (*CompleteNexusOperationRequest_Failure)(nil), } - file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[162].OneofWrappers = []any{ + file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[164].OneofWrappers = []any{ (*ExecuteMultiOperationRequest_Operation_StartWorkflow)(nil), (*ExecuteMultiOperationRequest_Operation_UpdateWorkflow)(nil), } - file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[163].OneofWrappers = []any{ + file_temporal_server_api_historyservice_v1_request_response_proto_msgTypes[165].OneofWrappers = []any{ (*ExecuteMultiOperationResponse_Response_StartWorkflow)(nil), (*ExecuteMultiOperationResponse_Response_UpdateWorkflow)(nil), } @@ -11988,7 +12095,7 @@ func file_temporal_server_api_historyservice_v1_request_response_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_temporal_server_api_historyservice_v1_request_response_proto_rawDesc), len(file_temporal_server_api_historyservice_v1_request_response_proto_rawDesc)), NumEnums: 0, - NumMessages: 171, + NumMessages: 173, NumExtensions: 1, NumServices: 0, }, diff --git a/api/historyservice/v1/service.pb.go b/api/historyservice/v1/service.pb.go index ece13b2ef31..640fef3b4d4 100644 --- a/api/historyservice/v1/service.pb.go +++ b/api/historyservice/v1/service.pb.go @@ -26,7 +26,7 @@ var File_temporal_server_api_historyservice_v1_service_proto protoreflect.FileDe const file_temporal_server_api_historyservice_v1_service_proto_rawDesc = "" + "\n" + - "3temporal/server/api/historyservice/v1/service.proto\x12%temporal.server.api.historyservice.v1\x1a0temporal/server/api/common/v1/api_category.proto\x1a.temporal.server.api.historyservice.v1.GetMutableStateResponse\"\x06\x8a\xb5\x18\x02\b\x01\x12\x9b\x01\n" + @@ -107,7 +107,8 @@ const file_temporal_server_api_historyservice_v1_service_proto_rawDesc = "" + "\x16PauseWorkflowExecution\x12D.temporal.server.api.historyservice.v1.PauseWorkflowExecutionRequest\x1aE.temporal.server.api.historyservice.v1.PauseWorkflowExecutionResponse\"\x06\x8a\xb5\x18\x02\b\x01\x12\xb3\x01\n" + "\x18UnpauseWorkflowExecution\x12F.temporal.server.api.historyservice.v1.UnpauseWorkflowExecutionRequest\x1aG.temporal.server.api.historyservice.v1.UnpauseWorkflowExecutionResponse\"\x06\x8a\xb5\x18\x02\b\x01\x12\xa4\x01\n" + "\x13StartNexusOperation\x12A.temporal.server.api.historyservice.v1.StartNexusOperationRequest\x1aB.temporal.server.api.historyservice.v1.StartNexusOperationResponse\"\x06\x8a\xb5\x18\x02\b\x01\x12\xa7\x01\n" + - "\x14CancelNexusOperation\x12B.temporal.server.api.historyservice.v1.CancelNexusOperationRequest\x1aC.temporal.server.api.historyservice.v1.CancelNexusOperationResponse\"\x06\x8a\xb5\x18\x02\b\x01B temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest @@ -343,85 +346,87 @@ var file_temporal_server_api_historyservice_v1_service_proto_depIdxs = []int32{ 74, // 74: temporal.server.api.historyservice.v1.HistoryService.UnpauseWorkflowExecution:input_type -> temporal.server.api.historyservice.v1.UnpauseWorkflowExecutionRequest 75, // 75: temporal.server.api.historyservice.v1.HistoryService.StartNexusOperation:input_type -> temporal.server.api.historyservice.v1.StartNexusOperationRequest 76, // 76: temporal.server.api.historyservice.v1.HistoryService.CancelNexusOperation:input_type -> temporal.server.api.historyservice.v1.CancelNexusOperationRequest - 77, // 77: temporal.server.api.historyservice.v1.HistoryService.StartWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse - 78, // 78: temporal.server.api.historyservice.v1.HistoryService.GetMutableState:output_type -> temporal.server.api.historyservice.v1.GetMutableStateResponse - 79, // 79: temporal.server.api.historyservice.v1.HistoryService.PollMutableState:output_type -> temporal.server.api.historyservice.v1.PollMutableStateResponse - 80, // 80: temporal.server.api.historyservice.v1.HistoryService.ResetStickyTaskQueue:output_type -> temporal.server.api.historyservice.v1.ResetStickyTaskQueueResponse - 81, // 81: temporal.server.api.historyservice.v1.HistoryService.RecordWorkflowTaskStarted:output_type -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse - 82, // 82: temporal.server.api.historyservice.v1.HistoryService.RecordActivityTaskStarted:output_type -> temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse - 83, // 83: temporal.server.api.historyservice.v1.HistoryService.RespondWorkflowTaskCompleted:output_type -> temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse - 84, // 84: temporal.server.api.historyservice.v1.HistoryService.RespondWorkflowTaskFailed:output_type -> temporal.server.api.historyservice.v1.RespondWorkflowTaskFailedResponse - 85, // 85: temporal.server.api.historyservice.v1.HistoryService.IsWorkflowTaskValid:output_type -> temporal.server.api.historyservice.v1.IsWorkflowTaskValidResponse - 86, // 86: temporal.server.api.historyservice.v1.HistoryService.RecordActivityTaskHeartbeat:output_type -> temporal.server.api.historyservice.v1.RecordActivityTaskHeartbeatResponse - 87, // 87: temporal.server.api.historyservice.v1.HistoryService.RespondActivityTaskCompleted:output_type -> temporal.server.api.historyservice.v1.RespondActivityTaskCompletedResponse - 88, // 88: temporal.server.api.historyservice.v1.HistoryService.RespondActivityTaskFailed:output_type -> temporal.server.api.historyservice.v1.RespondActivityTaskFailedResponse - 89, // 89: temporal.server.api.historyservice.v1.HistoryService.RespondActivityTaskCanceled:output_type -> temporal.server.api.historyservice.v1.RespondActivityTaskCanceledResponse - 90, // 90: temporal.server.api.historyservice.v1.HistoryService.IsActivityTaskValid:output_type -> temporal.server.api.historyservice.v1.IsActivityTaskValidResponse - 91, // 91: temporal.server.api.historyservice.v1.HistoryService.SignalWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.SignalWorkflowExecutionResponse - 92, // 92: temporal.server.api.historyservice.v1.HistoryService.SignalWithStartWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionResponse - 93, // 93: temporal.server.api.historyservice.v1.HistoryService.ExecuteMultiOperation:output_type -> temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse - 94, // 94: temporal.server.api.historyservice.v1.HistoryService.RemoveSignalMutableState:output_type -> temporal.server.api.historyservice.v1.RemoveSignalMutableStateResponse - 95, // 95: temporal.server.api.historyservice.v1.HistoryService.TerminateWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.TerminateWorkflowExecutionResponse - 96, // 96: temporal.server.api.historyservice.v1.HistoryService.DeleteWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.DeleteWorkflowExecutionResponse - 97, // 97: temporal.server.api.historyservice.v1.HistoryService.ResetWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.ResetWorkflowExecutionResponse - 98, // 98: temporal.server.api.historyservice.v1.HistoryService.UpdateWorkflowExecutionOptions:output_type -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse - 99, // 99: temporal.server.api.historyservice.v1.HistoryService.RequestCancelWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionResponse - 100, // 100: temporal.server.api.historyservice.v1.HistoryService.ScheduleWorkflowTask:output_type -> temporal.server.api.historyservice.v1.ScheduleWorkflowTaskResponse - 101, // 101: temporal.server.api.historyservice.v1.HistoryService.VerifyFirstWorkflowTaskScheduled:output_type -> temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledResponse - 102, // 102: temporal.server.api.historyservice.v1.HistoryService.RecordChildExecutionCompleted:output_type -> temporal.server.api.historyservice.v1.RecordChildExecutionCompletedResponse - 103, // 103: temporal.server.api.historyservice.v1.HistoryService.VerifyChildExecutionCompletionRecorded:output_type -> temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedResponse - 104, // 104: temporal.server.api.historyservice.v1.HistoryService.DescribeWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse - 105, // 105: temporal.server.api.historyservice.v1.HistoryService.ReplicateEventsV2:output_type -> temporal.server.api.historyservice.v1.ReplicateEventsV2Response - 106, // 106: temporal.server.api.historyservice.v1.HistoryService.ReplicateWorkflowState:output_type -> temporal.server.api.historyservice.v1.ReplicateWorkflowStateResponse - 107, // 107: temporal.server.api.historyservice.v1.HistoryService.SyncShardStatus:output_type -> temporal.server.api.historyservice.v1.SyncShardStatusResponse - 108, // 108: temporal.server.api.historyservice.v1.HistoryService.SyncActivity:output_type -> temporal.server.api.historyservice.v1.SyncActivityResponse - 109, // 109: temporal.server.api.historyservice.v1.HistoryService.DescribeMutableState:output_type -> temporal.server.api.historyservice.v1.DescribeMutableStateResponse - 110, // 110: temporal.server.api.historyservice.v1.HistoryService.DescribeHistoryHost:output_type -> temporal.server.api.historyservice.v1.DescribeHistoryHostResponse - 111, // 111: temporal.server.api.historyservice.v1.HistoryService.CloseShard:output_type -> temporal.server.api.historyservice.v1.CloseShardResponse - 112, // 112: temporal.server.api.historyservice.v1.HistoryService.GetShard:output_type -> temporal.server.api.historyservice.v1.GetShardResponse - 113, // 113: temporal.server.api.historyservice.v1.HistoryService.RemoveTask:output_type -> temporal.server.api.historyservice.v1.RemoveTaskResponse - 114, // 114: temporal.server.api.historyservice.v1.HistoryService.GetReplicationMessages:output_type -> temporal.server.api.historyservice.v1.GetReplicationMessagesResponse - 115, // 115: temporal.server.api.historyservice.v1.HistoryService.GetDLQReplicationMessages:output_type -> temporal.server.api.historyservice.v1.GetDLQReplicationMessagesResponse - 116, // 116: temporal.server.api.historyservice.v1.HistoryService.QueryWorkflow:output_type -> temporal.server.api.historyservice.v1.QueryWorkflowResponse - 117, // 117: temporal.server.api.historyservice.v1.HistoryService.ReapplyEvents:output_type -> temporal.server.api.historyservice.v1.ReapplyEventsResponse - 118, // 118: temporal.server.api.historyservice.v1.HistoryService.GetDLQMessages:output_type -> temporal.server.api.historyservice.v1.GetDLQMessagesResponse - 119, // 119: temporal.server.api.historyservice.v1.HistoryService.PurgeDLQMessages:output_type -> temporal.server.api.historyservice.v1.PurgeDLQMessagesResponse - 120, // 120: temporal.server.api.historyservice.v1.HistoryService.MergeDLQMessages:output_type -> temporal.server.api.historyservice.v1.MergeDLQMessagesResponse - 121, // 121: temporal.server.api.historyservice.v1.HistoryService.RefreshWorkflowTasks:output_type -> temporal.server.api.historyservice.v1.RefreshWorkflowTasksResponse - 122, // 122: temporal.server.api.historyservice.v1.HistoryService.GenerateLastHistoryReplicationTasks:output_type -> temporal.server.api.historyservice.v1.GenerateLastHistoryReplicationTasksResponse - 123, // 123: temporal.server.api.historyservice.v1.HistoryService.GetReplicationStatus:output_type -> temporal.server.api.historyservice.v1.GetReplicationStatusResponse - 124, // 124: temporal.server.api.historyservice.v1.HistoryService.RebuildMutableState:output_type -> temporal.server.api.historyservice.v1.RebuildMutableStateResponse - 125, // 125: temporal.server.api.historyservice.v1.HistoryService.ImportWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.ImportWorkflowExecutionResponse - 126, // 126: temporal.server.api.historyservice.v1.HistoryService.DeleteWorkflowVisibilityRecord:output_type -> temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordResponse - 127, // 127: temporal.server.api.historyservice.v1.HistoryService.UpdateWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse - 128, // 128: temporal.server.api.historyservice.v1.HistoryService.PollWorkflowExecutionUpdate:output_type -> temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateResponse - 129, // 129: temporal.server.api.historyservice.v1.HistoryService.StreamWorkflowReplicationMessages:output_type -> temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesResponse - 130, // 130: temporal.server.api.historyservice.v1.HistoryService.GetWorkflowExecutionHistory:output_type -> temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse - 131, // 131: temporal.server.api.historyservice.v1.HistoryService.GetWorkflowExecutionHistoryReverse:output_type -> temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseResponse - 132, // 132: temporal.server.api.historyservice.v1.HistoryService.GetWorkflowExecutionRawHistoryV2:output_type -> temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Response - 133, // 133: temporal.server.api.historyservice.v1.HistoryService.GetWorkflowExecutionRawHistory:output_type -> temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryResponse - 134, // 134: temporal.server.api.historyservice.v1.HistoryService.ForceDeleteWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionResponse - 135, // 135: temporal.server.api.historyservice.v1.HistoryService.DeleteExecution:output_type -> temporal.server.api.historyservice.v1.DeleteExecutionResponse - 136, // 136: temporal.server.api.historyservice.v1.HistoryService.GetDLQTasks:output_type -> temporal.server.api.historyservice.v1.GetDLQTasksResponse - 137, // 137: temporal.server.api.historyservice.v1.HistoryService.DeleteDLQTasks:output_type -> temporal.server.api.historyservice.v1.DeleteDLQTasksResponse - 138, // 138: temporal.server.api.historyservice.v1.HistoryService.ListQueues:output_type -> temporal.server.api.historyservice.v1.ListQueuesResponse - 139, // 139: temporal.server.api.historyservice.v1.HistoryService.AddTasks:output_type -> temporal.server.api.historyservice.v1.AddTasksResponse - 140, // 140: temporal.server.api.historyservice.v1.HistoryService.ListTasks:output_type -> temporal.server.api.historyservice.v1.ListTasksResponse - 141, // 141: temporal.server.api.historyservice.v1.HistoryService.CompleteNexusOperation:output_type -> temporal.server.api.historyservice.v1.CompleteNexusOperationResponse - 142, // 142: temporal.server.api.historyservice.v1.HistoryService.CompleteNexusOperationChasm:output_type -> temporal.server.api.historyservice.v1.CompleteNexusOperationChasmResponse - 143, // 143: temporal.server.api.historyservice.v1.HistoryService.InvokeStateMachineMethod:output_type -> temporal.server.api.historyservice.v1.InvokeStateMachineMethodResponse - 144, // 144: temporal.server.api.historyservice.v1.HistoryService.DeepHealthCheck:output_type -> temporal.server.api.historyservice.v1.DeepHealthCheckResponse - 145, // 145: temporal.server.api.historyservice.v1.HistoryService.SyncWorkflowState:output_type -> temporal.server.api.historyservice.v1.SyncWorkflowStateResponse - 146, // 146: temporal.server.api.historyservice.v1.HistoryService.UpdateActivityOptions:output_type -> temporal.server.api.historyservice.v1.UpdateActivityOptionsResponse - 147, // 147: temporal.server.api.historyservice.v1.HistoryService.PauseActivity:output_type -> temporal.server.api.historyservice.v1.PauseActivityResponse - 148, // 148: temporal.server.api.historyservice.v1.HistoryService.UnpauseActivity:output_type -> temporal.server.api.historyservice.v1.UnpauseActivityResponse - 149, // 149: temporal.server.api.historyservice.v1.HistoryService.ResetActivity:output_type -> temporal.server.api.historyservice.v1.ResetActivityResponse - 150, // 150: temporal.server.api.historyservice.v1.HistoryService.PauseWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.PauseWorkflowExecutionResponse - 151, // 151: temporal.server.api.historyservice.v1.HistoryService.UnpauseWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.UnpauseWorkflowExecutionResponse - 152, // 152: temporal.server.api.historyservice.v1.HistoryService.StartNexusOperation:output_type -> temporal.server.api.historyservice.v1.StartNexusOperationResponse - 153, // 153: temporal.server.api.historyservice.v1.HistoryService.CancelNexusOperation:output_type -> temporal.server.api.historyservice.v1.CancelNexusOperationResponse - 77, // [77:154] is the sub-list for method output_type - 0, // [0:77] is the sub-list for method input_type + 77, // 77: temporal.server.api.historyservice.v1.HistoryService.PollWorkflowExecutionTimeSkipping:input_type -> temporal.server.api.historyservice.v1.PollWorkflowExecutionTimeSkippingRequest + 78, // 78: temporal.server.api.historyservice.v1.HistoryService.StartWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse + 79, // 79: temporal.server.api.historyservice.v1.HistoryService.GetMutableState:output_type -> temporal.server.api.historyservice.v1.GetMutableStateResponse + 80, // 80: temporal.server.api.historyservice.v1.HistoryService.PollMutableState:output_type -> temporal.server.api.historyservice.v1.PollMutableStateResponse + 81, // 81: temporal.server.api.historyservice.v1.HistoryService.ResetStickyTaskQueue:output_type -> temporal.server.api.historyservice.v1.ResetStickyTaskQueueResponse + 82, // 82: temporal.server.api.historyservice.v1.HistoryService.RecordWorkflowTaskStarted:output_type -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse + 83, // 83: temporal.server.api.historyservice.v1.HistoryService.RecordActivityTaskStarted:output_type -> temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse + 84, // 84: temporal.server.api.historyservice.v1.HistoryService.RespondWorkflowTaskCompleted:output_type -> temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse + 85, // 85: temporal.server.api.historyservice.v1.HistoryService.RespondWorkflowTaskFailed:output_type -> temporal.server.api.historyservice.v1.RespondWorkflowTaskFailedResponse + 86, // 86: temporal.server.api.historyservice.v1.HistoryService.IsWorkflowTaskValid:output_type -> temporal.server.api.historyservice.v1.IsWorkflowTaskValidResponse + 87, // 87: temporal.server.api.historyservice.v1.HistoryService.RecordActivityTaskHeartbeat:output_type -> temporal.server.api.historyservice.v1.RecordActivityTaskHeartbeatResponse + 88, // 88: temporal.server.api.historyservice.v1.HistoryService.RespondActivityTaskCompleted:output_type -> temporal.server.api.historyservice.v1.RespondActivityTaskCompletedResponse + 89, // 89: temporal.server.api.historyservice.v1.HistoryService.RespondActivityTaskFailed:output_type -> temporal.server.api.historyservice.v1.RespondActivityTaskFailedResponse + 90, // 90: temporal.server.api.historyservice.v1.HistoryService.RespondActivityTaskCanceled:output_type -> temporal.server.api.historyservice.v1.RespondActivityTaskCanceledResponse + 91, // 91: temporal.server.api.historyservice.v1.HistoryService.IsActivityTaskValid:output_type -> temporal.server.api.historyservice.v1.IsActivityTaskValidResponse + 92, // 92: temporal.server.api.historyservice.v1.HistoryService.SignalWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.SignalWorkflowExecutionResponse + 93, // 93: temporal.server.api.historyservice.v1.HistoryService.SignalWithStartWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionResponse + 94, // 94: temporal.server.api.historyservice.v1.HistoryService.ExecuteMultiOperation:output_type -> temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse + 95, // 95: temporal.server.api.historyservice.v1.HistoryService.RemoveSignalMutableState:output_type -> temporal.server.api.historyservice.v1.RemoveSignalMutableStateResponse + 96, // 96: temporal.server.api.historyservice.v1.HistoryService.TerminateWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.TerminateWorkflowExecutionResponse + 97, // 97: temporal.server.api.historyservice.v1.HistoryService.DeleteWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.DeleteWorkflowExecutionResponse + 98, // 98: temporal.server.api.historyservice.v1.HistoryService.ResetWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.ResetWorkflowExecutionResponse + 99, // 99: temporal.server.api.historyservice.v1.HistoryService.UpdateWorkflowExecutionOptions:output_type -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse + 100, // 100: temporal.server.api.historyservice.v1.HistoryService.RequestCancelWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionResponse + 101, // 101: temporal.server.api.historyservice.v1.HistoryService.ScheduleWorkflowTask:output_type -> temporal.server.api.historyservice.v1.ScheduleWorkflowTaskResponse + 102, // 102: temporal.server.api.historyservice.v1.HistoryService.VerifyFirstWorkflowTaskScheduled:output_type -> temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledResponse + 103, // 103: temporal.server.api.historyservice.v1.HistoryService.RecordChildExecutionCompleted:output_type -> temporal.server.api.historyservice.v1.RecordChildExecutionCompletedResponse + 104, // 104: temporal.server.api.historyservice.v1.HistoryService.VerifyChildExecutionCompletionRecorded:output_type -> temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedResponse + 105, // 105: temporal.server.api.historyservice.v1.HistoryService.DescribeWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse + 106, // 106: temporal.server.api.historyservice.v1.HistoryService.ReplicateEventsV2:output_type -> temporal.server.api.historyservice.v1.ReplicateEventsV2Response + 107, // 107: temporal.server.api.historyservice.v1.HistoryService.ReplicateWorkflowState:output_type -> temporal.server.api.historyservice.v1.ReplicateWorkflowStateResponse + 108, // 108: temporal.server.api.historyservice.v1.HistoryService.SyncShardStatus:output_type -> temporal.server.api.historyservice.v1.SyncShardStatusResponse + 109, // 109: temporal.server.api.historyservice.v1.HistoryService.SyncActivity:output_type -> temporal.server.api.historyservice.v1.SyncActivityResponse + 110, // 110: temporal.server.api.historyservice.v1.HistoryService.DescribeMutableState:output_type -> temporal.server.api.historyservice.v1.DescribeMutableStateResponse + 111, // 111: temporal.server.api.historyservice.v1.HistoryService.DescribeHistoryHost:output_type -> temporal.server.api.historyservice.v1.DescribeHistoryHostResponse + 112, // 112: temporal.server.api.historyservice.v1.HistoryService.CloseShard:output_type -> temporal.server.api.historyservice.v1.CloseShardResponse + 113, // 113: temporal.server.api.historyservice.v1.HistoryService.GetShard:output_type -> temporal.server.api.historyservice.v1.GetShardResponse + 114, // 114: temporal.server.api.historyservice.v1.HistoryService.RemoveTask:output_type -> temporal.server.api.historyservice.v1.RemoveTaskResponse + 115, // 115: temporal.server.api.historyservice.v1.HistoryService.GetReplicationMessages:output_type -> temporal.server.api.historyservice.v1.GetReplicationMessagesResponse + 116, // 116: temporal.server.api.historyservice.v1.HistoryService.GetDLQReplicationMessages:output_type -> temporal.server.api.historyservice.v1.GetDLQReplicationMessagesResponse + 117, // 117: temporal.server.api.historyservice.v1.HistoryService.QueryWorkflow:output_type -> temporal.server.api.historyservice.v1.QueryWorkflowResponse + 118, // 118: temporal.server.api.historyservice.v1.HistoryService.ReapplyEvents:output_type -> temporal.server.api.historyservice.v1.ReapplyEventsResponse + 119, // 119: temporal.server.api.historyservice.v1.HistoryService.GetDLQMessages:output_type -> temporal.server.api.historyservice.v1.GetDLQMessagesResponse + 120, // 120: temporal.server.api.historyservice.v1.HistoryService.PurgeDLQMessages:output_type -> temporal.server.api.historyservice.v1.PurgeDLQMessagesResponse + 121, // 121: temporal.server.api.historyservice.v1.HistoryService.MergeDLQMessages:output_type -> temporal.server.api.historyservice.v1.MergeDLQMessagesResponse + 122, // 122: temporal.server.api.historyservice.v1.HistoryService.RefreshWorkflowTasks:output_type -> temporal.server.api.historyservice.v1.RefreshWorkflowTasksResponse + 123, // 123: temporal.server.api.historyservice.v1.HistoryService.GenerateLastHistoryReplicationTasks:output_type -> temporal.server.api.historyservice.v1.GenerateLastHistoryReplicationTasksResponse + 124, // 124: temporal.server.api.historyservice.v1.HistoryService.GetReplicationStatus:output_type -> temporal.server.api.historyservice.v1.GetReplicationStatusResponse + 125, // 125: temporal.server.api.historyservice.v1.HistoryService.RebuildMutableState:output_type -> temporal.server.api.historyservice.v1.RebuildMutableStateResponse + 126, // 126: temporal.server.api.historyservice.v1.HistoryService.ImportWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.ImportWorkflowExecutionResponse + 127, // 127: temporal.server.api.historyservice.v1.HistoryService.DeleteWorkflowVisibilityRecord:output_type -> temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordResponse + 128, // 128: temporal.server.api.historyservice.v1.HistoryService.UpdateWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse + 129, // 129: temporal.server.api.historyservice.v1.HistoryService.PollWorkflowExecutionUpdate:output_type -> temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateResponse + 130, // 130: temporal.server.api.historyservice.v1.HistoryService.StreamWorkflowReplicationMessages:output_type -> temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesResponse + 131, // 131: temporal.server.api.historyservice.v1.HistoryService.GetWorkflowExecutionHistory:output_type -> temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse + 132, // 132: temporal.server.api.historyservice.v1.HistoryService.GetWorkflowExecutionHistoryReverse:output_type -> temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseResponse + 133, // 133: temporal.server.api.historyservice.v1.HistoryService.GetWorkflowExecutionRawHistoryV2:output_type -> temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Response + 134, // 134: temporal.server.api.historyservice.v1.HistoryService.GetWorkflowExecutionRawHistory:output_type -> temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryResponse + 135, // 135: temporal.server.api.historyservice.v1.HistoryService.ForceDeleteWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionResponse + 136, // 136: temporal.server.api.historyservice.v1.HistoryService.DeleteExecution:output_type -> temporal.server.api.historyservice.v1.DeleteExecutionResponse + 137, // 137: temporal.server.api.historyservice.v1.HistoryService.GetDLQTasks:output_type -> temporal.server.api.historyservice.v1.GetDLQTasksResponse + 138, // 138: temporal.server.api.historyservice.v1.HistoryService.DeleteDLQTasks:output_type -> temporal.server.api.historyservice.v1.DeleteDLQTasksResponse + 139, // 139: temporal.server.api.historyservice.v1.HistoryService.ListQueues:output_type -> temporal.server.api.historyservice.v1.ListQueuesResponse + 140, // 140: temporal.server.api.historyservice.v1.HistoryService.AddTasks:output_type -> temporal.server.api.historyservice.v1.AddTasksResponse + 141, // 141: temporal.server.api.historyservice.v1.HistoryService.ListTasks:output_type -> temporal.server.api.historyservice.v1.ListTasksResponse + 142, // 142: temporal.server.api.historyservice.v1.HistoryService.CompleteNexusOperation:output_type -> temporal.server.api.historyservice.v1.CompleteNexusOperationResponse + 143, // 143: temporal.server.api.historyservice.v1.HistoryService.CompleteNexusOperationChasm:output_type -> temporal.server.api.historyservice.v1.CompleteNexusOperationChasmResponse + 144, // 144: temporal.server.api.historyservice.v1.HistoryService.InvokeStateMachineMethod:output_type -> temporal.server.api.historyservice.v1.InvokeStateMachineMethodResponse + 145, // 145: temporal.server.api.historyservice.v1.HistoryService.DeepHealthCheck:output_type -> temporal.server.api.historyservice.v1.DeepHealthCheckResponse + 146, // 146: temporal.server.api.historyservice.v1.HistoryService.SyncWorkflowState:output_type -> temporal.server.api.historyservice.v1.SyncWorkflowStateResponse + 147, // 147: temporal.server.api.historyservice.v1.HistoryService.UpdateActivityOptions:output_type -> temporal.server.api.historyservice.v1.UpdateActivityOptionsResponse + 148, // 148: temporal.server.api.historyservice.v1.HistoryService.PauseActivity:output_type -> temporal.server.api.historyservice.v1.PauseActivityResponse + 149, // 149: temporal.server.api.historyservice.v1.HistoryService.UnpauseActivity:output_type -> temporal.server.api.historyservice.v1.UnpauseActivityResponse + 150, // 150: temporal.server.api.historyservice.v1.HistoryService.ResetActivity:output_type -> temporal.server.api.historyservice.v1.ResetActivityResponse + 151, // 151: temporal.server.api.historyservice.v1.HistoryService.PauseWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.PauseWorkflowExecutionResponse + 152, // 152: temporal.server.api.historyservice.v1.HistoryService.UnpauseWorkflowExecution:output_type -> temporal.server.api.historyservice.v1.UnpauseWorkflowExecutionResponse + 153, // 153: temporal.server.api.historyservice.v1.HistoryService.StartNexusOperation:output_type -> temporal.server.api.historyservice.v1.StartNexusOperationResponse + 154, // 154: temporal.server.api.historyservice.v1.HistoryService.CancelNexusOperation:output_type -> temporal.server.api.historyservice.v1.CancelNexusOperationResponse + 155, // 155: temporal.server.api.historyservice.v1.HistoryService.PollWorkflowExecutionTimeSkipping:output_type -> temporal.server.api.historyservice.v1.PollWorkflowExecutionTimeSkippingResponse + 78, // [78:156] is the sub-list for method output_type + 0, // [0:78] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name diff --git a/api/historyservice/v1/service_grpc.pb.go b/api/historyservice/v1/service_grpc.pb.go index 8400c0a89eb..79e609f4a33 100644 --- a/api/historyservice/v1/service_grpc.pb.go +++ b/api/historyservice/v1/service_grpc.pb.go @@ -97,6 +97,7 @@ const ( HistoryService_UnpauseWorkflowExecution_FullMethodName = "/temporal.server.api.historyservice.v1.HistoryService/UnpauseWorkflowExecution" HistoryService_StartNexusOperation_FullMethodName = "/temporal.server.api.historyservice.v1.HistoryService/StartNexusOperation" HistoryService_CancelNexusOperation_FullMethodName = "/temporal.server.api.historyservice.v1.HistoryService/CancelNexusOperation" + HistoryService_PollWorkflowExecutionTimeSkipping_FullMethodName = "/temporal.server.api.historyservice.v1.HistoryService/PollWorkflowExecutionTimeSkipping" ) // HistoryServiceClient is the client API for HistoryService service. @@ -389,6 +390,7 @@ type HistoryServiceClient interface { StartNexusOperation(ctx context.Context, in *StartNexusOperationRequest, opts ...grpc.CallOption) (*StartNexusOperationResponse, error) // CancelNexusOperation cancels a Nexus operation on the __temporal_system endpoint. CancelNexusOperation(ctx context.Context, in *CancelNexusOperationRequest, opts ...grpc.CallOption) (*CancelNexusOperationResponse, error) + PollWorkflowExecutionTimeSkipping(ctx context.Context, in *PollWorkflowExecutionTimeSkippingRequest, opts ...grpc.CallOption) (*PollWorkflowExecutionTimeSkippingResponse, error) } type historyServiceClient struct { @@ -1114,6 +1116,15 @@ func (c *historyServiceClient) CancelNexusOperation(ctx context.Context, in *Can return out, nil } +func (c *historyServiceClient) PollWorkflowExecutionTimeSkipping(ctx context.Context, in *PollWorkflowExecutionTimeSkippingRequest, opts ...grpc.CallOption) (*PollWorkflowExecutionTimeSkippingResponse, error) { + out := new(PollWorkflowExecutionTimeSkippingResponse) + err := c.cc.Invoke(ctx, HistoryService_PollWorkflowExecutionTimeSkipping_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // HistoryServiceServer is the server API for HistoryService service. // All implementations must embed UnimplementedHistoryServiceServer // for forward compatibility @@ -1404,6 +1415,7 @@ type HistoryServiceServer interface { StartNexusOperation(context.Context, *StartNexusOperationRequest) (*StartNexusOperationResponse, error) // CancelNexusOperation cancels a Nexus operation on the __temporal_system endpoint. CancelNexusOperation(context.Context, *CancelNexusOperationRequest) (*CancelNexusOperationResponse, error) + PollWorkflowExecutionTimeSkipping(context.Context, *PollWorkflowExecutionTimeSkippingRequest) (*PollWorkflowExecutionTimeSkippingResponse, error) mustEmbedUnimplementedHistoryServiceServer() } @@ -1642,6 +1654,9 @@ func (UnimplementedHistoryServiceServer) StartNexusOperation(context.Context, *S func (UnimplementedHistoryServiceServer) CancelNexusOperation(context.Context, *CancelNexusOperationRequest) (*CancelNexusOperationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CancelNexusOperation not implemented") } +func (UnimplementedHistoryServiceServer) PollWorkflowExecutionTimeSkipping(context.Context, *PollWorkflowExecutionTimeSkippingRequest) (*PollWorkflowExecutionTimeSkippingResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PollWorkflowExecutionTimeSkipping not implemented") +} func (UnimplementedHistoryServiceServer) mustEmbedUnimplementedHistoryServiceServer() {} // UnsafeHistoryServiceServer may be embedded to opt out of forward compatibility for this service. @@ -3049,6 +3064,24 @@ func _HistoryService_CancelNexusOperation_Handler(srv interface{}, ctx context.C return interceptor(ctx, in, info, handler) } +func _HistoryService_PollWorkflowExecutionTimeSkipping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PollWorkflowExecutionTimeSkippingRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HistoryServiceServer).PollWorkflowExecutionTimeSkipping(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HistoryService_PollWorkflowExecutionTimeSkipping_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HistoryServiceServer).PollWorkflowExecutionTimeSkipping(ctx, req.(*PollWorkflowExecutionTimeSkippingRequest)) + } + return interceptor(ctx, in, info, handler) +} + // HistoryService_ServiceDesc is the grpc.ServiceDesc for HistoryService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -3360,6 +3393,10 @@ var HistoryService_ServiceDesc = grpc.ServiceDesc{ MethodName: "CancelNexusOperation", Handler: _HistoryService_CancelNexusOperation_Handler, }, + { + MethodName: "PollWorkflowExecutionTimeSkipping", + Handler: _HistoryService_PollWorkflowExecutionTimeSkipping_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/api/historyservicemock/v1/service_grpc.pb.mock.go b/api/historyservicemock/v1/service_grpc.pb.mock.go index bf216105dbd..6f4d8d32ff7 100644 --- a/api/historyservicemock/v1/service_grpc.pb.mock.go +++ b/api/historyservicemock/v1/service_grpc.pb.mock.go @@ -783,6 +783,26 @@ func (mr *MockHistoryServiceClientMockRecorder) PollMutableState(ctx, in any, op return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PollMutableState", reflect.TypeOf((*MockHistoryServiceClient)(nil).PollMutableState), varargs...) } +// PollWorkflowExecutionTimeSkipping mocks base method. +func (m *MockHistoryServiceClient) PollWorkflowExecutionTimeSkipping(ctx context.Context, in *historyservice.PollWorkflowExecutionTimeSkippingRequest, opts ...grpc.CallOption) (*historyservice.PollWorkflowExecutionTimeSkippingResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PollWorkflowExecutionTimeSkipping", varargs...) + ret0, _ := ret[0].(*historyservice.PollWorkflowExecutionTimeSkippingResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PollWorkflowExecutionTimeSkipping indicates an expected call of PollWorkflowExecutionTimeSkipping. +func (mr *MockHistoryServiceClientMockRecorder) PollWorkflowExecutionTimeSkipping(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PollWorkflowExecutionTimeSkipping", reflect.TypeOf((*MockHistoryServiceClient)(nil).PollWorkflowExecutionTimeSkipping), varargs...) +} + // PollWorkflowExecutionUpdate mocks base method. func (m *MockHistoryServiceClient) PollWorkflowExecutionUpdate(ctx context.Context, in *historyservice.PollWorkflowExecutionUpdateRequest, opts ...grpc.CallOption) (*historyservice.PollWorkflowExecutionUpdateResponse, error) { m.ctrl.T.Helper() @@ -2300,6 +2320,21 @@ func (mr *MockHistoryServiceServerMockRecorder) PollMutableState(arg0, arg1 any) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PollMutableState", reflect.TypeOf((*MockHistoryServiceServer)(nil).PollMutableState), arg0, arg1) } +// PollWorkflowExecutionTimeSkipping mocks base method. +func (m *MockHistoryServiceServer) PollWorkflowExecutionTimeSkipping(arg0 context.Context, arg1 *historyservice.PollWorkflowExecutionTimeSkippingRequest) (*historyservice.PollWorkflowExecutionTimeSkippingResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PollWorkflowExecutionTimeSkipping", arg0, arg1) + ret0, _ := ret[0].(*historyservice.PollWorkflowExecutionTimeSkippingResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PollWorkflowExecutionTimeSkipping indicates an expected call of PollWorkflowExecutionTimeSkipping. +func (mr *MockHistoryServiceServerMockRecorder) PollWorkflowExecutionTimeSkipping(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PollWorkflowExecutionTimeSkipping", reflect.TypeOf((*MockHistoryServiceServer)(nil).PollWorkflowExecutionTimeSkipping), arg0, arg1) +} + // PollWorkflowExecutionUpdate mocks base method. func (m *MockHistoryServiceServer) PollWorkflowExecutionUpdate(arg0 context.Context, arg1 *historyservice.PollWorkflowExecutionUpdateRequest) (*historyservice.PollWorkflowExecutionUpdateResponse, error) { m.ctrl.T.Helper() diff --git a/api/persistence/v1/executions.pb.go b/api/persistence/v1/executions.pb.go index 113a506e447..260c766c87b 100644 --- a/api/persistence/v1/executions.pb.go +++ b/api/persistence/v1/executions.pb.go @@ -1168,21 +1168,22 @@ func (*WorkflowExecutionInfo_LastWorkflowTaskTimedOutType) isWorkflowExecutionIn type TimeSkippingInfo struct { state protoimpl.MessageState `protogen:"open.v1"` - // Current time-skipping configuration applied to the workflow. + // Current time-skipping configuration applied to the execution. Config *v13.TimeSkippingConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` - // Total skipped duration for the current workflow execution run, including any - // inherited skipped duration carried over from a preceding execution that started this run. + // Total skipped duration for the current execution, including any + // inherited skipped duration carried over from a preceding run that started this one. AccumulatedSkippedDuration *durationpb.Duration `protobuf:"bytes,2,opt,name=accumulated_skipped_duration,json=accumulatedSkippedDuration,proto3" json:"accumulated_skipped_duration,omitempty"` // The current fast-forward info for time skipping. - FastForwardInfo *FastForwardInfo `protobuf:"bytes,4,opt,name=fast_forward_info,json=fastForwardInfo,proto3" json:"fast_forward_info,omitempty"` + FastForwardInfo *FastForwardInfo `protobuf:"bytes,4,opt,name=fast_forward_info,json=fastForwardInfo,proto3" json:"fast_forward_info,omitempty"` + FastForwardInfoLastUpdateVersionedTransition *VersionedTransition `protobuf:"bytes,7,opt,name=fast_forward_info_last_update_versioned_transition,json=fastForwardInfoLastUpdateVersionedTransition,proto3" json:"fast_forward_info_last_update_versioned_transition,omitempty"` // Versioned transition at which this TimeSkippingInfo was last modified (i.e. when a - // skip transition changed accumulated_skipped_duration). Used by PartialRefresh to detect - // that pending timer tasks must be re-stamped against the new accumulated skip, since a - // skip mutates this workflow-level field without bumping any per-timer - // last_update_versioned_transition. Mirrors the per-entity stamps on TimerInfo/ActivityInfo. + // skip transition changed accumulated_skipped_duration or a request updated the config.) LastUpdateVersionedTransition *VersionedTransition `protobuf:"bytes,5,opt,name=last_update_versioned_transition,json=lastUpdateVersionedTransition,proto3" json:"last_update_versioned_transition,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Tracks the number of times a time-skipping transition has occurred since time skipping + // was most recently enabled. It is reset every time the skipping config is updated. + SessionSkipCount int32 `protobuf:"varint,6,opt,name=session_skip_count,json=sessionSkipCount,proto3" json:"session_skip_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TimeSkippingInfo) Reset() { @@ -1236,6 +1237,13 @@ func (x *TimeSkippingInfo) GetFastForwardInfo() *FastForwardInfo { return nil } +func (x *TimeSkippingInfo) GetFastForwardInfoLastUpdateVersionedTransition() *VersionedTransition { + if x != nil { + return x.FastForwardInfoLastUpdateVersionedTransition + } + return nil +} + func (x *TimeSkippingInfo) GetLastUpdateVersionedTransition() *VersionedTransition { if x != nil { return x.LastUpdateVersionedTransition @@ -1243,15 +1251,21 @@ func (x *TimeSkippingInfo) GetLastUpdateVersionedTransition() *VersionedTransiti return nil } +func (x *TimeSkippingInfo) GetSessionSkipCount() int32 { + if x != nil { + return x.SessionSkipCount + } + return 0 +} + type FastForwardInfo struct { state protoimpl.MessageState `protogen:"open.v1"` // Target time for the fast-forward, expressed in virtual time. TargetTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=target_time,json=targetTime,proto3" json:"target_time,omitempty"` // Indicates whether this fast-forward has already been reached, used for idempotency checks. - HasReached bool `protobuf:"varint,2,opt,name=has_reached,json=hasReached,proto3" json:"has_reached,omitempty"` - LastUpdateVersionedTransition *VersionedTransition `protobuf:"bytes,4,opt,name=last_update_versioned_transition,json=lastUpdateVersionedTransition,proto3" json:"last_update_versioned_transition,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + HasReached bool `protobuf:"varint,2,opt,name=has_reached,json=hasReached,proto3" json:"has_reached,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FastForwardInfo) Reset() { @@ -1298,13 +1312,6 @@ func (x *FastForwardInfo) GetHasReached() bool { return false } -func (x *FastForwardInfo) GetLastUpdateVersionedTransition() *VersionedTransition { - if x != nil { - return x.LastUpdateVersionedTransition - } - return nil -} - // Internal wrapper message to distinguish "never notified" (nil wrapper) from // "notified about an unversioned target" (non-nil wrapper with nil deployment_version). // Used only within server persistence; never flows to the public API. @@ -5080,18 +5087,19 @@ 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\"\xbd\x03\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\"\x8f\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" + - "\x11fast_forward_info\x18\x04 \x01(\v23.temporal.server.api.persistence.v1.FastForwardInfoR\x0ffastForwardInfo\x12\x80\x01\n" + - " last_update_versioned_transition\x18\x05 \x01(\v27.temporal.server.api.persistence.v1.VersionedTransitionR\x1dlastUpdateVersionedTransitionJ\x04\b\x03\x10\x04R\x1ecurrent_elapsed_duration_bound\"\x89\x02\n" + + "\x11fast_forward_info\x18\x04 \x01(\v23.temporal.server.api.persistence.v1.FastForwardInfoR\x0ffastForwardInfo\x12\xa1\x01\n" + + "2fast_forward_info_last_update_versioned_transition\x18\a \x01(\v27.temporal.server.api.persistence.v1.VersionedTransitionR,fastForwardInfoLastUpdateVersionedTransition\x12\x80\x01\n" + + " last_update_versioned_transition\x18\x05 \x01(\v27.temporal.server.api.persistence.v1.VersionedTransitionR\x1dlastUpdateVersionedTransition\x12,\n" + + "\x12session_skip_count\x18\x06 \x01(\x05R\x10sessionSkipCountJ\x04\b\x03\x10\x04R\x1ecurrent_elapsed_duration_bound\"\xae\x01\n" + "\x0fFastForwardInfo\x12;\n" + "\vtarget_time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\n" + "targetTime\x12\x1f\n" + "\vhas_reached\x18\x02 \x01(\bR\n" + - "hasReached\x12\x80\x01\n" + - " last_update_versioned_transition\x18\x04 \x01(\v27.temporal.server.api.persistence.v1.VersionedTransitionR\x1dlastUpdateVersionedTransitionJ\x04\b\x03\x10\x04R\x0fsource_event_id\"\xa8\x01\n" + + "hasReachedJ\x04\b\x03\x10\x04J\x04\b\x04\x10\x05R\x0fsource_event_idR last_update_versioned_transition\"\xa8\x01\n" + "\x19LastNotifiedTargetVersion\x12b\n" + "\x12deployment_version\x18\x01 \x01(\v23.temporal.api.deployment.v1.WorkerDeploymentVersionR\x11deploymentVersion\x12'\n" + "\x0frevision_number\x18\x02 \x01(\x03R\x0erevisionNumber\"\x9d\x01\n" + @@ -5589,9 +5597,9 @@ var file_temporal_server_api_persistence_v1_executions_proto_depIdxs = []int32{ 64, // 48: temporal.server.api.persistence.v1.TimeSkippingInfo.config:type_name -> temporal.api.common.v1.TimeSkippingConfig 48, // 49: temporal.server.api.persistence.v1.TimeSkippingInfo.accumulated_skipped_duration:type_name -> google.protobuf.Duration 3, // 50: temporal.server.api.persistence.v1.TimeSkippingInfo.fast_forward_info:type_name -> temporal.server.api.persistence.v1.FastForwardInfo - 56, // 51: temporal.server.api.persistence.v1.TimeSkippingInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 47, // 52: temporal.server.api.persistence.v1.FastForwardInfo.target_time:type_name -> google.protobuf.Timestamp - 56, // 53: temporal.server.api.persistence.v1.FastForwardInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 56, // 51: temporal.server.api.persistence.v1.TimeSkippingInfo.fast_forward_info_last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 56, // 52: temporal.server.api.persistence.v1.TimeSkippingInfo.last_update_versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 47, // 53: temporal.server.api.persistence.v1.FastForwardInfo.target_time:type_name -> google.protobuf.Timestamp 65, // 54: temporal.server.api.persistence.v1.LastNotifiedTargetVersion.deployment_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion 66, // 55: temporal.server.api.persistence.v1.WorkflowExecutionState.state:type_name -> temporal.server.api.enums.v1.WorkflowExecutionState 67, // 56: temporal.server.api.persistence.v1.WorkflowExecutionState.status:type_name -> temporal.api.enums.v1.WorkflowExecutionStatus diff --git a/client/frontend/client_gen.go b/client/frontend/client_gen.go index 298cdc4ce84..7d8513a7298 100644 --- a/client/frontend/client_gen.go +++ b/client/frontend/client_gen.go @@ -649,6 +649,16 @@ func (c *clientImpl) PollNexusTaskQueue( return c.client.PollNexusTaskQueue(ctx, request, opts...) } +func (c *clientImpl) PollWorkflowExecutionTimeSkipping( + ctx context.Context, + request *workflowservice.PollWorkflowExecutionTimeSkippingRequest, + opts ...grpc.CallOption, +) (*workflowservice.PollWorkflowExecutionTimeSkippingResponse, error) { + ctx, cancel := c.createContext(ctx) + defer cancel() + return c.client.PollWorkflowExecutionTimeSkipping(ctx, request, opts...) +} + func (c *clientImpl) PollWorkflowExecutionUpdate( ctx context.Context, request *workflowservice.PollWorkflowExecutionUpdateRequest, diff --git a/client/frontend/metric_client_gen.go b/client/frontend/metric_client_gen.go index f0b079455c8..a79a8e55ed1 100644 --- a/client/frontend/metric_client_gen.go +++ b/client/frontend/metric_client_gen.go @@ -905,6 +905,20 @@ func (c *metricClient) PollNexusTaskQueue( return c.client.PollNexusTaskQueue(ctx, request, opts...) } +func (c *metricClient) PollWorkflowExecutionTimeSkipping( + ctx context.Context, + request *workflowservice.PollWorkflowExecutionTimeSkippingRequest, + opts ...grpc.CallOption, +) (_ *workflowservice.PollWorkflowExecutionTimeSkippingResponse, retError error) { + + metricsHandler, startTime := c.startMetricsRecording(ctx, "FrontendClientPollWorkflowExecutionTimeSkipping") + defer func() { + c.finishMetricsRecording(metricsHandler, startTime, retError) + }() + + return c.client.PollWorkflowExecutionTimeSkipping(ctx, request, opts...) +} + func (c *metricClient) PollWorkflowExecutionUpdate( ctx context.Context, request *workflowservice.PollWorkflowExecutionUpdateRequest, diff --git a/client/frontend/retryable_client_gen.go b/client/frontend/retryable_client_gen.go index f9ad9357640..a8a09ef412f 100644 --- a/client/frontend/retryable_client_gen.go +++ b/client/frontend/retryable_client_gen.go @@ -971,6 +971,21 @@ func (c *retryableClient) PollNexusTaskQueue( return resp, err } +func (c *retryableClient) PollWorkflowExecutionTimeSkipping( + ctx context.Context, + request *workflowservice.PollWorkflowExecutionTimeSkippingRequest, + opts ...grpc.CallOption, +) (*workflowservice.PollWorkflowExecutionTimeSkippingResponse, error) { + var resp *workflowservice.PollWorkflowExecutionTimeSkippingResponse + op := func(ctx context.Context) error { + var err error + resp, err = c.client.PollWorkflowExecutionTimeSkipping(ctx, request, opts...) + return err + } + err := backoff.ThrottleRetryContext(ctx, op, c.policy, c.isRetryable) + return resp, err +} + func (c *retryableClient) PollWorkflowExecutionUpdate( ctx context.Context, request *workflowservice.PollWorkflowExecutionUpdateRequest, diff --git a/client/history/client_gen.go b/client/history/client_gen.go index 494b6135e9b..a84ae8574f1 100644 --- a/client/history/client_gen.go +++ b/client/history/client_gen.go @@ -679,6 +679,26 @@ func (c *clientImpl) PollMutableState( return response, nil } +func (c *clientImpl) PollWorkflowExecutionTimeSkipping( + ctx context.Context, + request *historyservice.PollWorkflowExecutionTimeSkippingRequest, + opts ...grpc.CallOption, +) (*historyservice.PollWorkflowExecutionTimeSkippingResponse, error) { + shardID := c.shardIDFromWorkflowID(request.GetNamespaceId(), request.GetRequest().GetWorkflowExecution().GetWorkflowId()) + var response *historyservice.PollWorkflowExecutionTimeSkippingResponse + op := func(ctx context.Context, client historyservice.HistoryServiceClient) error { + var err error + ctx, cancel := c.createContext(ctx) + defer cancel() + response, err = client.PollWorkflowExecutionTimeSkipping(ctx, request, opts...) + return err + } + if err := c.executeWithRedirect(ctx, shardID, op); err != nil { + return nil, err + } + return response, nil +} + func (c *clientImpl) PollWorkflowExecutionUpdate( ctx context.Context, request *historyservice.PollWorkflowExecutionUpdateRequest, diff --git a/client/history/metric_client_gen.go b/client/history/metric_client_gen.go index 767aaf576c7..8d4cad72aa5 100644 --- a/client/history/metric_client_gen.go +++ b/client/history/metric_client_gen.go @@ -513,6 +513,20 @@ func (c *metricClient) PollMutableState( return c.client.PollMutableState(ctx, request, opts...) } +func (c *metricClient) PollWorkflowExecutionTimeSkipping( + ctx context.Context, + request *historyservice.PollWorkflowExecutionTimeSkippingRequest, + opts ...grpc.CallOption, +) (_ *historyservice.PollWorkflowExecutionTimeSkippingResponse, retError error) { + + metricsHandler, startTime := c.startMetricsRecording(ctx, "HistoryClientPollWorkflowExecutionTimeSkipping") + defer func() { + c.finishMetricsRecording(metricsHandler, startTime, retError) + }() + + return c.client.PollWorkflowExecutionTimeSkipping(ctx, request, opts...) +} + func (c *metricClient) PollWorkflowExecutionUpdate( ctx context.Context, request *historyservice.PollWorkflowExecutionUpdateRequest, diff --git a/client/history/retryable_client_gen.go b/client/history/retryable_client_gen.go index 7a608e08a10..31e48612095 100644 --- a/client/history/retryable_client_gen.go +++ b/client/history/retryable_client_gen.go @@ -566,6 +566,21 @@ func (c *retryableClient) PollMutableState( return resp, err } +func (c *retryableClient) PollWorkflowExecutionTimeSkipping( + ctx context.Context, + request *historyservice.PollWorkflowExecutionTimeSkippingRequest, + opts ...grpc.CallOption, +) (*historyservice.PollWorkflowExecutionTimeSkippingResponse, error) { + var resp *historyservice.PollWorkflowExecutionTimeSkippingResponse + op := func(ctx context.Context) error { + var err error + resp, err = c.client.PollWorkflowExecutionTimeSkipping(ctx, request, opts...) + return err + } + err := backoff.ThrottleRetryContext(ctx, op, c.policy, c.isRetryable) + return resp, err +} + func (c *retryableClient) PollWorkflowExecutionUpdate( ctx context.Context, request *historyservice.PollWorkflowExecutionUpdateRequest, diff --git a/cmd/tools/getproto/files.go b/cmd/tools/getproto/files.go index aecd47fb3be..38b6ef67622 100644 --- a/cmd/tools/getproto/files.go +++ b/cmd/tools/getproto/files.go @@ -1,3 +1,4 @@ + // Code generated by getproto. DO NOT EDIT. // If you get build errors in this file, just delete it. It will be regenerated. @@ -73,6 +74,7 @@ func init() { importMap["temporal/api/enums/v1/reset.proto"] = enums.File_temporal_api_enums_v1_reset_proto importMap["temporal/api/enums/v1/schedule.proto"] = enums.File_temporal_api_enums_v1_schedule_proto importMap["temporal/api/enums/v1/task_queue.proto"] = enums.File_temporal_api_enums_v1_task_queue_proto + importMap["temporal/api/enums/v1/time_skipping.proto"] = enums.File_temporal_api_enums_v1_time_skipping_proto importMap["temporal/api/enums/v1/update.proto"] = enums.File_temporal_api_enums_v1_update_proto importMap["temporal/api/enums/v1/workflow.proto"] = enums.File_temporal_api_enums_v1_workflow_proto importMap["temporal/api/failure/v1/message.proto"] = failure.File_temporal_api_failure_v1_message_proto diff --git a/common/api/metadata.go b/common/api/metadata.go index 1430d48f3f7..41843262284 100644 --- a/common/api/metadata.go +++ b/common/api/metadata.go @@ -145,6 +145,7 @@ var ( "GetWorkerTaskReachability": {Scope: ScopeNamespace, Access: AccessReadOnly, Polling: PollingNone}, "UpdateWorkflowExecution": {Scope: ScopeNamespace, Access: AccessWrite, Polling: PollingNone}, "PollWorkflowExecutionUpdate": {Scope: ScopeNamespace, Access: AccessReadOnly, Polling: PollingAlways}, + "PollWorkflowExecutionTimeSkipping": {Scope: ScopeNamespace, Access: AccessReadOnly, Polling: PollingAlways}, "StartBatchOperation": {Scope: ScopeNamespace, Access: AccessWrite, Polling: PollingNone}, "StopBatchOperation": {Scope: ScopeNamespace, Access: AccessWrite, Polling: PollingNone}, "DescribeBatchOperation": {Scope: ScopeNamespace, Access: AccessReadOnly, Polling: PollingNone}, diff --git a/common/dynamicconfig/constants.go b/common/dynamicconfig/constants.go index 99751c98265..cae4f221fee 100644 --- a/common/dynamicconfig/constants.go +++ b/common/dynamicconfig/constants.go @@ -3590,9 +3590,14 @@ WorkerActivitiesPerSecond, MaxConcurrentActivityTaskPollers. false, `WorkflowPauseEnabled is a "feature enable" flag. When enabled it allows clients to pause workflows.`, ) - TimeSkippingEnabled = NewNamespaceBoolSetting( - "frontend.TimeSkippingEnabled", + WorkflowTimeSkippingEnabled = NewNamespaceBoolSetting( + "frontend.WorkflowTimeSkippingEnabled", false, - `TimeSkippingEnabled is a "feature enable" flag. When enabled it allows clients to skip time in executions.`, + `WorkflowTimeSkippingEnabled is a "feature enable" flag. When enabled it allows clients to skip time in executions.`, + ) + WorkflowTimeSkippingMaxSkipPerSession = NewNamespaceIntSetting( + "frontend.WorkflowTimeSkippingMaxSkipPerSession", + 200, + "WorkflowTimeSkippingMaxSkipPerSession is the default max-skip-per-session applied to requests that don't set one; callers may override it with a higher value.", ) ) diff --git a/common/rpc/interceptor/logtags/history_service_server_gen.go b/common/rpc/interceptor/logtags/history_service_server_gen.go index e79170db9cf..038ccbf9c40 100644 --- a/common/rpc/interceptor/logtags/history_service_server_gen.go +++ b/common/rpc/interceptor/logtags/history_service_server_gen.go @@ -233,6 +233,13 @@ func (wt *WorkflowTags) extractFromHistoryServiceServerMessage(message any) []ta tag.WorkflowID(r.GetExecution().GetWorkflowId()), tag.WorkflowRunID(r.GetExecution().GetRunId()), } + case *historyservice.PollWorkflowExecutionTimeSkippingRequest: + return []tag.Tag{ + tag.WorkflowID(r.GetRequest().GetWorkflowExecution().GetWorkflowId()), + tag.WorkflowRunID(r.GetRequest().GetWorkflowExecution().GetRunId()), + } + case *historyservice.PollWorkflowExecutionTimeSkippingResponse: + return nil case *historyservice.PollWorkflowExecutionUpdateRequest: return []tag.Tag{ tag.WorkflowID(r.GetRequest().GetUpdateRef().GetWorkflowExecution().GetWorkflowId()), diff --git a/common/rpc/interceptor/logtags/workflow_service_server_gen.go b/common/rpc/interceptor/logtags/workflow_service_server_gen.go index 749ff8afa84..8de3d6ad4ef 100644 --- a/common/rpc/interceptor/logtags/workflow_service_server_gen.go +++ b/common/rpc/interceptor/logtags/workflow_service_server_gen.go @@ -316,6 +316,13 @@ func (wt *WorkflowTags) extractFromWorkflowServiceServerMessage(message any) []t return nil case *workflowservice.PollNexusTaskQueueResponse: return wt.fromTaskToken(r.GetTaskToken()) + case *workflowservice.PollWorkflowExecutionTimeSkippingRequest: + return []tag.Tag{ + tag.WorkflowID(r.GetWorkflowExecution().GetWorkflowId()), + tag.WorkflowRunID(r.GetWorkflowExecution().GetRunId()), + } + case *workflowservice.PollWorkflowExecutionTimeSkippingResponse: + return nil case *workflowservice.PollWorkflowExecutionUpdateRequest: return []tag.Tag{ tag.WorkflowID(r.GetUpdateRef().GetWorkflowExecution().GetWorkflowId()), diff --git a/common/rpc/interceptor/redirection.go b/common/rpc/interceptor/redirection.go index d2a30580840..80a1cf01423 100644 --- a/common/rpc/interceptor/redirection.go +++ b/common/rpc/interceptor/redirection.go @@ -83,6 +83,7 @@ var ( "ExecuteMultiOperation": func() any { return &workflowservice.ExecuteMultiOperationResponse{} }, "UpdateWorkflowExecution": func() any { return &workflowservice.UpdateWorkflowExecutionResponse{} }, "PollWorkflowExecutionUpdate": func() any { return &workflowservice.PollWorkflowExecutionUpdateResponse{} }, + "PollWorkflowExecutionTimeSkipping": func() any { return &workflowservice.PollWorkflowExecutionTimeSkippingResponse{} }, "TerminateWorkflowExecution": func() any { return &workflowservice.TerminateWorkflowExecutionResponse{} }, "DeleteWorkflowExecution": func() any { return &workflowservice.DeleteWorkflowExecutionResponse{} }, "ListTaskQueuePartitions": func() any { return &workflowservice.ListTaskQueuePartitionsResponse{} }, diff --git a/common/rpc/interceptor/redirection_test.go b/common/rpc/interceptor/redirection_test.go index b5679aa7959..11e6df3a681 100644 --- a/common/rpc/interceptor/redirection_test.go +++ b/common/rpc/interceptor/redirection_test.go @@ -141,6 +141,7 @@ func (s *redirectionInterceptorSuite) TestGlobalAPI() { "ExecuteMultiOperation": {}, "UpdateWorkflowExecution": {}, "PollWorkflowExecutionUpdate": {}, + "PollWorkflowExecutionTimeSkipping": {}, "TerminateWorkflowExecution": {}, "DeleteWorkflowExecution": {}, "ListTaskQueuePartitions": {}, diff --git a/common/rpc/interceptor/routing_key_extractor_gen.go b/common/rpc/interceptor/routing_key_extractor_gen.go index a0398f4c005..0edb8074fe5 100644 --- a/common/rpc/interceptor/routing_key_extractor_gen.go +++ b/common/rpc/interceptor/routing_key_extractor_gen.go @@ -64,6 +64,8 @@ func workflowServiceRequestRoutingKey(req any) namespace.RoutingKey { return namespace.RoutingKey{ID: r.GetPollerGroupId(), Strategy: namespace.RoutingStrategyPollerGroup} case *workflowservice.PollNexusTaskQueueRequest: return namespace.RoutingKey{ID: r.GetPollerGroupId(), Strategy: namespace.RoutingStrategyPollerGroup} + case *workflowservice.PollWorkflowExecutionTimeSkippingRequest: + return namespace.RoutingKey{ID: r.GetWorkflowExecution().GetWorkflowId()} case *workflowservice.PollWorkflowExecutionUpdateRequest: return namespace.RoutingKey{ID: r.GetUpdateRef().GetWorkflowExecution().GetWorkflowId()} case *workflowservice.PollWorkflowTaskQueueRequest: diff --git a/common/testing/mockapi/workflowservicemock/v1/service_grpc.pb.mock.go b/common/testing/mockapi/workflowservicemock/v1/service_grpc.pb.mock.go index f5802847987..7193985a28a 100644 --- a/common/testing/mockapi/workflowservicemock/v1/service_grpc.pb.mock.go +++ b/common/testing/mockapi/workflowservicemock/v1/service_grpc.pb.mock.go @@ -1322,6 +1322,26 @@ func (mr *MockWorkflowServiceClientMockRecorder) PollNexusTaskQueue(ctx, in any, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PollNexusTaskQueue", reflect.TypeOf((*MockWorkflowServiceClient)(nil).PollNexusTaskQueue), varargs...) } +// PollWorkflowExecutionTimeSkipping mocks base method. +func (m *MockWorkflowServiceClient) PollWorkflowExecutionTimeSkipping(ctx context.Context, in *workflowservice.PollWorkflowExecutionTimeSkippingRequest, opts ...grpc.CallOption) (*workflowservice.PollWorkflowExecutionTimeSkippingResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PollWorkflowExecutionTimeSkipping", varargs...) + ret0, _ := ret[0].(*workflowservice.PollWorkflowExecutionTimeSkippingResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PollWorkflowExecutionTimeSkipping indicates an expected call of PollWorkflowExecutionTimeSkipping. +func (mr *MockWorkflowServiceClientMockRecorder) PollWorkflowExecutionTimeSkipping(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PollWorkflowExecutionTimeSkipping", reflect.TypeOf((*MockWorkflowServiceClient)(nil).PollWorkflowExecutionTimeSkipping), varargs...) +} + // PollWorkflowExecutionUpdate mocks base method. func (m *MockWorkflowServiceClient) PollWorkflowExecutionUpdate(ctx context.Context, in *workflowservice.PollWorkflowExecutionUpdateRequest, opts ...grpc.CallOption) (*workflowservice.PollWorkflowExecutionUpdateResponse, error) { m.ctrl.T.Helper() diff --git a/go.mod b/go.mod index bb558ce25d0..0e330c36f30 100644 --- a/go.mod +++ b/go.mod @@ -66,7 +66,7 @@ require ( go.opentelemetry.io/otel/sdk v1.43.0 go.opentelemetry.io/otel/sdk/metric v1.43.0 go.opentelemetry.io/otel/trace v1.44.0 - go.temporal.io/api v1.63.4 + go.temporal.io/api v1.63.5-0.20260730194729-ba94140a7640 go.temporal.io/auto-scaled-workers v0.0.0-20260706201056-4320b34799ee go.temporal.io/sdk v1.44.0 go.uber.org/fx v1.24.0 diff --git a/go.sum b/go.sum index 4bdb5650794..588b9de385d 100644 --- a/go.sum +++ b/go.sum @@ -479,8 +479,8 @@ go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:R go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U= go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0= go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4= -go.temporal.io/api v1.63.4 h1:p4dVIAP3dJop0MfcyH9QSzjU7+V/ttLDhxFhSRUar58= -go.temporal.io/api v1.63.4/go.mod h1:SrlW2JMwVlDP4nRWSNznUFqnSHd+YeMDS1BkYo63HCQ= +go.temporal.io/api v1.63.5-0.20260730194729-ba94140a7640 h1:jA7eUGPMrhXi35QHD0FM+bqPCmajFgoTUjn8cVnDRAY= +go.temporal.io/api v1.63.5-0.20260730194729-ba94140a7640/go.mod h1:SrlW2JMwVlDP4nRWSNznUFqnSHd+YeMDS1BkYo63HCQ= go.temporal.io/auto-scaled-workers v0.0.0-20260706201056-4320b34799ee h1:y6A65Iml06cR3CpxW2Zn8FQLjniPDTnN4jtr69UZxXI= go.temporal.io/auto-scaled-workers v0.0.0-20260706201056-4320b34799ee/go.mod h1:hhHijO9XRPIkAflLJJHix61M9FzbRPqk8fSydkcLkqw= go.temporal.io/sdk v1.44.0 h1:suitPDukX74rW3/N1FqvEbZTZVJJsxMKhv0KMa/j7pU= 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 88ee7f6a273..86aa8bdaf60 100644 --- a/proto/internal/temporal/server/api/historyservice/v1/request_response.proto +++ b/proto/internal/temporal/server/api/historyservice/v1/request_response.proto @@ -1439,3 +1439,14 @@ message CancelNexusOperationRequest { message CancelNexusOperationResponse { temporal.api.nexus.v1.CancelOperationResponse response = 1; } + +message PollWorkflowExecutionTimeSkippingRequest { + option (routing).workflow_id = "request.workflow_execution.workflow_id"; + + string namespace_id = 1; + temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingRequest request = 2; +} + +message PollWorkflowExecutionTimeSkippingResponse { + temporal.api.workflowservice.v1.PollWorkflowExecutionTimeSkippingResponse response = 1; +} diff --git a/proto/internal/temporal/server/api/historyservice/v1/service.proto b/proto/internal/temporal/server/api/historyservice/v1/service.proto index dd27cbd34d6..58b0019b9f7 100644 --- a/proto/internal/temporal/server/api/historyservice/v1/service.proto +++ b/proto/internal/temporal/server/api/historyservice/v1/service.proto @@ -518,4 +518,8 @@ service HistoryService { rpc CancelNexusOperation(CancelNexusOperationRequest) returns (CancelNexusOperationResponse) { option (temporal.server.api.common.v1.api_category).category = API_CATEGORY_STANDARD; } + + rpc PollWorkflowExecutionTimeSkipping(PollWorkflowExecutionTimeSkippingRequest) returns (PollWorkflowExecutionTimeSkippingResponse) { + option (temporal.server.api.common.v1.api_category).category = API_CATEGORY_LONG_POLL; + } } diff --git a/proto/internal/temporal/server/api/persistence/v1/executions.proto b/proto/internal/temporal/server/api/persistence/v1/executions.proto index f20a9440dbe..4d2be0f15ad 100644 --- a/proto/internal/temporal/server/api/persistence/v1/executions.proto +++ b/proto/internal/temporal/server/api/persistence/v1/executions.proto @@ -325,35 +325,35 @@ message TimeSkippingInfo { reserved 3; reserved "current_elapsed_duration_bound"; - // Current time-skipping configuration applied to the workflow. + // Current time-skipping configuration applied to the execution. temporal.api.common.v1.TimeSkippingConfig config = 1; - // Total skipped duration for the current workflow execution run, including any - // inherited skipped duration carried over from a preceding execution that started this run. + // Total skipped duration for the current execution, including any + // inherited skipped duration carried over from a preceding run that started this one. google.protobuf.Duration accumulated_skipped_duration = 2; // The current fast-forward info for time skipping. FastForwardInfo fast_forward_info = 4; + VersionedTransition fast_forward_info_last_update_versioned_transition = 7; // Versioned transition at which this TimeSkippingInfo was last modified (i.e. when a - // skip transition changed accumulated_skipped_duration). Used by PartialRefresh to detect - // that pending timer tasks must be re-stamped against the new accumulated skip, since a - // skip mutates this workflow-level field without bumping any per-timer - // last_update_versioned_transition. Mirrors the per-entity stamps on TimerInfo/ActivityInfo. + // skip transition changed accumulated_skipped_duration or a request updated the config.) VersionedTransition last_update_versioned_transition = 5; + + // Tracks the number of times a time-skipping transition has occurred since time skipping + // was most recently enabled. It is reset every time the skipping config is updated. + int32 session_skip_count = 6; } message FastForwardInfo { - reserved 3; - reserved "source_event_id"; + reserved 3, 4; + reserved "source_event_id", "last_update_versioned_transition"; // Target time for the fast-forward, expressed in virtual time. google.protobuf.Timestamp target_time = 1; // Indicates whether this fast-forward has already been reached, used for idempotency checks. bool has_reached = 2; - - VersionedTransition last_update_versioned_transition = 4; } // Internal wrapper message to distinguish "never notified" (nil wrapper) from diff --git a/service/frontend/configs/quotas.go b/service/frontend/configs/quotas.go index b1875a81402..da1b9f39ac6 100644 --- a/service/frontend/configs/quotas.go +++ b/service/frontend/configs/quotas.go @@ -36,11 +36,12 @@ var ( // they both block until a background WFT is complete. ExecutionAPICountLimitOverride = map[string]int{ // These methods here are long-running because they block until there is a task available. - "/temporal.api.workflowservice.v1.WorkflowService/PollActivityTaskQueue": 1, - "/temporal.api.workflowservice.v1.WorkflowService/PollWorkflowTaskQueue": 1, - "/temporal.api.workflowservice.v1.WorkflowService/PollWorkflowExecutionUpdate": 1, - "/temporal.api.workflowservice.v1.WorkflowService/PollNexusTaskQueue": 1, - "/temporal.api.workflowservice.v1.WorkflowService/PollNexusOperationExecution": 1, + "/temporal.api.workflowservice.v1.WorkflowService/PollActivityTaskQueue": 1, + "/temporal.api.workflowservice.v1.WorkflowService/PollWorkflowTaskQueue": 1, + "/temporal.api.workflowservice.v1.WorkflowService/PollWorkflowExecutionUpdate": 1, + "/temporal.api.workflowservice.v1.WorkflowService/PollNexusTaskQueue": 1, + "/temporal.api.workflowservice.v1.WorkflowService/PollNexusOperationExecution": 1, + "/temporal.api.workflowservice.v1.WorkflowService/PollWorkflowExecutionTimeSkipping": 1, // Long-running if activity outcome is not already available "/temporal.api.workflowservice.v1.WorkflowService/PollActivityExecution": 1, @@ -199,6 +200,8 @@ var ( // Treat these as long-poll but lower priority (5) so spikes don’t block Poll* APIs. PollWorkflowHistoryAPIName: 5, PollActivityExecutionAPIName: 5, + // Test-support long-poll; not required for the service to function, so it yields to production traffic. + "/temporal.api.workflowservice.v1.WorkflowService/PollWorkflowExecutionTimeSkipping": 5, // Informational API that aren't required for the temporal service to function OpenAPIV3APIName: 5, OpenAPIV2APIName: 5, diff --git a/service/frontend/errors.go b/service/frontend/errors.go index 624f1297b61..dccc89db6c8 100644 --- a/service/frontend/errors.go +++ b/service/frontend/errors.go @@ -114,4 +114,7 @@ var ( errWorkerVersioningWorkflowAPIsNotAllowed = serviceerror.NewPermissionDenied("Worker versioning in workflow progress APIs is disabled on this namespace.", "") errListHistoryTasksNotAllowed = serviceerror.NewPermissionDenied("ListHistoryTasks feature is disabled on this cluster.", "") + + errWorkflowTimeSkippingNotEnabled = serviceerror.NewUnimplemented("The Time-Skipping feature is not enabled for namespace.") + errTimeSkippingFastForwardIDNotSet = serviceerror.NewInvalidArgument("Time skipping config invalid: fast_forward_id must be set") ) diff --git a/service/frontend/service.go b/service/frontend/service.go index 85741de29c6..542406c155a 100644 --- a/service/frontend/service.go +++ b/service/frontend/service.go @@ -238,7 +238,8 @@ type Config struct { WorkerCommandsEnabled dynamicconfig.BoolPropertyFnWithNamespaceFilter PollerAutoscalingAutoEnroll dynamicconfig.BoolPropertyFnWithNamespaceFilter WorkflowPauseEnabled dynamicconfig.BoolPropertyFnWithNamespaceFilter - TimeSkippingEnabled dynamicconfig.BoolPropertyFnWithNamespaceFilter + WorkflowTimeSkippingEnabled dynamicconfig.BoolPropertyFnWithNamespaceFilter + WorkflowTimeSkippingMaxSkipPerSession dynamicconfig.IntPropertyFnWithNamespaceFilter StandaloneNexusOperationsEnabled dynamicconfig.BoolPropertyFnWithNamespaceFilter EnableWorkflowTaskCompletionPagination dynamicconfig.BoolPropertyFnWithNamespaceFilter @@ -415,7 +416,8 @@ func NewConfig( WorkerCommandsEnabled: dynamicconfig.WorkerCommandsEnabled.Get(dc), PollerAutoscalingAutoEnroll: dynamicconfig.PollerAutoscalingAutoEnroll.Get(dc), WorkflowPauseEnabled: dynamicconfig.WorkflowPauseEnabled.Get(dc), - TimeSkippingEnabled: dynamicconfig.TimeSkippingEnabled.Get(dc), + WorkflowTimeSkippingEnabled: dynamicconfig.WorkflowTimeSkippingEnabled.Get(dc), + WorkflowTimeSkippingMaxSkipPerSession: dynamicconfig.WorkflowTimeSkippingMaxSkipPerSession.Get(dc), StandaloneNexusOperationsEnabled: chasmnexus.Enabled.Get(dc), EnableWorkflowTaskCompletionPagination: dynamicconfig.EnableWorkflowTaskCompletionPagination.Get(dc), diff --git a/service/frontend/workflow_handler.go b/service/frontend/workflow_handler.go index 3aaaefc1110..0f6429bb003 100644 --- a/service/frontend/workflow_handler.go +++ b/service/frontend/workflow_handler.go @@ -694,13 +694,13 @@ func (wh *WorkflowHandler) prepareStartWorkflowRequest( return nil, err } - if err := wh.validateTimeSkippingConfig(request.GetTimeSkippingConfig(), namespaceName); err != nil { + if err := wh.validateAndPopulateTimeSkippingConfig(request.GetTimeSkippingConfig(), namespaceName); err != nil { return nil, err } return request, nil } -func (wh *WorkflowHandler) validateTimeSkippingConfig( +func (wh *WorkflowHandler) validateAndPopulateTimeSkippingConfig( tsc *commonpb.TimeSkippingConfig, ns namespace.Name, ) error { @@ -708,23 +708,29 @@ func (wh *WorkflowHandler) validateTimeSkippingConfig( return nil } // if this feature is not enabled, we don't allow setting any related config - if !wh.config.TimeSkippingEnabled(ns.String()) { - return serviceerror.NewUnimplementedf( - "The Time-Skipping feature is not enabled for namespace %s", - ns.String(), - ) + if !wh.config.WorkflowTimeSkippingEnabled(ns.String()) { + return errWorkflowTimeSkippingNotEnabled + } + if tsc.GetMaxSessionSkipCount() <= 0 { + defaultMaxSkipPerSession := wh.config.WorkflowTimeSkippingMaxSkipPerSession(ns.String()) + tsc.MaxSessionSkipCount = max(1, int32(defaultMaxSkipPerSession)) + } + + if ff := tsc.GetFastForwardConfig(); ff != nil { + if ff.GetDuration().AsDuration() <= 0 { + return serviceerror.NewInvalidArgument("Time skipping config invalid: fast_forward duration must be positive") + } + if strings.TrimSpace(ff.GetId()) == "" { + return errTimeSkippingFastForwardIDNotSet + } } if !tsc.GetEnabled() { - if tsc.GetFastForward() != nil { + if tsc.GetFastForwardConfig() != nil { return serviceerror.NewInvalidArgument("time_skipping_config: cannot set fast_forward when enabled is false") } return nil } - if ff := tsc.GetFastForward(); ff != nil && ff.AsDuration() < 0 { - return serviceerror.NewInvalidArgument("time_skipping_config: fast_forward must be positive") - } - return nil } @@ -2352,7 +2358,7 @@ func (wh *WorkflowHandler) SignalWithStartWorkflowExecution(ctx context.Context, } namespaceName := namespace.Name(request.GetNamespace()) - if err := wh.validateTimeSkippingConfig(request.GetTimeSkippingConfig(), namespaceName); err != nil { + if err := wh.validateAndPopulateTimeSkippingConfig(request.GetTimeSkippingConfig(), namespaceName); err != nil { return nil, err } @@ -2404,7 +2410,7 @@ func (wh *WorkflowHandler) ResetWorkflowExecution(ctx context.Context, request * for _, postOp := range request.GetPostResetOperations() { if updateOpts := postOp.GetUpdateWorkflowOptions(); updateOpts != nil { - if err := wh.validateTimeSkippingConfig( + if err := wh.validateAndPopulateTimeSkippingConfig( updateOpts.GetWorkflowExecutionOptions().GetTimeSkippingConfig(), namespace.Name(request.GetNamespace()), ); err != nil { @@ -5806,7 +5812,7 @@ func (wh *WorkflowHandler) StartBatchOperation( identity = op.ResetOperation.GetIdentity() for _, postOp := range op.ResetOperation.GetPostResetOperations() { if updateOpts := postOp.GetUpdateWorkflowOptions(); updateOpts != nil { - if err := wh.validateTimeSkippingConfig( + if err := wh.validateAndPopulateTimeSkippingConfig( updateOpts.GetWorkflowExecutionOptions().GetTimeSkippingConfig(), namespace.Name(request.GetNamespace()), ); err != nil { @@ -5817,7 +5823,7 @@ func (wh *WorkflowHandler) StartBatchOperation( case *workflowservice.StartBatchOperationRequest_UpdateWorkflowOptionsOperation: input.BatchType = enumspb.BATCH_OPERATION_TYPE_UPDATE_EXECUTION_OPTIONS identity = op.UpdateWorkflowOptionsOperation.GetIdentity() - if err := wh.validateTimeSkippingConfig( + if err := wh.validateAndPopulateTimeSkippingConfig( op.UpdateWorkflowOptionsOperation.GetWorkflowExecutionOptions().GetTimeSkippingConfig(), namespace.Name(request.GetNamespace()), ); err != nil { @@ -7012,7 +7018,7 @@ func (wh *WorkflowHandler) UpdateWorkflowExecutionOptions( if err := priorities.Validate(opts.GetPriority()); err != nil { return nil, err } - if err := wh.validateTimeSkippingConfig(opts.GetTimeSkippingConfig(), namespace.Name(request.GetNamespace())); err != nil { + if err := wh.validateAndPopulateTimeSkippingConfig(opts.GetTimeSkippingConfig(), namespace.Name(request.GetNamespace())); err != nil { return nil, err } @@ -7523,3 +7529,37 @@ func (wh *WorkflowHandler) UnpauseWorkflowExecution(ctx context.Context, request return &workflowservice.UnpauseWorkflowExecutionResponse{}, nil } + +func (wh *WorkflowHandler) PollWorkflowExecutionTimeSkipping(ctx context.Context, request *workflowservice.PollWorkflowExecutionTimeSkippingRequest) (*workflowservice.PollWorkflowExecutionTimeSkippingResponse, error) { + if request == nil { + return nil, errRequestNotSet + } + if err := validateExecution(request.GetWorkflowExecution()); err != nil { + return nil, err + } + nsID, err := wh.namespaceRegistry.GetNamespaceID(namespace.Name(request.GetNamespace())) + if err != nil { + return nil, err + } + + if !wh.config.WorkflowTimeSkippingEnabled(request.Namespace) { + return nil, errWorkflowTimeSkippingNotEnabled + } + if strings.TrimSpace(request.GetFastForwardId()) == "" { + return nil, errTimeSkippingFastForwardIDNotSet + } + + ctx, cancel := context.WithTimeout(ctx, frontend.DefaultLongPollTimeout) + defer cancel() + histResp, err := wh.historyClient.PollWorkflowExecutionTimeSkipping( + ctx, + &historyservice.PollWorkflowExecutionTimeSkippingRequest{ + NamespaceId: nsID.String(), + Request: request, + }, + ) + if err != nil { + return nil, err + } + return histResp.GetResponse(), nil +} diff --git a/service/frontend/workflow_handler_test.go b/service/frontend/workflow_handler_test.go index b8e7f02412e..3801c69d6f2 100644 --- a/service/frontend/workflow_handler_test.go +++ b/service/frontend/workflow_handler_test.go @@ -3686,27 +3686,147 @@ func (s *WorkflowHandlerSuite) TestValidateTimeSkippingConfig() { var invalidArgumentErr *serviceerror.InvalidArgument // nil config is valid - s.Require().NoError(wh.validateTimeSkippingConfig(nil, s.testNamespace)) + s.Require().NoError(wh.validateAndPopulateTimeSkippingConfig(nil, s.testNamespace)) // config with enabled=false but dynamic config disabled returns error - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) - s.Require().ErrorAs(wh.validateTimeSkippingConfig(&commonpb.TimeSkippingConfig{Enabled: false}, s.testNamespace), &unimplementedErr) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) + s.Require().ErrorAs(wh.validateAndPopulateTimeSkippingConfig(&commonpb.TimeSkippingConfig{Enabled: false}, s.testNamespace), &unimplementedErr) // config with enabled=true but dynamic config disabled returns error - s.Require().ErrorAs(wh.validateTimeSkippingConfig(&commonpb.TimeSkippingConfig{Enabled: true}, s.testNamespace), &unimplementedErr) + s.Require().ErrorAs(wh.validateAndPopulateTimeSkippingConfig(&commonpb.TimeSkippingConfig{Enabled: true}, s.testNamespace), &unimplementedErr) // config with enabled=false and dynamic config enabled is valid - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true) - s.Require().NoError(wh.validateTimeSkippingConfig(&commonpb.TimeSkippingConfig{Enabled: false}, s.testNamespace)) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true) + s.Require().NoError(wh.validateAndPopulateTimeSkippingConfig(&commonpb.TimeSkippingConfig{Enabled: false}, s.testNamespace)) // config with enabled=true and dynamic config enabled is valid - s.Require().NoError(wh.validateTimeSkippingConfig(&commonpb.TimeSkippingConfig{Enabled: true}, s.testNamespace)) + s.Require().NoError(wh.validateAndPopulateTimeSkippingConfig(&commonpb.TimeSkippingConfig{Enabled: true}, s.testNamespace)) - s.Require().ErrorAs(wh.validateTimeSkippingConfig(&commonpb.TimeSkippingConfig{ - Enabled: false, FastForward: durationpb.New(time.Second * 10)}, s.testNamespace), &invalidArgumentErr) + // fast_forward set while enabled=false is rejected + s.Require().ErrorAs(wh.validateAndPopulateTimeSkippingConfig(&commonpb.TimeSkippingConfig{ + Enabled: false, FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Second * 10), Id: "ff-id"}}, s.testNamespace), &invalidArgumentErr) - s.Require().ErrorAs(wh.validateTimeSkippingConfig(&commonpb.TimeSkippingConfig{ - Enabled: true, FastForward: durationpb.New(time.Second * -10)}, s.testNamespace), &invalidArgumentErr) + // negative fast_forward duration is rejected + s.Require().ErrorAs(wh.validateAndPopulateTimeSkippingConfig(&commonpb.TimeSkippingConfig{ + Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Second * -10), Id: "ff-id"}}, s.testNamespace), &invalidArgumentErr) + + // fast_forward set without a fast_forward_id is rejected + s.Require().ErrorAs(wh.validateAndPopulateTimeSkippingConfig(&commonpb.TimeSkippingConfig{ + Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Second * 10)}}, s.testNamespace), &invalidArgumentErr) + + // a blank (whitespace-only) fast_forward_id is rejected + s.Require().ErrorAs(wh.validateAndPopulateTimeSkippingConfig(&commonpb.TimeSkippingConfig{ + Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Second * 10), Id: " "}}, s.testNamespace), &invalidArgumentErr) + + // fast_forward with a valid fast_forward_id is accepted + s.Require().NoError(wh.validateAndPopulateTimeSkippingConfig(&commonpb.TimeSkippingConfig{ + Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Second * 10), Id: "ff-id"}}, s.testNamespace)) + + // MaxSkipPerSession is populated from dynamic config: a per-namespace override wins for that + // namespace, a constraint-less (per-cell) value is the fallback for other namespaces, and a + // value already set on the request is left untouched. + const otherNamespace = "other-namespace" + maxSkipClient := dc.StaticClient{ + dc.WorkflowTimeSkippingEnabled.Key(): true, + dc.WorkflowTimeSkippingMaxSkipPerSession.Key(): []dc.ConstrainedValue{ + {Value: 42}, // per-cell (constraint-less) value + {Constraints: dc.Constraints{Namespace: s.testNamespace.String()}, Value: 7}, // per-namespace override + }, + } + maxSkipWH := s.getWorkflowHandler(NewConfig(dc.NewCollection(maxSkipClient, log.NewNoopLogger()), numHistoryShards)) + + // namespace with a per-namespace override uses that value + tsc := &commonpb.TimeSkippingConfig{Enabled: true} + s.Require().NoError(maxSkipWH.validateAndPopulateTimeSkippingConfig(tsc, s.testNamespace)) + s.Require().Equal(int32(7), tsc.GetMaxSessionSkipCount()) + + // namespace without a per-namespace setting falls back to the per-cell value + tsc = &commonpb.TimeSkippingConfig{Enabled: true} + s.Require().NoError(maxSkipWH.validateAndPopulateTimeSkippingConfig(tsc, namespace.Name(otherNamespace))) + s.Require().Equal(int32(42), tsc.GetMaxSessionSkipCount()) + + // a value already on the request is preserved, not overwritten by dynamic config + tsc = &commonpb.TimeSkippingConfig{Enabled: true, MaxSessionSkipCount: 999} + s.Require().NoError(maxSkipWH.validateAndPopulateTimeSkippingConfig(tsc, s.testNamespace)) + s.Require().Equal(int32(999), tsc.GetMaxSessionSkipCount()) +} + +func (s *WorkflowHandlerSuite) TestPollWorkflowExecutionTimeSkipping() { + validExecution := &commonpb.WorkflowExecution{WorkflowId: "wf-id"} + newReq := func(fastForwardID string) *workflowservice.PollWorkflowExecutionTimeSkippingRequest { + return &workflowservice.PollWorkflowExecutionTimeSkippingRequest{ + Namespace: s.testNamespace.String(), + WorkflowExecution: validExecution, + FastForwardId: fastForwardID, + } + } + handler := func(timeSkippingEnabled bool) *WorkflowHandler { + config := s.newConfig() + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(timeSkippingEnabled) + return s.getWorkflowHandler(config) + } + + s.Run("nil request is rejected", func() { + _, err := handler(true).PollWorkflowExecutionTimeSkipping(context.Background(), nil) + s.ErrorIs(err, errRequestNotSet) + }) + + s.Run("invalid execution is rejected before namespace lookup", func() { + _, err := handler(true).PollWorkflowExecutionTimeSkipping(context.Background(), + &workflowservice.PollWorkflowExecutionTimeSkippingRequest{ + Namespace: s.testNamespace.String(), + WorkflowExecution: &commonpb.WorkflowExecution{}, // no workflow id + FastForwardId: "ff-id", + }) + s.ErrorIs(err, errWorkflowIDNotSet) + }) + + s.Run("disabled dynamic config is rejected", func() { + s.mockNamespaceCache.EXPECT().GetNamespaceID(s.testNamespace).Return(s.testNamespaceID, nil) + _, err := handler(false).PollWorkflowExecutionTimeSkipping(context.Background(), newReq("ff-id")) + s.ErrorIs(err, errWorkflowTimeSkippingNotEnabled) + }) + + s.Run("blank fast_forward_id is rejected", func() { + s.mockNamespaceCache.EXPECT().GetNamespaceID(s.testNamespace).Return(s.testNamespaceID, nil) + _, err := handler(true).PollWorkflowExecutionTimeSkipping(context.Background(), newReq(" ")) + s.ErrorIs(err, errTimeSkippingFastForwardIDNotSet) + }) + + s.Run("valid request forwards to history and returns its response", func() { + req := newReq("ff-id") + s.mockNamespaceCache.EXPECT().GetNamespaceID(s.testNamespace).Return(s.testNamespaceID, nil) + + want := &workflowservice.PollWorkflowExecutionTimeSkippingResponse{ + FastForwardPollingResult: enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED, + FastForwardInfo: &commonpb.TimeSkippingFastForwardInfo{ + FastForwardId: "ff-id", + HasCompleted: true, + }, + } + s.mockHistoryClient.EXPECT(). + PollWorkflowExecutionTimeSkipping(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, histReq *historyservice.PollWorkflowExecutionTimeSkippingRequest, _ ...grpc.CallOption) (*historyservice.PollWorkflowExecutionTimeSkippingResponse, error) { + // The frontend stamps the resolved namespace id and forwards the request verbatim. + s.Equal(s.testNamespaceID.String(), histReq.GetNamespaceId()) + s.Same(req, histReq.GetRequest()) + return &historyservice.PollWorkflowExecutionTimeSkippingResponse{Response: want}, nil + }) + + resp, err := handler(true).PollWorkflowExecutionTimeSkipping(context.Background(), req) + s.NoError(err) + s.Same(want, resp) + }) + + s.Run("history error is propagated", func() { + s.mockNamespaceCache.EXPECT().GetNamespaceID(s.testNamespace).Return(s.testNamespaceID, nil) + s.mockHistoryClient.EXPECT(). + PollWorkflowExecutionTimeSkipping(gomock.Any(), gomock.Any()). + Return(nil, serviceerror.NewUnavailable("history unavailable")) + _, err := handler(true).PollWorkflowExecutionTimeSkipping(context.Background(), newReq("ff-id")) + var unavailable *serviceerror.Unavailable + s.ErrorAs(err, &unavailable) + }) } // TestExecuteMultiOperation_TimeSkipping_DCDisabled verifies that when the DC gate is off, @@ -3714,7 +3834,7 @@ func (s *WorkflowHandlerSuite) TestValidateTimeSkippingConfig() { // as a MultiOperationExecution error with the per-operation InvalidArgument at index 0. func (s *WorkflowHandlerSuite) TestExecuteMultiOperation_TimeSkipping_DCDisabled() { config := s.newConfig() - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) wh := s.getWorkflowHandler(config) // Namespace lookup happens before operation validation. s.mockNamespaceCache.EXPECT().GetNamespaceID(namespace.Name(s.testNamespace.String())).Return(s.testNamespaceID, nil) @@ -3762,7 +3882,7 @@ func (s *WorkflowHandlerSuite) TestExecuteMultiOperation_TimeSkipping_DCDisabled // forwarded to the history client inside the StartWorkflow request. func (s *WorkflowHandlerSuite) TestExecuteMultiOperation_TimeSkipping_DCEnabled() { config := s.newConfig() - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true) wh := s.getWorkflowHandler(config) s.mockNamespaceCache.EXPECT().GetNamespaceID(namespace.Name(s.testNamespace.String())).Return(s.testNamespaceID, nil) s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil) @@ -3819,7 +3939,7 @@ func (s *WorkflowHandlerSuite) TestExecuteMultiOperation_TimeSkipping_DCEnabled( // a StartWorkflowExecution with a TimeSkippingConfig is rejected. func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_TimeSkipping_DCDisabled() { config := s.newConfig() - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) wh := s.getWorkflowHandler(config) s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil) @@ -3842,7 +3962,7 @@ func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_TimeSkipping_DCDisable // forwarded to the history client inside the StartWorkflow request. func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_TimeSkipping_DCEnabled() { config := s.newConfig() - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true) wh := s.getWorkflowHandler(config) s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil) s.mockNamespaceCache.EXPECT().GetNamespaceID(namespace.Name(s.testNamespace.String())).Return(s.testNamespaceID, nil) @@ -3867,7 +3987,7 @@ func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_TimeSkipping_DCEnabled // a SignalWithStartWorkflowExecution with a TimeSkippingConfig is rejected. func (s *WorkflowHandlerSuite) TestSignalWithStartWorkflowExecution_TimeSkipping_DCDisabled() { config := s.newConfig() - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) wh := s.getWorkflowHandler(config) s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil) @@ -3891,7 +4011,7 @@ func (s *WorkflowHandlerSuite) TestSignalWithStartWorkflowExecution_TimeSkipping // forwarded to the history client inside the request. func (s *WorkflowHandlerSuite) TestSignalWithStartWorkflowExecution_TimeSkipping_DCEnabled() { config := s.newConfig() - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true) wh := s.getWorkflowHandler(config) s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil) s.mockNamespaceCache.EXPECT().GetNamespaceID(namespace.Name(s.testNamespace.String())).Return(s.testNamespaceID, nil) @@ -3917,7 +4037,7 @@ func (s *WorkflowHandlerSuite) TestSignalWithStartWorkflowExecution_TimeSkipping // a ResetWorkflowExecution request with a TimeSkippingConfig inside PostResetOperations is rejected. func (s *WorkflowHandlerSuite) TestResetWorkflowExecution_TimeSkipping_DCDisabled() { config := s.newConfig() - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) wh := s.getWorkflowHandler(config) _, err := wh.ResetWorkflowExecution(context.Background(), &workflowservice.ResetWorkflowExecutionRequest{ @@ -3949,7 +4069,7 @@ func (s *WorkflowHandlerSuite) TestResetWorkflowExecution_TimeSkipping_DCDisable // is off, a batch reset with a TimeSkippingConfig inside PostResetOperations is rejected. func (s *WorkflowHandlerSuite) TestStartBatchOperation_ResetOperation_TimeSkipping_DCDisabled() { config := s.newConfig() - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) wh := s.getWorkflowHandler(config) namespaceID := namespace.ID(uuid.NewString()) s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes() @@ -3989,7 +4109,7 @@ func (s *WorkflowHandlerSuite) TestStartBatchOperation_ResetOperation_TimeSkippi // when the DC gate is off, a batch UpdateWorkflowOptions with a TimeSkippingConfig is rejected. func (s *WorkflowHandlerSuite) TestStartBatchOperation_UpdateWorkflowOptionsOperation_TimeSkipping_DCDisabled() { config := s.newConfig() - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) wh := s.getWorkflowHandler(config) namespaceID := namespace.ID(uuid.NewString()) s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes() @@ -5230,7 +5350,7 @@ func (s *WorkflowHandlerSuite) TestUpdateWorkflowExecutionOptions_Priority() { // off, UpdateWorkflowExecutionOptions rejects a request containing a TimeSkippingConfig. func (s *WorkflowHandlerSuite) TestUpdateWorkflowExecutionOptions_TimeSkipping_DCDisabled() { config := s.newConfig() - config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) + config.WorkflowTimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false) wh := s.getWorkflowHandler(config) _, err := wh.UpdateWorkflowExecutionOptions(context.Background(), &workflowservice.UpdateWorkflowExecutionOptionsRequest{ diff --git a/service/history/api/describeworkflow/api.go b/service/history/api/describeworkflow/api.go index 8e1d89d9c19..94cfaf60535 100644 --- a/service/history/api/describeworkflow/api.go +++ b/service/history/api/describeworkflow/api.go @@ -379,6 +379,12 @@ func Invoke( } result.PendingNexusOperations = append(result.PendingNexusOperations, hsmNexusOpInfos...) + if executionInfo.TimeSkippingInfo != nil { + result.WorkflowExtendedInfo.TimeSkippingInfo = workflow. + NewTimeSkippingInfoUtil(executionInfo.TimeSkippingInfo). + ToDescribeInfo(mutableState.Now()) + } + return result, nil } diff --git a/service/history/api/polltimeskipping/api.go b/service/history/api/polltimeskipping/api.go new file mode 100644 index 00000000000..03a5cdfe57c --- /dev/null +++ b/service/history/api/polltimeskipping/api.go @@ -0,0 +1,192 @@ +package polltimeskipping + +import ( + "context" + "time" + + commonpb "go.temporal.io/api/common/v1" + enumspb "go.temporal.io/api/enums/v1" + "go.temporal.io/api/serviceerror" + "go.temporal.io/api/workflowservice/v1" + "go.temporal.io/server/api/historyservice/v1" + "go.temporal.io/server/chasm" + "go.temporal.io/server/common/definition" + "go.temporal.io/server/common/locks" + "go.temporal.io/server/common/namespace" + "go.temporal.io/server/service/history/api" + historyi "go.temporal.io/server/service/history/interfaces" + "go.temporal.io/server/service/history/notification" + "go.temporal.io/server/service/history/workflow" +) + +// Reasons reported alongside FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED. They are part of the +// API response, so they describe the cause in caller terms rather than naming server internals. +const ( + failedReasonIDMismatch = "the requested fast_forward_id does not match the execution's current fast-forward" + failedReasonReset = "the fast-forward was reset" + failedReasonExecutionEnded = "the execution ended before the fast-forward completed" + failedReasonTimeSkippingDisabled = "time skipping was disabled before the fast-forward completed" +) + +func Invoke( + ctx context.Context, + req *historyservice.PollWorkflowExecutionTimeSkippingRequest, + shard historyi.ShardContext, + workflowConsistencyChecker api.WorkflowConsistencyChecker, + ffNotifier notification.TimeSkippingFastForwardNotifier, +) (*historyservice.PollWorkflowExecutionTimeSkippingResponse, error) { + if err := api.ValidateNamespaceUUID(namespace.ID(req.GetNamespaceId())); err != nil { + return nil, err + } + execution := req.GetRequest().GetWorkflowExecution() + requestedFFID := req.GetRequest().GetFastForwardId() + ns, err := shard.GetNamespaceRegistry().GetNamespaceByID(namespace.ID(req.GetNamespaceId())) + if err != nil { + return nil, err + } + + // step-1: Subscribe before reading current state so any fast-forward update persisted + // after our read still delivers a wake-up on the channel (no lost notification). + watchKey := notification.NewTimeSkippingNotificationKey( + req.GetNamespaceId(), execution.GetWorkflowId(), chasm.WorkflowArchetypeID) + subscriberID, channel, err := ffNotifier.Watch(watchKey) + if err != nil { + return nil, err + } + defer func() { _ = ffNotifier.Unwatch(watchKey, subscriberID) }() + + // step-2: an initial read to see we can short-circuit the polling + ffinfo, tsDisabled, wfClosed, err := readFastForwardInfo(ctx, req, workflowConsistencyChecker) + if err != nil { + return nil, err + } + if ffinfo == nil || ffinfo.GetFastForwardId() != requestedFFID { + return newFailedResponse(ffinfo, failedReasonIDMismatch), nil + } + if ffinfo.GetHasCompleted() { + return newResponse(ffinfo, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED, ""), nil + } + if wfClosed { + return newFailedResponse(ffinfo, failedReasonExecutionEnded), nil + } + if tsDisabled { + return newFailedResponse(ffinfo, failedReasonTimeSkippingDisabled), nil + } + + // step-3: Fast-forward is pending and matches the request: long-poll for a change. + softTimeout := shard.GetConfig().LongPollExpirationInterval(ns.Name().String()) + updatedFFInfo, result, failedReason, err := waitFastForwardNotification(ctx, shard.GetLifecycleContext(), channel, softTimeout, requestedFFID, ffinfo) + if err != nil { + return nil, err + } + return newResponse(updatedFFInfo, result, failedReason), nil +} + +// readFastForwardInfo acquires the workflow lease, reads the current fast-forward +// info, and releases the lease before returning, so the caller can long-poll +// without holding the lease. +func readFastForwardInfo( + ctx context.Context, + req *historyservice.PollWorkflowExecutionTimeSkippingRequest, + workflowConsistencyChecker api.WorkflowConsistencyChecker, +) (_ *commonpb.TimeSkippingFastForwardInfo, tsDisabled bool, closed bool, retError error) { + execution := req.GetRequest().GetWorkflowExecution() + workflowLease, err := workflowConsistencyChecker.GetWorkflowLease( + ctx, + nil, + definition.NewWorkflowKey(req.GetNamespaceId(), execution.GetWorkflowId(), execution.GetRunId()), + locks.PriorityLow, // testing api + ) + if err != nil { + return nil, false, false, err + } + defer func() { workflowLease.GetReleaseFn()(retError) }() + + ms := workflowLease.GetMutableState() + tsi := ms.GetExecutionInfo().GetTimeSkippingInfo() + ffinfo := workflow.NewTimeSkippingInfoUtil(tsi).ToFastForwardInfo() + tsDisabled = !tsi.GetConfig().GetEnabled() + // A closed run with no continuation (retry / cron / CaN set NewExecutionRunId) + // can never complete a pending fast-forward. + closed = !ms.IsWorkflowExecutionRunning() && ms.GetExecutionInfo().GetNewExecutionRunId() == "" + return ffinfo, tsDisabled, closed, nil +} + +// waitFastForwardNotification blocks until a fast-forward update for the polled +// execution arrives. The workflow lease is not hold in this step so as not to block the workflow. +func waitFastForwardNotification( + ctx context.Context, + shardLifecycleCtx context.Context, + channel <-chan *notification.TimeSkippingFastForwardNotification, + softTimeout time.Duration, + requestedFFID string, + pending *commonpb.TimeSkippingFastForwardInfo, +) (*commonpb.TimeSkippingFastForwardInfo, enumspb.FastForwardPollingResult, string, error) { + stCtx, stCancel := context.WithTimeout(ctx, softTimeout) + defer stCancel() + + for { + select { + // (1) A notification arrived. + case notif, ok := <-channel: + if !ok { + // The pub-sub notifier never closes a live subscription's channel; a closed + // channel means we were handed a dead one (e.g. a misconfigured noop notifier), + // which should never happen for a real poll. + return nil, enumspb.FAST_FORWARD_POLLING_RESULT_UNSPECIFIED, "", serviceerror.NewInternal("fast-forward notification channel closed unexpectedly") + } + ffinfo := workflow.NewTimeSkippingInfoUtil(notif.GetTimeSkippingInfo()).ToFastForwardInfo() + tsDisabled := !notif.GetTimeSkippingInfo().GetConfig().GetEnabled() + switch { + // should always verify ffid first + case ffinfo == nil || ffinfo.GetFastForwardId() != requestedFFID: + return ffinfo, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, failedReasonIDMismatch, nil + case !ffinfo.GetTargetTime().AsTime().Equal(pending.GetTargetTime().AsTime()): + return ffinfo, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, failedReasonReset, nil + case ffinfo.GetHasCompleted(): + return ffinfo, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED, "", nil + case notif.GetWorkflowExecutionCompleted(): + return ffinfo, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, failedReasonExecutionEnded, nil + case tsDisabled: + return ffinfo, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, failedReasonTimeSkippingDisabled, nil + default: + continue + } + // (2) long-poll budget elapsed / (4) caller's context ended. stCtx = WithTimeout(ctx, + // softTimeout); softTimeout is the server-side budget, configured shorter than the caller's + // RPC deadline (as in pollupdate's WaitLifecycleStage), so it normally fires first and we + // return a graceful poll-timeout. If instead the caller's context ended first (shorter + // deadline / client disconnect / shutdown), propagate that error rather than faking a timeout. + case <-stCtx.Done(): + if ctx.Err() != nil { + return nil, enumspb.FAST_FORWARD_POLLING_RESULT_UNSPECIFIED, "", ctx.Err() + } + return pending, enumspb.FAST_FORWARD_POLLING_RESULT_POLL_TIMEOUT, "", nil + // (3) The shard moved or closed. Release promptly instead of holding the subscriber slot + // until the soft timeout; the client's retry routes to the new shard owner. + case <-shardLifecycleCtx.Done(): + return pending, enumspb.FAST_FORWARD_POLLING_RESULT_POLL_TIMEOUT, "", nil + } + } +} + +func newFailedResponse( + ffinfo *commonpb.TimeSkippingFastForwardInfo, + failedReason string, +) *historyservice.PollWorkflowExecutionTimeSkippingResponse { + return newResponse(ffinfo, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, failedReason) +} + +func newResponse( + ffinfo *commonpb.TimeSkippingFastForwardInfo, + result enumspb.FastForwardPollingResult, + failedReason string, +) *historyservice.PollWorkflowExecutionTimeSkippingResponse { + return &historyservice.PollWorkflowExecutionTimeSkippingResponse{ + Response: &workflowservice.PollWorkflowExecutionTimeSkippingResponse{ + FastForwardInfo: ffinfo, + FastForwardPollingResult: result, + FailedReason: failedReason, + }, + } +} diff --git a/service/history/api/polltimeskipping/api_test.go b/service/history/api/polltimeskipping/api_test.go new file mode 100644 index 00000000000..e51408d28d0 --- /dev/null +++ b/service/history/api/polltimeskipping/api_test.go @@ -0,0 +1,414 @@ +package polltimeskipping + +import ( + "context" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + commonpb "go.temporal.io/api/common/v1" + enumspb "go.temporal.io/api/enums/v1" + "go.temporal.io/api/serviceerror" + "go.temporal.io/api/workflowservice/v1" + clockspb "go.temporal.io/server/api/clock/v1" + "go.temporal.io/server/api/historyservice/v1" + persistencespb "go.temporal.io/server/api/persistence/v1" + "go.temporal.io/server/common/definition" + "go.temporal.io/server/common/locks" + "go.temporal.io/server/common/namespace" + "go.temporal.io/server/service/history/api" + historyi "go.temporal.io/server/service/history/interfaces" + "go.temporal.io/server/service/history/notification" + "go.temporal.io/server/service/history/tests" + "go.uber.org/mock/gomock" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + testWorkflowID = "wf-id" + testFastForwardID = "ff-id" +) + +// testFastForwardTarget is the target every fixture below installs. waitFastForwardNotification +// compares the notification's target against the caller's, so the pending info and the notified +// info must agree on it or every notification reads as a replaced fast-forward. +var testFastForwardTarget = time.Unix(0, 0).Add(time.Hour) + +// mockConsistencyChecker returns a fixed lease (or error) from GetWorkflowLease; the +// other WorkflowConsistencyChecker methods are unused here and left to the embedded nil. +type mockConsistencyChecker struct { + api.WorkflowConsistencyChecker + lease api.WorkflowLease + err error +} + +func (m mockConsistencyChecker) GetWorkflowLease( + context.Context, *clockspb.VectorClock, definition.WorkflowKey, locks.Priority, +) (api.WorkflowLease, error) { + return m.lease, m.err +} + +type mockLease struct { + api.WorkflowLease + ms historyi.MutableState +} + +func (m mockLease) GetMutableState() historyi.MutableState { return m.ms } +func (m mockLease) GetReleaseFn() historyi.ReleaseWorkflowContextFunc { return func(error) {} } + +func fastForwardTSI(fastForwardID string, hasReached bool) *persistencespb.TimeSkippingInfo { + return &persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{ + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Id: fastForwardID}, + }, + FastForwardInfo: &persistencespb.FastForwardInfo{ + HasReached: hasReached, + TargetTime: timestamppb.New(testFastForwardTarget), + }, + } +} + +func tsiNotif(fastForwardID string, hasReached, workflowCompleted bool) *notification.TimeSkippingFastForwardNotification { + return ¬ification.TimeSkippingFastForwardNotification{ + TimeSkippingInfo: fastForwardTSI(fastForwardID, hasReached), + WorkflowExecutionCompleted: workflowCompleted, + } +} + +func disabledTSI(fastForwardID string) *persistencespb.TimeSkippingInfo { + tsi := fastForwardTSI(fastForwardID, false) + tsi.Config.Enabled = false + return tsi +} + +func mutableState(ctrl *gomock.Controller, tsi *persistencespb.TimeSkippingInfo, running bool, newRunID string) historyi.MutableState { + ms := historyi.NewMockMutableState(ctrl) + ms.EXPECT().GetExecutionInfo().Return(&persistencespb.WorkflowExecutionInfo{ + TimeSkippingInfo: tsi, + NewExecutionRunId: newRunID, + }).AnyTimes() + ms.EXPECT().IsWorkflowExecutionRunning().Return(running).AnyTimes() + return ms +} + +func shardContext(ctrl *gomock.Controller, softTimeout time.Duration) historyi.ShardContext { + nsReg := namespace.NewMockRegistry(ctrl) + nsReg.EXPECT().GetNamespaceByID(gomock.Any()).Return(tests.GlobalNamespaceEntry, nil).AnyTimes() + shard := historyi.NewMockShardContext(ctrl) + cfg := tests.NewDynamicConfig() + cfg.LongPollExpirationInterval = func(string) time.Duration { return softTimeout } + shard.EXPECT().GetConfig().Return(cfg).AnyTimes() + shard.EXPECT().GetNamespaceRegistry().Return(nsReg).AnyTimes() + shard.EXPECT().GetLifecycleContext().Return(context.Background()).AnyTimes() + return shard +} + +func pollReq(namespaceID, workflowID, fastForwardID string) *historyservice.PollWorkflowExecutionTimeSkippingRequest { + return &historyservice.PollWorkflowExecutionTimeSkippingRequest{ + NamespaceId: namespaceID, + Request: &workflowservice.PollWorkflowExecutionTimeSkippingRequest{ + WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: workflowID}, + FastForwardId: fastForwardID, + }, + } +} + +func TestInvoke(t *testing.T) { + t.Run("invalid namespace id is rejected", func(t *testing.T) { + _, err := Invoke(context.Background(), pollReq("", testWorkflowID, testFastForwardID), + nil, mockConsistencyChecker{}, notification.NoopTimeSkippingFastForwardNotifier) + var invalidArg *serviceerror.InvalidArgument + require.ErrorAs(t, err, &invalidArg) + }) + + t.Run("get workflow lease error is propagated", func(t *testing.T) { + ctrl := gomock.NewController(t) + checker := mockConsistencyChecker{err: serviceerror.NewNotFound("workflow not found")} + _, err := Invoke(context.Background(), pollReq(uuid.NewString(), testWorkflowID, testFastForwardID), + shardContext(ctrl, 20*time.Millisecond), checker, notification.NoopTimeSkippingFastForwardNotifier) + var notFound *serviceerror.NotFound + require.ErrorAs(t, err, ¬Found) + }) + + t.Run("no fast-forward info fails with an id mismatch", func(t *testing.T) { + ctrl := gomock.NewController(t) + checker := mockConsistencyChecker{lease: mockLease{ms: mutableState(ctrl, nil, true, "")}} + resp, err := Invoke(context.Background(), pollReq(uuid.NewString(), testWorkflowID, testFastForwardID), + shardContext(ctrl, 20*time.Millisecond), checker, notification.NoopTimeSkippingFastForwardNotifier) + require.NoError(t, err) + require.Equal(t, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, resp.GetResponse().GetFastForwardPollingResult()) + require.Equal(t, failedReasonIDMismatch, resp.GetResponse().GetFailedReason()) + }) + + t.Run("fast-forward id mismatch fails with an id mismatch", func(t *testing.T) { + ctrl := gomock.NewController(t) + checker := mockConsistencyChecker{lease: mockLease{ms: mutableState(ctrl, fastForwardTSI("other-id", false), true, "")}} + resp, err := Invoke(context.Background(), pollReq(uuid.NewString(), testWorkflowID, testFastForwardID), + shardContext(ctrl, 20*time.Millisecond), checker, notification.NoopTimeSkippingFastForwardNotifier) + require.NoError(t, err) + require.Equal(t, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, resp.GetResponse().GetFastForwardPollingResult()) + require.Equal(t, failedReasonIDMismatch, resp.GetResponse().GetFailedReason()) + }) + + t.Run("already completed returns completed", func(t *testing.T) { + ctrl := gomock.NewController(t) + checker := mockConsistencyChecker{lease: mockLease{ms: mutableState(ctrl, fastForwardTSI(testFastForwardID, true), true, "")}} + resp, err := Invoke(context.Background(), pollReq(uuid.NewString(), testWorkflowID, testFastForwardID), + shardContext(ctrl, 20*time.Millisecond), checker, notification.NoopTimeSkippingFastForwardNotifier) + require.NoError(t, err) + require.Equal(t, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED, resp.GetResponse().GetFastForwardPollingResult()) + require.True(t, resp.GetResponse().GetFastForwardInfo().GetHasCompleted()) + require.Equal(t, testFastForwardID, resp.GetResponse().GetFastForwardInfo().GetFastForwardId()) + }) + + t.Run("run closed before completion fails because the execution ended", func(t *testing.T) { + ctrl := gomock.NewController(t) + // pending fast-forward, not running, no successor run => can never complete. + checker := mockConsistencyChecker{lease: mockLease{ms: mutableState(ctrl, fastForwardTSI(testFastForwardID, false), false, "")}} + resp, err := Invoke(context.Background(), pollReq(uuid.NewString(), testWorkflowID, testFastForwardID), + shardContext(ctrl, 20*time.Millisecond), checker, notification.NoopTimeSkippingFastForwardNotifier) + require.NoError(t, err) + require.Equal(t, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, resp.GetResponse().GetFastForwardPollingResult()) + require.Equal(t, failedReasonExecutionEnded, resp.GetResponse().GetFailedReason()) + }) + + t.Run("time skipping disabled before completion fails with the disabled reason", func(t *testing.T) { + ctrl := gomock.NewController(t) + // matching pending fast-forward, but time skipping is disabled => can never complete. + checker := mockConsistencyChecker{lease: mockLease{ms: mutableState(ctrl, disabledTSI(testFastForwardID), true, "")}} + resp, err := Invoke(context.Background(), pollReq(uuid.NewString(), testWorkflowID, testFastForwardID), + shardContext(ctrl, 20*time.Millisecond), checker, notification.NoopTimeSkippingFastForwardNotifier) + require.NoError(t, err) + require.Equal(t, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, resp.GetResponse().GetFastForwardPollingResult()) + require.Equal(t, failedReasonTimeSkippingDisabled, resp.GetResponse().GetFailedReason()) + }) + + // step-2's checks are ordered, and the states below satisfy more than one of them at once, + // so these pin the precedence and must mirror waitFastForwardNotification's switch order. + t.Run("step-2 mismatched id wins over completion and closed run", func(t *testing.T) { + ctrl := gomock.NewController(t) + checker := mockConsistencyChecker{lease: mockLease{ms: mutableState(ctrl, fastForwardTSI("other-id", true), false, "")}} + resp, err := Invoke(context.Background(), pollReq(uuid.NewString(), testWorkflowID, testFastForwardID), + shardContext(ctrl, 20*time.Millisecond), checker, notification.NoopTimeSkippingFastForwardNotifier) + require.NoError(t, err) + require.Equal(t, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, resp.GetResponse().GetFastForwardPollingResult()) + require.Equal(t, failedReasonIDMismatch, resp.GetResponse().GetFailedReason()) + }) + + t.Run("step-2 completion wins over closed run", func(t *testing.T) { + ctrl := gomock.NewController(t) + checker := mockConsistencyChecker{lease: mockLease{ms: mutableState(ctrl, fastForwardTSI(testFastForwardID, true), false, "")}} + resp, err := Invoke(context.Background(), pollReq(uuid.NewString(), testWorkflowID, testFastForwardID), + shardContext(ctrl, 20*time.Millisecond), checker, notification.NoopTimeSkippingFastForwardNotifier) + require.NoError(t, err) + require.Equal(t, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED, resp.GetResponse().GetFastForwardPollingResult()) + require.True(t, resp.GetResponse().GetFastForwardInfo().GetHasCompleted()) + }) + + t.Run("step-2 closed run wins over time skipping disabled", func(t *testing.T) { + ctrl := gomock.NewController(t) + checker := mockConsistencyChecker{lease: mockLease{ms: mutableState(ctrl, disabledTSI(testFastForwardID), false, "")}} + resp, err := Invoke(context.Background(), pollReq(uuid.NewString(), testWorkflowID, testFastForwardID), + shardContext(ctrl, 20*time.Millisecond), checker, notification.NoopTimeSkippingFastForwardNotifier) + require.NoError(t, err) + require.Equal(t, enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, resp.GetResponse().GetFastForwardPollingResult()) + require.Equal(t, failedReasonExecutionEnded, resp.GetResponse().GetFailedReason()) + }) + + t.Run("closed run with successor is not workflow-end and long-polls until timeout", func(t *testing.T) { + ctrl := gomock.NewController(t) + // not running, but NewExecutionRunId set (retry/cron/CaN) => not "closed"; polls and times out. + checker := mockConsistencyChecker{lease: mockLease{ms: mutableState(ctrl, fastForwardTSI(testFastForwardID, false), false, "next-run")}} + ffNotifier := notification.NewTimeSkippingFastForwardNotifier() + resp, err := Invoke(context.Background(), pollReq(uuid.NewString(), testWorkflowID, testFastForwardID), + shardContext(ctrl, 20*time.Millisecond), checker, ffNotifier) + require.NoError(t, err) + require.Equal(t, enumspb.FAST_FORWARD_POLLING_RESULT_POLL_TIMEOUT, resp.GetResponse().GetFastForwardPollingResult()) + }) + + t.Run("pending fast-forward with no notification times out and returns the pending info", func(t *testing.T) { + ctrl := gomock.NewController(t) + checker := mockConsistencyChecker{lease: mockLease{ms: mutableState(ctrl, fastForwardTSI(testFastForwardID, false), true, "")}} + // Real notifier: never notified, so the wait blocks until the soft timeout. + ffNotifier := notification.NewTimeSkippingFastForwardNotifier() + resp, err := Invoke(context.Background(), pollReq(uuid.NewString(), testWorkflowID, testFastForwardID), + shardContext(ctrl, 20*time.Millisecond), checker, ffNotifier) + require.NoError(t, err) + require.Equal(t, enumspb.FAST_FORWARD_POLLING_RESULT_POLL_TIMEOUT, resp.GetResponse().GetFastForwardPollingResult()) + require.Equal(t, testFastForwardID, resp.GetResponse().GetFastForwardInfo().GetFastForwardId()) + }) +} + +func TestWaitFastForwardNotification(t *testing.T) { + pending := &commonpb.TimeSkippingFastForwardInfo{ + FastForwardId: testFastForwardID, + TargetTime: timestamppb.New(testFastForwardTarget), + } + completed := enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED + failed := enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED + pollTimeout := enumspb.FAST_FORWARD_POLLING_RESULT_POLL_TIMEOUT + + t.Run("completion notification returns completed", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification, 1) + ch <- tsiNotif(testFastForwardID, true, false) + ffinfo, result, reason, err := waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, completed, result) + require.Empty(t, reason, "a successful outcome must not carry a failure reason") + require.True(t, ffinfo.GetHasCompleted()) + }) + + t.Run("different fast-forward id fails with an id mismatch", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification, 1) + ch <- tsiNotif("new-id", false, false) + ffinfo, result, reason, err := waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, failed, result) + require.Equal(t, failedReasonIDMismatch, reason) + require.Equal(t, "new-id", ffinfo.GetFastForwardId()) + }) + + // Reusing an id does not make it the same fast-forward: a replaced target must be reported + // rather than silently waited on. + t.Run("same id with a different target fails as replaced", func(t *testing.T) { + replaced := fastForwardTSI(testFastForwardID, false) + replaced.FastForwardInfo.TargetTime = timestamppb.New(testFastForwardTarget.Add(time.Hour)) + ch := make(chan *notification.TimeSkippingFastForwardNotification, 1) + ch <- ¬ification.TimeSkippingFastForwardNotification{TimeSkippingInfo: replaced} + ffinfo, result, reason, err := waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, failed, result) + require.Equal(t, failedReasonReset, reason) + require.Equal(t, testFastForwardID, ffinfo.GetFastForwardId()) + }) + + // Completion preserves the target, so the target check must not swallow the caller's own + // completion — the arm ordering is what guarantees that. + t.Run("completion with the caller's target still returns completed", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification, 1) + ch <- tsiNotif(testFastForwardID, true, false) + ffinfo, result, reason, err := waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, completed, result) + require.Empty(t, reason, "a successful outcome must not carry a failure reason") + require.True(t, ffinfo.GetHasCompleted()) + }) + + t.Run("closed run fails because the execution ended", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification, 1) + ch <- tsiNotif(testFastForwardID, false, true) + _, result, reason, err := waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, failed, result) + require.Equal(t, failedReasonExecutionEnded, reason) + }) + + t.Run("time skipping disabled fails with the disabled reason", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification, 1) + ch <- ¬ification.TimeSkippingFastForwardNotification{TimeSkippingInfo: disabledTSI(testFastForwardID)} + _, result, reason, err := waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, failed, result) + require.Equal(t, failedReasonTimeSkippingDisabled, reason) + }) + + // The switch arms are mutually reachable, so these pin the precedence rather than the + // individual outcomes: each notification below satisfies more than one arm. + t.Run("mismatched id wins over both completions", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification, 1) + ch <- tsiNotif("new-id", true, true) + ffinfo, result, reason, err := waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, failed, result) + require.Equal(t, failedReasonIDMismatch, reason) + require.Equal(t, "new-id", ffinfo.GetFastForwardId()) + }) + + t.Run("fast-forward completion wins over workflow completion", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification, 1) + ch <- tsiNotif(testFastForwardID, true, true) + ffinfo, result, reason, err := waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, completed, result) + require.Empty(t, reason, "a successful outcome must not carry a failure reason") + require.True(t, ffinfo.GetHasCompleted()) + }) + + t.Run("workflow completion wins over time skipping disabled", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification, 1) + ch <- ¬ification.TimeSkippingFastForwardNotification{ + TimeSkippingInfo: disabledTSI(testFastForwardID), + WorkflowExecutionCompleted: true, + } + _, result, reason, err := waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, failed, result) + require.Equal(t, failedReasonExecutionEnded, reason) + }) + + t.Run("no-op wake keeps waiting until a meaningful change", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification, 2) + // same id, not completed, not closed => no meaningful change, must keep waiting. + ch <- tsiNotif(testFastForwardID, false, false) + ch <- tsiNotif(testFastForwardID, true, false) + _, result, reason, err := waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, completed, result) + require.Empty(t, reason) + }) + + // Delivery permits a nil payload, so this must not depend on the switch order to stay safe. + t.Run("nil notification fails with an id mismatch without panicking", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification, 1) + ch <- nil + var result enumspb.FastForwardPollingResult + var reason string + var err error + require.NotPanics(t, func() { + _, result, reason, err = waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + }) + require.NoError(t, err) + require.Equal(t, failed, result) + require.Equal(t, failedReasonIDMismatch, reason) + }) + + t.Run("closed channel returns an internal error", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification) + close(ch) + _, _, _, err := waitFastForwardNotification(context.Background(), context.Background(), ch, time.Minute, testFastForwardID, pending) + var internalErr *serviceerror.Internal + require.ErrorAs(t, err, &internalErr) + }) + + t.Run("shard move wakes the poll before the soft timeout", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification) + shardCtx, cancelShard := context.WithCancel(context.Background()) + cancelShard() // shard already moved/closed + // Long soft timeout: if the shard-lifecycle case were missing, this would block for a minute. + ffinfo, result, reason, err := waitFastForwardNotification(context.Background(), shardCtx, ch, time.Minute, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, pollTimeout, result) + require.Empty(t, reason) + require.Same(t, pending, ffinfo) + }) + + t.Run("timeout returns the pending info unchanged", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification) + ffinfo, result, reason, err := waitFastForwardNotification(context.Background(), context.Background(), ch, 20*time.Millisecond, testFastForwardID, pending) + require.NoError(t, err) + require.Equal(t, pollTimeout, result) + require.Empty(t, reason) + require.Same(t, pending, ffinfo) + }) + + t.Run("caller context cancellation is propagated as an error, not a poll-timeout", func(t *testing.T) { + ch := make(chan *notification.TimeSkippingFastForwardNotification) + callerCtx, cancelCaller := context.WithCancel(context.Background()) + cancelCaller() // client disconnected / deadline exceeded + // Long soft timeout so the only thing that fires is the caller's cancelled context. + _, _, _, err := waitFastForwardNotification(callerCtx, context.Background(), ch, time.Minute, testFastForwardID, pending) + require.ErrorIs(t, err, context.Canceled) + }) +} diff --git a/service/history/api/updateworkflowoptions/api.go b/service/history/api/updateworkflowoptions/api.go index 4a720c6f6a9..422ce5664dd 100644 --- a/service/history/api/updateworkflowoptions/api.go +++ b/service/history/api/updateworkflowoptions/api.go @@ -274,7 +274,7 @@ func mergeWorkflowExecutionOptions( var fastForwardSet bool if _, ok := updateFields["timeSkippingConfig"]; ok { mergeInto.TimeSkippingConfig = mergeFrom.GetTimeSkippingConfig() - if mergeFrom.GetTimeSkippingConfig().GetFastForward() != nil { + if mergeFrom.GetTimeSkippingConfig().GetFastForwardConfig() != nil { fastForwardSet = true } } diff --git a/service/history/api/updateworkflowoptions/api_test.go b/service/history/api/updateworkflowoptions/api_test.go index 4b34759c4a1..3b545ae7f8c 100644 --- a/service/history/api/updateworkflowoptions/api_test.go +++ b/service/history/api/updateworkflowoptions/api_test.go @@ -254,8 +254,8 @@ func TestMergeOptions_TimeSkippingConfig(t *testing.T) { tscMask := &fieldmaskpb.FieldMask{Paths: []string{"time_skipping_config"}} cfgA := &commonpb.TimeSkippingConfig{Enabled: true} cfgB := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour)}, } cfgC := &commonpb.TimeSkippingConfig{Enabled: false} @@ -505,46 +505,46 @@ func TestMergeAndApply(t *testing.T) { name: "basic: fast-forward from nil", initialConfig: nil, updateOptions: &workflowpb.WorkflowExecutionOptions{ - TimeSkippingConfig: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: oneHour}, + TimeSkippingConfig: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: oneHour}}, }, updateMask: tscMask, expectChanges: true, expectVersioningOverride: nil, unsetVersion: true, - expectTSCInEvent: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: oneHour}, + expectTSCInEvent: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: oneHour}}, expectTSCUpdated: true, - resultTSC: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: oneHour}, + resultTSC: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: oneHour}}, needCheckResultTSC: true, }, { name: "reapply: TSC updated with same fast-forward", - initialConfig: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: oneHour}, + initialConfig: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: oneHour}}, updateOptions: &workflowpb.WorkflowExecutionOptions{ - TimeSkippingConfig: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: oneHour}, + TimeSkippingConfig: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: oneHour}}, }, updateMask: tscMask, expectVersioningOverride: nil, unsetVersion: true, expectChanges: true, - expectTSCInEvent: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: oneHour}, + expectTSCInEvent: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: oneHour}}, expectTSCUpdated: true, - resultTSC: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: oneHour}, + resultTSC: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: oneHour}}, needCheckResultTSC: true, }, { name: "TSC no change: version update with fast-forward untouched", - initialConfig: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: oneHour}, + initialConfig: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: oneHour}}, updateOptions: &workflowpb.WorkflowExecutionOptions{VersioningOverride: newOverride}, updateMask: versioningMask, expectVersioningOverride: newOverride, unsetVersion: false, // as we always put the merged TSC into the event, even if it is the same as the initial config expectChanges: true, - expectTSCInEvent: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: oneHour}, + expectTSCInEvent: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: oneHour}}, expectTSCUpdated: false, needCheckResultTSC: true, - resultTSC: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: oneHour}, + resultTSC: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: oneHour}}, }, { name: "TSC no change: TSC without fast-forward are updated with same value", @@ -557,7 +557,7 @@ func TestMergeAndApply(t *testing.T) { }, { name: "nil allowed: nil with mask clears the TSC", - initialConfig: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: oneHour}, + initialConfig: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: oneHour}}, updateOptions: &workflowpb.WorkflowExecutionOptions{VersioningOverride: newOverride}, updateMask: &fieldmaskpb.FieldMask{Paths: []string{"time_skipping_config", "versioning_override"}}, expectChanges: true, diff --git a/service/history/handler.go b/service/history/handler.go index cd91d60af0f..10fad968cc5 100644 --- a/service/history/handler.go +++ b/service/history/handler.go @@ -1856,6 +1856,26 @@ func (h *Handler) PollWorkflowExecutionUpdate( return engine.PollWorkflowExecutionUpdate(ctx, request) } +func (h *Handler) PollWorkflowExecutionTimeSkipping( + ctx context.Context, + request *historyservice.PollWorkflowExecutionTimeSkippingRequest, +) (*historyservice.PollWorkflowExecutionTimeSkippingResponse, error) { + shardContext, err := h.controller.GetShardByNamespaceWorkflow( + namespace.ID(request.GetNamespaceId()), + request.GetRequest().GetWorkflowExecution().GetWorkflowId(), + ) + if err != nil { + return nil, h.convertError(err) + } + + engine, err := shardContext.GetEngine(ctx) + if err != nil { + return nil, h.convertError(err) + } + + return engine.PollWorkflowExecutionTimeSkipping(ctx, request) +} + func (h *Handler) StreamWorkflowReplicationMessages( server historyservice.HistoryService_StreamWorkflowReplicationMessagesServer, ) (retErr error) { diff --git a/service/history/history_engine.go b/service/history/history_engine.go index e31b591bd34..9608177bfaa 100644 --- a/service/history/history_engine.go +++ b/service/history/history_engine.go @@ -52,6 +52,7 @@ import ( "go.temporal.io/server/service/history/api/multioperation" "go.temporal.io/server/service/history/api/pauseactivity" "go.temporal.io/server/service/history/api/pauseworkflow" + "go.temporal.io/server/service/history/api/polltimeskipping" "go.temporal.io/server/service/history/api/pollupdate" "go.temporal.io/server/service/history/api/queryworkflow" "go.temporal.io/server/service/history/api/reapplyevents" @@ -92,6 +93,7 @@ import ( "go.temporal.io/server/service/history/hsm" historyi "go.temporal.io/server/service/history/interfaces" "go.temporal.io/server/service/history/ndc" + "go.temporal.io/server/service/history/notification" "go.temporal.io/server/service/history/queues" "go.temporal.io/server/service/history/replication" "go.temporal.io/server/service/history/tasks" @@ -117,6 +119,7 @@ type ( nDCHSMStateReplicator ndc.HSMStateReplicator replicationProcessorMgr replication.TaskProcessor eventNotifier events.Notifier + fastForwardNotifier notification.TimeSkippingFastForwardNotifier tokenSerializer *tasktoken.Serializer metricsHandler metrics.Handler logger log.Logger @@ -214,6 +217,7 @@ func NewEngineWithShardContext( throttledLogger: log.With(shard.GetThrottledLogger(), tag.ComponentHistoryEngine), metricsHandler: shard.GetMetricsHandler(), eventNotifier: eventNotifier, + fastForwardNotifier: notification.NewTimeSkippingFastForwardNotifier(), config: config, sdkClientFactory: sdkClientFactory, matchingClient: matchingClient, @@ -681,6 +685,13 @@ func (e *historyEngineImpl) PollWorkflowExecutionUpdate( return pollupdate.Invoke(ctx, req, e.shardContext, e.workflowConsistencyChecker) } +func (e *historyEngineImpl) PollWorkflowExecutionTimeSkipping( + ctx context.Context, + req *historyservice.PollWorkflowExecutionTimeSkippingRequest, +) (*historyservice.PollWorkflowExecutionTimeSkippingResponse, error) { + return polltimeskipping.Invoke(ctx, req, e.shardContext, e.workflowConsistencyChecker, e.fastForwardNotifier) +} + // RemoveSignalMutableState remove the signal request id in signal_requested for deduplicate func (e *historyEngineImpl) RemoveSignalMutableState( ctx context.Context, @@ -878,6 +889,22 @@ func (e *historyEngineImpl) NotifyNewHistoryEvent( e.eventNotifier.NotifyNewHistoryEvent(notification) } +func (e *historyEngineImpl) NotifyFastForwardUpdate( + key notification.TimeSkippingNotificationKey, + fastforwardNotification *notification.TimeSkippingFastForwardNotification, +) { + if e.fastForwardNotifier == nil { + // Always set by NewEngineWithShardContext; a nil here means a hand-built engine. + // Fast-forward notification is best-effort (waiters re-poll on timeout), so log and + // skip rather than panic. + e.logger.Warn("fastForwardNotifier is not configured; skipping fast-forward notification", + tag.WorkflowNamespaceID(key.NamespaceID), + tag.WorkflowID(key.WorkflowID)) + return + } + e.fastForwardNotifier.Notify(key, fastforwardNotification) +} + func (e *historyEngineImpl) NotifyChasmExecution(executionKey chasm.ExecutionKey, componentRef []byte) { if e.chasmEngine != nil { e.chasmEngine.NotifyExecution(executionKey) diff --git a/service/history/history_engine2_test.go b/service/history/history_engine2_test.go index bd7c96cf6cd..0fbcd0b7507 100644 --- a/service/history/history_engine2_test.go +++ b/service/history/history_engine2_test.go @@ -57,6 +57,7 @@ import ( "go.temporal.io/server/service/history/hsm" historyi "go.temporal.io/server/service/history/interfaces" "go.temporal.io/server/service/history/ndc" + "go.temporal.io/server/service/history/notification" "go.temporal.io/server/service/history/queues" "go.temporal.io/server/service/history/shard" "go.temporal.io/server/service/history/tasks" @@ -192,17 +193,18 @@ func (s *engine2Suite) SetupTest() { s.mockWorkflowStateReplicator = ndc.NewMockWorkflowStateReplicator(s.controller) h := &historyEngineImpl{ - currentClusterName: s.mockShard.GetClusterMetadata().GetCurrentClusterName(), - shardContext: s.mockShard, - clusterMetadata: s.mockClusterMetadata, - executionManager: s.mockExecutionMgr, - logger: s.logger, - throttledLogger: s.logger, - metricsHandler: metrics.NoopMetricsHandler, - tokenSerializer: tasktoken.NewSerializer(), - config: s.config, - timeSource: s.mockShard.GetTimeSource(), - eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + currentClusterName: s.mockShard.GetClusterMetadata().GetCurrentClusterName(), + shardContext: s.mockShard, + clusterMetadata: s.mockClusterMetadata, + executionManager: s.mockExecutionMgr, + logger: s.logger, + throttledLogger: s.logger, + metricsHandler: metrics.NoopMetricsHandler, + tokenSerializer: tasktoken.NewSerializer(), + config: s.config, + timeSource: s.mockShard.GetTimeSource(), + eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + fastForwardNotifier: notification.NoopTimeSkippingFastForwardNotifier, queueProcessors: map[tasks.Category]queues.Queue{ s.mockArchivalProcessor.Category(): s.mockArchivalProcessor, s.mockTxProcessor.Category(): s.mockTxProcessor, diff --git a/service/history/history_engine3_eventsv2_test.go b/service/history/history_engine3_eventsv2_test.go index 191fdceb2b7..e360539df45 100644 --- a/service/history/history_engine3_eventsv2_test.go +++ b/service/history/history_engine3_eventsv2_test.go @@ -33,6 +33,7 @@ import ( "go.temporal.io/server/service/history/configs" "go.temporal.io/server/service/history/events" "go.temporal.io/server/service/history/hsm" + "go.temporal.io/server/service/history/notification" "go.temporal.io/server/service/history/queues" "go.temporal.io/server/service/history/shard" "go.temporal.io/server/service/history/tasks" @@ -132,17 +133,18 @@ func (s *engine3Suite) SetupTest() { s.logger = s.mockShard.GetLogger() h := &historyEngineImpl{ - currentClusterName: s.mockShard.GetClusterMetadata().GetCurrentClusterName(), - shardContext: s.mockShard, - clusterMetadata: s.mockClusterMetadata, - executionManager: s.mockExecutionMgr, - logger: s.logger, - throttledLogger: s.logger, - metricsHandler: metrics.NoopMetricsHandler, - tokenSerializer: tasktoken.NewSerializer(), - config: s.config, - timeSource: s.mockShard.GetTimeSource(), - eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + currentClusterName: s.mockShard.GetClusterMetadata().GetCurrentClusterName(), + shardContext: s.mockShard, + clusterMetadata: s.mockClusterMetadata, + executionManager: s.mockExecutionMgr, + logger: s.logger, + throttledLogger: s.logger, + metricsHandler: metrics.NoopMetricsHandler, + tokenSerializer: tasktoken.NewSerializer(), + config: s.config, + timeSource: s.mockShard.GetTimeSource(), + eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + fastForwardNotifier: notification.NoopTimeSkippingFastForwardNotifier, queueProcessors: map[tasks.Category]queues.Queue{ s.mockTxProcessor.Category(): s.mockTxProcessor, s.mockTimerProcessor.Category(): s.mockTimerProcessor, diff --git a/service/history/history_engine_test.go b/service/history/history_engine_test.go index 3281c594e0f..e05c72f9969 100644 --- a/service/history/history_engine_test.go +++ b/service/history/history_engine_test.go @@ -65,6 +65,7 @@ import ( "go.temporal.io/server/service/history/hsm" historyi "go.temporal.io/server/service/history/interfaces" "go.temporal.io/server/service/history/ndc" + "go.temporal.io/server/service/history/notification" "go.temporal.io/server/service/history/queues" "go.temporal.io/server/service/history/shard" "go.temporal.io/server/service/history/tasks" @@ -208,15 +209,16 @@ func (s *engineSuite) SetupTest() { ) h := &historyEngineImpl{ - currentClusterName: s.mockShard.GetClusterMetadata().GetCurrentClusterName(), - shardContext: s.mockShard, - clusterMetadata: s.mockClusterMetadata, - executionManager: s.mockExecutionMgr, - logger: s.mockShard.GetLogger(), - metricsHandler: s.mockShard.GetMetricsHandler(), - tokenSerializer: tasktoken.NewSerializer(), - eventNotifier: eventNotifier, - config: s.config, + currentClusterName: s.mockShard.GetClusterMetadata().GetCurrentClusterName(), + shardContext: s.mockShard, + clusterMetadata: s.mockClusterMetadata, + executionManager: s.mockExecutionMgr, + logger: s.mockShard.GetLogger(), + metricsHandler: s.mockShard.GetMetricsHandler(), + tokenSerializer: tasktoken.NewSerializer(), + eventNotifier: eventNotifier, + fastForwardNotifier: notification.NoopTimeSkippingFastForwardNotifier, + config: s.config, queueProcessors: map[tasks.Category]queues.Queue{ s.mockTxProcessor.Category(): s.mockTxProcessor, s.mockTimerProcessor.Category(): s.mockTimerProcessor, diff --git a/service/history/interfaces/engine.go b/service/history/interfaces/engine.go index a7dd9055d56..1c03f0e583f 100644 --- a/service/history/interfaces/engine.go +++ b/service/history/interfaces/engine.go @@ -19,6 +19,7 @@ import ( "go.temporal.io/server/common/namespace" "go.temporal.io/server/service/history/events" "go.temporal.io/server/service/history/hsm" + "go.temporal.io/server/service/history/notification" "go.temporal.io/server/service/history/tasks" ) @@ -85,6 +86,7 @@ type ( GetReplicationStatus(ctx context.Context, request *historyservice.GetReplicationStatusRequest) (*historyservice.ShardReplicationStatus, error) UpdateWorkflowExecution(ctx context.Context, request *historyservice.UpdateWorkflowExecutionRequest) (*historyservice.UpdateWorkflowExecutionResponse, error) PollWorkflowExecutionUpdate(ctx context.Context, request *historyservice.PollWorkflowExecutionUpdateRequest) (*historyservice.PollWorkflowExecutionUpdateResponse, error) + PollWorkflowExecutionTimeSkipping(ctx context.Context, request *historyservice.PollWorkflowExecutionTimeSkippingRequest) (*historyservice.PollWorkflowExecutionTimeSkippingResponse, error) GetWorkflowExecutionHistory(ctx context.Context, request *historyservice.GetWorkflowExecutionHistoryRequest) (*historyservice.GetWorkflowExecutionHistoryResponseWithRaw, error) GetWorkflowExecutionHistoryReverse(ctx context.Context, request *historyservice.GetWorkflowExecutionHistoryReverseRequest) (*historyservice.GetWorkflowExecutionHistoryReverseResponse, error) GetWorkflowExecutionRawHistory(ctx context.Context, request *historyservice.GetWorkflowExecutionRawHistoryRequest) (*historyservice.GetWorkflowExecutionRawHistoryResponse, error) @@ -102,6 +104,7 @@ type ( NotifyNewHistoryEvent(event *events.Notification) NotifyNewTasks(tasks map[tasks.Category][]tasks.Task) NotifyChasmExecution(executionKey chasm.ExecutionKey, componentRef []byte) + NotifyFastForwardUpdate(key notification.TimeSkippingNotificationKey, fastforwardNotification *notification.TimeSkippingFastForwardNotification) // TODO(bergundy): This Environment should be host level once shard level workflow cache is deprecated. StateMachineEnvironment(operationTag metrics.Tag) hsm.Environment diff --git a/service/history/interfaces/engine_mock.go b/service/history/interfaces/engine_mock.go index f976c352bf0..b2ca488f358 100644 --- a/service/history/interfaces/engine_mock.go +++ b/service/history/interfaces/engine_mock.go @@ -27,6 +27,7 @@ import ( namespace "go.temporal.io/server/common/namespace" events "go.temporal.io/server/service/history/events" hsm "go.temporal.io/server/service/history/hsm" + notification "go.temporal.io/server/service/history/notification" tasks "go.temporal.io/server/service/history/tasks" gomock "go.uber.org/mock/gomock" ) @@ -426,6 +427,18 @@ func (mr *MockEngineMockRecorder) NotifyChasmExecution(executionKey, componentRe return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotifyChasmExecution", reflect.TypeOf((*MockEngine)(nil).NotifyChasmExecution), executionKey, componentRef) } +// NotifyFastForwardUpdate mocks base method. +func (m *MockEngine) NotifyFastForwardUpdate(key notification.TimeSkippingNotificationKey, fastforwardNotification *notification.TimeSkippingFastForwardNotification) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "NotifyFastForwardUpdate", key, fastforwardNotification) +} + +// NotifyFastForwardUpdate indicates an expected call of NotifyFastForwardUpdate. +func (mr *MockEngineMockRecorder) NotifyFastForwardUpdate(key, fastforwardNotification any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotifyFastForwardUpdate", reflect.TypeOf((*MockEngine)(nil).NotifyFastForwardUpdate), key, fastforwardNotification) +} + // NotifyNewHistoryEvent mocks base method. func (m *MockEngine) NotifyNewHistoryEvent(event *events.Notification) { m.ctrl.T.Helper() @@ -495,6 +508,21 @@ func (mr *MockEngineMockRecorder) PollMutableState(ctx, request any) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PollMutableState", reflect.TypeOf((*MockEngine)(nil).PollMutableState), ctx, request) } +// PollWorkflowExecutionTimeSkipping mocks base method. +func (m *MockEngine) PollWorkflowExecutionTimeSkipping(ctx context.Context, request *historyservice.PollWorkflowExecutionTimeSkippingRequest) (*historyservice.PollWorkflowExecutionTimeSkippingResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PollWorkflowExecutionTimeSkipping", ctx, request) + ret0, _ := ret[0].(*historyservice.PollWorkflowExecutionTimeSkippingResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PollWorkflowExecutionTimeSkipping indicates an expected call of PollWorkflowExecutionTimeSkipping. +func (mr *MockEngineMockRecorder) PollWorkflowExecutionTimeSkipping(ctx, request any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PollWorkflowExecutionTimeSkipping", reflect.TypeOf((*MockEngine)(nil).PollWorkflowExecutionTimeSkipping), ctx, request) +} + // PollWorkflowExecutionUpdate mocks base method. func (m *MockEngine) PollWorkflowExecutionUpdate(ctx context.Context, request *historyservice.PollWorkflowExecutionUpdateRequest) (*historyservice.PollWorkflowExecutionUpdateResponse, error) { m.ctrl.T.Helper() diff --git a/service/history/notification/notifier.go b/service/history/notification/notifier.go new file mode 100644 index 00000000000..d79869f2044 --- /dev/null +++ b/service/history/notification/notifier.go @@ -0,0 +1,148 @@ +// Package notification provides a generic, keyed in-memory pub/sub used by +// server-side long polls: a waiter subscribes on a key of type K and is woken with a +// value of type T when something publishes to that key. Concrete features (e.g. +// time-skipping fast-forward) instantiate PubSubNotifier[K, T] with their own key and +// payload types. +package notification + +import ( + "github.com/google/uuid" + enumspb "go.temporal.io/api/enums/v1" + "go.temporal.io/api/serviceerror" + "go.temporal.io/server/common/collection" +) + +// PubSubNotifier is a keyed pub/sub over payloads of type T. Notify fans out +// synchronously on the publisher's goroutine (the one producing the change, not the +// waiter's) with non-blocking sends over buffered-1 channels: a value is dropped when a +// waiter already has one pending. +// Watch rejects a new subscriber once a key already has the configured +// maximum, bounding the number of concurrent waiters per key. +type PubSubNotifier[K comparable, T any] interface { + Notify(key K, value T) + Watch(key K) (subscriberID string, channel <-chan T, err error) + Unwatch(key K, subscriberID string) error +} + +type pubSubNotifierImpl[K comparable, T any] struct { + maxSubscribersPerKey int + // key: K, value: map[subscriberID]chan T. The inner map is + // not thread-safe on its own; every access is guarded by the ConcurrentTxMap's + // per-key action callbacks. Subscribers per key are expected to be few. + subscriptions collection.ConcurrentTxMap +} + +// noopNotifier drops every notification and registers no subscribers. +type noopNotifier[K comparable, T any] struct{} + +// NewNoopNotifier returns a PubSubNotifier for components (and tests) that never +// participate in the long poll: publishing is a no-op and watching returns an already +// closed channel so an accidental waiter wakes immediately instead of blocking forever. +func NewNoopNotifier[K comparable, T any]() PubSubNotifier[K, T] { + return noopNotifier[K, T]{} +} + +func (noopNotifier[K, T]) Notify(K, T) {} + +func (noopNotifier[K, T]) Watch(K) (string, <-chan T, error) { + ch := make(chan T) + close(ch) + return "", ch, nil +} + +func (noopNotifier[K, T]) Unwatch(K, string) error { return nil } + +// NewPubSubNotifier creates a notifier that admits at most maxSubscribersPerKey +// concurrent waiters per key; Watch beyond that returns a ResourceExhausted error. +// +// hashKey only stripes the internal map's locks for concurrency — any uniform hash of the +// key works and it carries no history-shard meaning here. It need not cover every field of K: +// keys are matched by equality, so a hash over a subset only affects lock distribution. +func NewPubSubNotifier[K comparable, T any](hashKey func(K) uint32, maxSubscribersPerKey int) PubSubNotifier[K, T] { + if hashKey == nil { + // A caller bug, but not a fatal one, and better caught here than as a nil-func panic on + // the first Watch: a constant is still a correct hash, costing lock striping not lookups. + hashKey = func(K) uint32 { return 0 } + } + // The any-typed hash exists only because ConcurrentTxMap is not generic. + hashFn := func(key any) uint32 { + k, ok := key.(K) + if !ok { + // Unreachable: the map is only ever keyed through Notify/Watch/Unwatch, which are + // typed K. Checked for sanity, and degrading beats panicking as above. + return 0 + } + return hashKey(k) + } + return &pubSubNotifierImpl[K, T]{ + maxSubscribersPerKey: maxSubscribersPerKey, + subscriptions: collection.NewShardedConcurrentTxMap(1024, hashFn), + } +} + +func (n *pubSubNotifierImpl[K, T]) Watch(key K) (string, <-chan T, error) { + channel := make(chan T, 1) + subscriberID := uuid.NewString() + subscribers := map[string]chan T{subscriberID: channel} + + _, _, err := n.subscriptions.PutOrDo(key, subscribers, func(_ any, value any) error { + existing, ok := value.(map[string]chan T) + if !ok { + return serviceerror.NewInternal("unexpected subscription value type") + } + if len(existing) >= n.maxSubscribersPerKey { + return serviceerror.NewResourceExhaustedf( + enumspb.RESOURCE_EXHAUSTED_CAUSE_CONCURRENT_LIMIT, + "too many concurrent waiters (limit %d) for this execution", n.maxSubscribersPerKey) + } + if _, ok := existing[subscriberID]; ok { + return serviceerror.NewUnavailable("unable to watch execution") + } + existing[subscriberID] = channel + return nil + }) + if err != nil { + return "", nil, err + } + return subscriberID, channel, nil +} + +func (n *pubSubNotifierImpl[K, T]) Unwatch(key K, subscriberID string) error { + success := true + n.subscriptions.RemoveIf(key, func(_ any, value any) bool { + subscribers, ok := value.(map[string]chan T) + if !ok { + success = false + return false + } + if _, ok := subscribers[subscriberID]; !ok { + success = false + } else { + delete(subscribers, subscriberID) + } + return len(subscribers) == 0 + }) + if !success { + return serviceerror.NewInternal("unable to unwatch execution") + } + return nil +} + +func (n *pubSubNotifierImpl[K, T]) Notify(key K, value T) { + _, _, _ = n.subscriptions.GetAndDo(key, func(_ any, existing any) error { + subscribers, ok := existing.(map[string]chan T) + if !ok { + return nil + } + for _, channel := range subscribers { + select { + case channel <- value: + default: + // Buffer (size 1) is full: this value is dropped, so a newer one can be lost + // behind an older one. Delivery is best-effort; subscribers must recover on + // their own (re-read state on wake, or time out and get polled again). + } + } + return nil + }) +} diff --git a/service/history/notification/notifier_test.go b/service/history/notification/notifier_test.go new file mode 100644 index 00000000000..ddfde07c7d9 --- /dev/null +++ b/service/history/notification/notifier_test.go @@ -0,0 +1,180 @@ +package notification + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/require" + "go.temporal.io/api/serviceerror" + "go.temporal.io/server/common/definition" +) + +func oneShard(definition.WorkflowKey) uint32 { return 1 } + +// testKey builds an arbitrary subscription key for the generic notifier tests; the generic +// pub/sub is key-agnostic, so these don't use the time-skipping-specific key constructor. +func testKey(namespaceID, workflowID string) definition.WorkflowKey { + return definition.NewWorkflowKey(namespaceID, workflowID, "") +} + +// liveKeys returns the number of keys currently held in the notifier's internal map, +// used to prove keys are reclaimed (no leak) once their last waiter unwatches. +func liveKeys[K comparable, T any](t *testing.T, n PubSubNotifier[K, T]) int { + t.Helper() + impl, ok := n.(*pubSubNotifierImpl[K, T]) + require.True(t, ok) + return impl.subscriptions.Len() +} + +func TestPubSubNotifier(t *testing.T) { + t.Run("fan-out delivers to every subscriber on the key", func(t *testing.T) { + n := NewPubSubNotifier[definition.WorkflowKey, int](oneShard, 5) + key := testKey("ns", "wf") + _, ch1, err := n.Watch(key) + require.NoError(t, err) + _, ch2, err := n.Watch(key) + require.NoError(t, err) + n.Notify(key, 7) + require.Equal(t, 7, <-ch1) + require.Equal(t, 7, <-ch2) + }) + + t.Run("notify to a key with no subscribers is a no-op", func(t *testing.T) { + n := NewPubSubNotifier[definition.WorkflowKey, int](oneShard, 5) + require.NotPanics(t, func() { n.Notify(testKey("ns", "absent"), 1) }) + require.Zero(t, liveKeys(t, n)) + }) + + t.Run("notify is non-blocking and drops when the waiter buffer is full", func(t *testing.T) { + n := NewPubSubNotifier[definition.WorkflowKey, int](oneShard, 5) + key := testKey("ns", "wf") + _, ch, err := n.Watch(key) + require.NoError(t, err) + n.Notify(key, 1) // fills the buffered-1 channel + require.NotPanics(t, func() { n.Notify(key, 2) }) // dropped; must not block or panic + require.Equal(t, 1, <-ch) + select { + case v := <-ch: + require.Failf(t, "unexpected extra value", "got %d", v) + default: + } + }) + + t.Run("watch is rejected past the per-key cap", func(t *testing.T) { + const limit = 3 + n := NewPubSubNotifier[definition.WorkflowKey, int](oneShard, limit) + key := testKey("ns", "wf") + for range limit { + _, _, err := n.Watch(key) + require.NoError(t, err) + } + _, _, err := n.Watch(key) + var resourceExhausted *serviceerror.ResourceExhausted + require.ErrorAs(t, err, &resourceExhausted) + }) + + t.Run("keys are isolated from each other", func(t *testing.T) { + n := NewPubSubNotifier[definition.WorkflowKey, int](oneShard, 1) + keyA := testKey("ns", "a") + keyB := testKey("ns", "b") + _, chA, err := n.Watch(keyA) + require.NoError(t, err) + _, _, err = n.Watch(keyB) // different key is unaffected by A's cap of 1 + require.NoError(t, err) + n.Notify(keyA, 8) + require.Equal(t, 8, <-chA) + }) + + t.Run("unwatch frees a slot", func(t *testing.T) { + n := NewPubSubNotifier[definition.WorkflowKey, int](oneShard, 1) + key := testKey("ns", "wf") + id, _, err := n.Watch(key) + require.NoError(t, err) + _, _, err = n.Watch(key) // at the cap of 1 + require.Error(t, err) + require.NoError(t, n.Unwatch(key, id)) + _, _, err = n.Watch(key) // slot freed, succeeds again + require.NoError(t, err) + }) + + t.Run("unwatching the last subscriber removes the key (no leak)", func(t *testing.T) { + n := NewPubSubNotifier[definition.WorkflowKey, int](oneShard, 5) + key := testKey("ns", "wf") + id1, _, err := n.Watch(key) + require.NoError(t, err) + id2, _, err := n.Watch(key) + require.NoError(t, err) + require.Equal(t, 1, liveKeys(t, n)) // one key, two subscribers + require.NoError(t, n.Unwatch(key, id1)) + require.Equal(t, 1, liveKeys(t, n)) // one subscriber remains -> key stays + require.NoError(t, n.Unwatch(key, id2)) + require.Zero(t, liveKeys(t, n)) // last subscriber gone -> key removed + }) + + t.Run("unwatch of an unknown subscriber errors without panicking", func(t *testing.T) { + n := NewPubSubNotifier[definition.WorkflowKey, int](oneShard, 5) + key := testKey("ns", "wf") + _, _, err := n.Watch(key) + require.NoError(t, err) + var unwatchErr error + require.NotPanics(t, func() { unwatchErr = n.Unwatch(key, "does-not-exist") }) + require.Error(t, unwatchErr) + }) + + t.Run("unwatch of an unknown key is a no-op without panicking", func(t *testing.T) { + n := NewPubSubNotifier[definition.WorkflowKey, int](oneShard, 5) + require.NotPanics(t, func() { _ = n.Unwatch(testKey("ns", "absent"), "x") }) + require.Zero(t, liveKeys(t, n)) + }) + + t.Run("nil payload is delivered without panicking", func(t *testing.T) { + type payload struct{ v int } + n := NewPubSubNotifier[definition.WorkflowKey, *payload](oneShard, 5) + key := testKey("ns", "wf") + _, ch, err := n.Watch(key) + require.NoError(t, err) + require.NotPanics(t, func() { n.Notify(key, nil) }) + require.Nil(t, <-ch) + }) + + // Stresses the ConcurrentTxMap's per-key guarding: concurrent Watch/Notify/Unwatch + // must not panic or race (run with -race), and every key must be reclaimed once its + // waiters leave. Uses a high cap so the cap itself is not exercised here. + t.Run("concurrent watch/notify/unwatch is panic-free and leaks no keys", func(t *testing.T) { + n := NewPubSubNotifier[definition.WorkflowKey, int](oneShard, 1000) + const goroutines = 200 + var wg sync.WaitGroup + wg.Add(goroutines) + for i := range goroutines { + go func(i int) { + defer wg.Done() + key := testKey("ns", fmt.Sprintf("wf-%d", i%16)) // 16 shared keys + id, ch, err := n.Watch(key) + if err != nil { + return + } + n.Notify(key, i) + select { + case <-ch: + default: + } + _ = n.Unwatch(key, id) + }(i) + } + wg.Wait() + require.Zero(t, liveKeys(t, n), "all keys must be removed after their waiters unwatch") + }) +} + +func TestNoopNotifier(t *testing.T) { + n := NewNoopNotifier[definition.WorkflowKey, int]() + key := testKey("ns", "wf") + + id, ch, err := n.Watch(key) + require.NoError(t, err) + require.NotPanics(t, func() { n.Notify(key, 1) }) // dropped + _, open := <-ch + require.False(t, open) // Watch returns an already-closed channel: never blocks + require.NoError(t, n.Unwatch(key, id)) +} diff --git a/service/history/notification/timeskipping_notifier.go b/service/history/notification/timeskipping_notifier.go new file mode 100644 index 00000000000..f3822da71e1 --- /dev/null +++ b/service/history/notification/timeskipping_notifier.go @@ -0,0 +1,96 @@ +package notification + +import ( + "strconv" + + "github.com/dgryski/go-farm" + persistencespb "go.temporal.io/server/api/persistence/v1" + "go.temporal.io/server/chasm" +) + +// TimeSkippingFastForwardNotification is the payload delivered to subscribers waiting for +// fast-forward completion of an execution. +// +// Subscribers and publishers key on (namespace, workflowID, archetype) with no RunID, so one poll +// follows a chain of runs (e.g. continue-as-new / retry / cron); see TimeSkippingNotificationKey. +// The payload carries a clone of the persisted TimeSkippingInfo, so subscribers decide from it +// alone and never re-acquire the workflow lease — holding that lease would block the very +// execution being polled. +// +// The poll is sound because of the three properties below, and notably not because delivery is +// reliable: +// +// 1. Subscribe before read. Invoke watches the key before its initial state read, so an update +// persisted in between still lands on the channel instead of being missed by both. +// 2. Filtered publishes. notifyFastForwardUpdate fires only when the transaction stamped the +// fast-forward (init / update / completion) or the run chain ended, so every +// delivered message is meaningful and the waiter can act on it without re-reading. +// 3. One buffered notification is usually enough. #2 reduces noise, so a poller usually +// returns on the first notification: the fast-forward has either completed or lost its chance +// to. The lone exception is the init in #2 inheriting a propagated target — a chain boundary +// reinstalling the same fast-forward — which keeps waiting. A chain can emit several of those +// back to back, so no buffer size removes the risk of a later notification being dropped behind +// them. That edge case is accepted rather than solved here: the wait runs to its soft timeout +// and the caller's next poll re-reads authoritative state, so treat POLL_TIMEOUT as "ask again". +type TimeSkippingFastForwardNotification struct { + TimeSkippingInfo *persistencespb.TimeSkippingInfo + WorkflowExecutionCompleted bool +} + +func (n *TimeSkippingFastForwardNotification) GetTimeSkippingInfo() *persistencespb.TimeSkippingInfo { + if n == nil { + return nil + } + return n.TimeSkippingInfo +} + +func (n *TimeSkippingFastForwardNotification) GetWorkflowExecutionCompleted() bool { + if n == nil { + return false + } + return n.WorkflowExecutionCompleted +} + +type TimeSkippingNotificationKey struct { + NamespaceID string + WorkflowID string + ArchetypeID chasm.ArchetypeID +} + +type TimeSkippingFastForwardNotifier = PubSubNotifier[TimeSkippingNotificationKey, *TimeSkippingFastForwardNotification] + +func NewTimeSkippingNotificationKey( + namespaceID string, + workflowID string, + archetypeID chasm.ArchetypeID, +) TimeSkippingNotificationKey { + if archetypeID == chasm.UnspecifiedArchetypeID { + // A record persisted before archetype IDs existed, or a not-yet-initialized chasm tree, + // is a workflow. Normalizing here is what keeps publisher and waiter on one key: the + // waiter always names the workflow archetype, while the publisher forwards whatever the + // execution carries. + archetypeID = chasm.WorkflowArchetypeID + } + return TimeSkippingNotificationKey{ + NamespaceID: namespaceID, + WorkflowID: workflowID, + ArchetypeID: archetypeID, + } +} + +const maxFastForwardWaitersPerExecution = 5 + +func NewTimeSkippingFastForwardNotifier() TimeSkippingFastForwardNotifier { + return NewPubSubNotifier[TimeSkippingNotificationKey, *TimeSkippingFastForwardNotification]( + hashTimeSkippingNotificationKey, + maxFastForwardWaitersPerExecution, + ) +} + +func hashTimeSkippingNotificationKey(key TimeSkippingNotificationKey) uint32 { + return farm.Fingerprint32([]byte( + key.NamespaceID + "_" + key.WorkflowID + "_" + strconv.FormatUint(uint64(key.ArchetypeID), 10), + )) +} + +var NoopTimeSkippingFastForwardNotifier = NewNoopNotifier[TimeSkippingNotificationKey, *TimeSkippingFastForwardNotification]() diff --git a/service/history/notification/timeskipping_notifier_test.go b/service/history/notification/timeskipping_notifier_test.go new file mode 100644 index 00000000000..1828bc20983 --- /dev/null +++ b/service/history/notification/timeskipping_notifier_test.go @@ -0,0 +1,78 @@ +package notification + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.temporal.io/server/chasm" +) + +func TestTimeSkippingFastForwardNotifier(t *testing.T) { + t.Run("archetype isolates executions sharing a business id", func(t *testing.T) { + n := NewTimeSkippingFastForwardNotifier() + workflowKey := NewTimeSkippingNotificationKey("ns", "wf", chasm.WorkflowArchetypeID) + schedulerKey := NewTimeSkippingNotificationKey("ns", "wf", chasm.SchedulerArchetypeID) + + _, workflowCh, err := n.Watch(workflowKey) + require.NoError(t, err) + _, schedulerCh, err := n.Watch(schedulerKey) + require.NoError(t, err) + require.Equal(t, 2, liveKeys(t, n)) + + notif := &TimeSkippingFastForwardNotification{WorkflowExecutionCompleted: true} + n.Notify(schedulerKey, notif) + + require.Same(t, notif, <-schedulerCh) + select { + case got := <-workflowCh: + require.Failf(t, "waiter woken by another archetype", "got %v", got) + default: + } + }) + + // The waiter always names the workflow archetype while the publisher forwards whatever the + // execution carries, so a pre-archetype record (unspecified) must reach the workflow waiter. + t.Run("unspecified archetype is normalized to workflow", func(t *testing.T) { + n := NewTimeSkippingFastForwardNotifier() + require.Equal(t, + NewTimeSkippingNotificationKey("ns", "wf", chasm.WorkflowArchetypeID), + NewTimeSkippingNotificationKey("ns", "wf", chasm.UnspecifiedArchetypeID)) + + _, ch, err := n.Watch(NewTimeSkippingNotificationKey("ns", "wf", chasm.WorkflowArchetypeID)) + require.NoError(t, err) + + notif := &TimeSkippingFastForwardNotification{WorkflowExecutionCompleted: true} + n.Notify(NewTimeSkippingNotificationKey("ns", "wf", chasm.UnspecifiedArchetypeID), notif) + + require.Same(t, notif, <-ch) + }) + + t.Run("nil hash key degrades instead of panicking", func(t *testing.T) { + n := NewPubSubNotifier[TimeSkippingNotificationKey, *TimeSkippingFastForwardNotification](nil, 5) + key := NewTimeSkippingNotificationKey("ns", "wf", chasm.WorkflowArchetypeID) + + notif := &TimeSkippingFastForwardNotification{WorkflowExecutionCompleted: true} + require.NotPanics(t, func() { + _, _, err := n.Watch(key) + require.NoError(t, err) + n.Notify(key, notif) + }) + }) + + t.Run("nil payload accessors are safe", func(t *testing.T) { + var notif *TimeSkippingFastForwardNotification + require.Nil(t, notif.GetTimeSkippingInfo()) + require.False(t, notif.GetWorkflowExecutionCompleted()) + }) + + t.Run("keys match by value, not identity", func(t *testing.T) { + n := NewTimeSkippingFastForwardNotifier() + _, ch, err := n.Watch(NewTimeSkippingNotificationKey("ns", "wf", chasm.WorkflowArchetypeID)) + require.NoError(t, err) + + notif := &TimeSkippingFastForwardNotification{WorkflowExecutionCompleted: true} + n.Notify(NewTimeSkippingNotificationKey("ns", "wf", chasm.WorkflowArchetypeID), notif) + + require.Same(t, notif, <-ch) + }) +} diff --git a/service/history/statemachine_environment_test.go b/service/history/statemachine_environment_test.go index 0183508a679..935c619812e 100644 --- a/service/history/statemachine_environment_test.go +++ b/service/history/statemachine_environment_test.go @@ -31,6 +31,7 @@ import ( "go.temporal.io/server/service/history/events" "go.temporal.io/server/service/history/hsm" historyi "go.temporal.io/server/service/history/interfaces" + "go.temporal.io/server/service/history/notification" "go.temporal.io/server/service/history/queues" "go.temporal.io/server/service/history/shard" "go.temporal.io/server/service/history/tasks" @@ -100,14 +101,15 @@ func newStateMachineEnvTestContext(t *testing.T, enableTransitionHistory bool) * mockTimerProcessor.EXPECT().NotifyNewTasks(gomock.Any()).AnyTimes() h := &historyEngineImpl{ - currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), - shardContext: s.mockShard, - clusterMetadata: mockClusterMetadata, - executionManager: s.mockShard.GetExecutionManager(), - logger: s.mockShard.GetLogger(), - tokenSerializer: tasktoken.NewSerializer(), - metricsHandler: s.mockShard.GetMetricsHandler(), - eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), + shardContext: s.mockShard, + clusterMetadata: mockClusterMetadata, + executionManager: s.mockShard.GetExecutionManager(), + logger: s.mockShard.GetLogger(), + tokenSerializer: tasktoken.NewSerializer(), + metricsHandler: s.mockShard.GetMetricsHandler(), + eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + fastForwardNotifier: notification.NoopTimeSkippingFastForwardNotifier, queueProcessors: map[tasks.Category]queues.Queue{ mockTimerProcessor.Category(): mockTimerProcessor, }, diff --git a/service/history/timer_queue_active_task_executor.go b/service/history/timer_queue_active_task_executor.go index 7b17f0a70da..4b908773f6a 100644 --- a/service/history/timer_queue_active_task_executor.go +++ b/service/history/timer_queue_active_task_executor.go @@ -962,7 +962,7 @@ func (t *timerQueueActiveTaskExecutor) executeTimeSkippingTimerTask( return errNoTimerFired } - ffVT := tsi.GetFastForwardInfo().GetLastUpdateVersionedTransition() + ffVT := tsi.GetFastForwardInfoLastUpdateVersionedTransition() if ffVT == nil || task.VersionedTransition == nil { // Invariant: when a pending fast-forward and a task both exist, they must both have a // non-nil versioned transition. A nil here is a "should never happen" state bug, not lost diff --git a/service/history/timer_queue_active_task_executor_test.go b/service/history/timer_queue_active_task_executor_test.go index aaa0039d2e2..393e7fd82fc 100644 --- a/service/history/timer_queue_active_task_executor_test.go +++ b/service/history/timer_queue_active_task_executor_test.go @@ -40,6 +40,7 @@ import ( "go.temporal.io/server/service/history/events" "go.temporal.io/server/service/history/hsm" historyi "go.temporal.io/server/service/history/interfaces" + "go.temporal.io/server/service/history/notification" "go.temporal.io/server/service/history/queues" "go.temporal.io/server/service/history/shard" "go.temporal.io/server/service/history/tasks" @@ -158,14 +159,15 @@ func (s *timerQueueActiveTaskExecutorSuite) SetupTest() { s.mockDeleteManager = deletemanager.NewMockDeleteManager(s.controller) h := &historyEngineImpl{ - currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), - shardContext: s.mockShard, - clusterMetadata: s.mockClusterMetadata, - executionManager: s.mockExecutionMgr, - logger: s.logger, - tokenSerializer: tasktoken.NewSerializer(), - metricsHandler: s.mockShard.GetMetricsHandler(), - eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), + shardContext: s.mockShard, + clusterMetadata: s.mockClusterMetadata, + executionManager: s.mockExecutionMgr, + logger: s.logger, + tokenSerializer: tasktoken.NewSerializer(), + metricsHandler: s.mockShard.GetMetricsHandler(), + eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + fastForwardNotifier: notification.NoopTimeSkippingFastForwardNotifier, queueProcessors: map[tasks.Category]queues.Queue{ s.mockTxProcessor.Category(): s.mockTxProcessor, s.mockTimerProcessor.Category(): s.mockTimerProcessor, @@ -2417,10 +2419,10 @@ func (s *timerQueueActiveTaskExecutorSuite) TestExecuteTimeSkippingTimerTask() { FastForwardInfo: &persistencespb.FastForwardInfo{ TargetTime: target, HasReached: hasReached, - LastUpdateVersionedTransition: &persistencespb.VersionedTransition{ - NamespaceFailoverVersion: version, - TransitionCount: transitionCount, - }, + }, + FastForwardInfoLastUpdateVersionedTransition: &persistencespb.VersionedTransition{ + NamespaceFailoverVersion: version, + TransitionCount: transitionCount, }, } } @@ -2450,11 +2452,9 @@ func (s *timerQueueActiveTaskExecutorSuite) TestExecuteTimeSkippingTimerTask() { { name: "ConfigDisabled", tsi: &persistencespb.TimeSkippingInfo{ - Config: &commonpb.TimeSkippingConfig{Enabled: false}, - FastForwardInfo: &persistencespb.FastForwardInfo{ - TargetTime: target, - LastUpdateVersionedTransition: &persistencespb.VersionedTransition{NamespaceFailoverVersion: s.version, TransitionCount: 1}, - }, + Config: &commonpb.TimeSkippingConfig{Enabled: false}, + FastForwardInfo: &persistencespb.FastForwardInfo{TargetTime: target}, + FastForwardInfoLastUpdateVersionedTransition: &persistencespb.VersionedTransition{NamespaceFailoverVersion: s.version, TransitionCount: 1}, }, taskVersion: s.version, taskTransitionCount: 1, diff --git a/service/history/timer_queue_standby_task_executor.go b/service/history/timer_queue_standby_task_executor.go index 2b8d6fdffef..35da61b9c9b 100644 --- a/service/history/timer_queue_standby_task_executor.go +++ b/service/history/timer_queue_standby_task_executor.go @@ -249,7 +249,7 @@ func (t *timerQueueStandbyTaskExecutor) executeTimeSkippingTimerTask( // the fast-forward this timer task is associated with is still valid and has not been reached so keep waiting if ffi != nil && - transitionhistory.Compare(ffi.GetLastUpdateVersionedTransition(), timerTask.VersionedTransition) == 0 && + transitionhistory.Compare(tsi.GetFastForwardInfoLastUpdateVersionedTransition(), timerTask.VersionedTransition) == 0 && !ffi.GetHasReached() { return &struct{}{}, nil } diff --git a/service/history/timer_queue_standby_task_executor_test.go b/service/history/timer_queue_standby_task_executor_test.go index 86d997f2d36..104c6209e36 100644 --- a/service/history/timer_queue_standby_task_executor_test.go +++ b/service/history/timer_queue_standby_task_executor_test.go @@ -44,6 +44,7 @@ import ( "go.temporal.io/server/service/history/events" "go.temporal.io/server/service/history/hsm" historyi "go.temporal.io/server/service/history/interfaces" + "go.temporal.io/server/service/history/notification" "go.temporal.io/server/service/history/queues" "go.temporal.io/server/service/history/shard" "go.temporal.io/server/service/history/tasks" @@ -172,14 +173,15 @@ func (s *timerQueueStandbyTaskExecutorSuite) SetupTest() { s.mockDeleteManager = deletemanager.NewMockDeleteManager(s.controller) h := &historyEngineImpl{ - currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), - shardContext: s.mockShard, - clusterMetadata: s.mockClusterMetadata, - executionManager: s.mockExecutionMgr, - logger: s.logger, - tokenSerializer: tasktoken.NewSerializer(), - metricsHandler: s.mockShard.GetMetricsHandler(), - eventNotifier: events.NewNotifier(s.timeSource, metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), + shardContext: s.mockShard, + clusterMetadata: s.mockClusterMetadata, + executionManager: s.mockExecutionMgr, + logger: s.logger, + tokenSerializer: tasktoken.NewSerializer(), + metricsHandler: s.mockShard.GetMetricsHandler(), + eventNotifier: events.NewNotifier(s.timeSource, metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + fastForwardNotifier: notification.NoopTimeSkippingFastForwardNotifier, queueProcessors: map[tasks.Category]queues.Queue{ s.mockTxProcessor.Category(): s.mockTxProcessor, s.mockTimerProcessor.Category(): s.mockTimerProcessor, @@ -2497,13 +2499,13 @@ func (s *timerQueueStandbyTaskExecutorSuite) TestExecuteTimeSkippingTimerTask() pms, workflowKey := makeTimeSkippingMS() pms.ExecutionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour)}, }, FastForwardInfo: &persistencespb.FastForwardInfo{ - TargetTime: timestamppb.New(s.now.Add(time.Hour)), - LastUpdateVersionedTransition: &persistencespb.VersionedTransition{NamespaceFailoverVersion: s.version, TransitionCount: 1}, + TargetTime: timestamppb.New(s.now.Add(time.Hour)), }, + FastForwardInfoLastUpdateVersionedTransition: &persistencespb.VersionedTransition{NamespaceFailoverVersion: s.version, TransitionCount: 1}, } return pms, workflowKey } diff --git a/service/history/transfer_queue_active_task_executor_test.go b/service/history/transfer_queue_active_task_executor_test.go index e0b903fb946..7f8ef23f177 100644 --- a/service/history/transfer_queue_active_task_executor_test.go +++ b/service/history/transfer_queue_active_task_executor_test.go @@ -55,6 +55,7 @@ import ( "go.temporal.io/server/service/history/events" "go.temporal.io/server/service/history/hsm" historyi "go.temporal.io/server/service/history/interfaces" + "go.temporal.io/server/service/history/notification" "go.temporal.io/server/service/history/queues" "go.temporal.io/server/service/history/shard" "go.temporal.io/server/service/history/tasks" @@ -204,14 +205,15 @@ func (s *transferQueueActiveTaskExecutorSuite) SetupTest() { s.logger = s.mockShard.GetLogger() h := &historyEngineImpl{ - currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), - shardContext: s.mockShard, - clusterMetadata: s.mockClusterMetadata, - executionManager: s.mockExecutionMgr, - logger: s.logger, - tokenSerializer: tasktoken.NewSerializer(), - metricsHandler: s.mockShard.GetMetricsHandler(), - eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), + shardContext: s.mockShard, + clusterMetadata: s.mockClusterMetadata, + executionManager: s.mockExecutionMgr, + logger: s.logger, + tokenSerializer: tasktoken.NewSerializer(), + metricsHandler: s.mockShard.GetMetricsHandler(), + eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + fastForwardNotifier: notification.NoopTimeSkippingFastForwardNotifier, queueProcessors: map[tasks.Category]queues.Queue{ s.mockTxProcessor.Category(): s.mockTxProcessor, s.mockTimerProcessor.Category(): s.mockTimerProcessor, diff --git a/service/history/transfer_queue_standby_task_executor_test.go b/service/history/transfer_queue_standby_task_executor_test.go index 993e9928840..ab7d6221504 100644 --- a/service/history/transfer_queue_standby_task_executor_test.go +++ b/service/history/transfer_queue_standby_task_executor_test.go @@ -45,6 +45,7 @@ import ( "go.temporal.io/server/service/history/events" "go.temporal.io/server/service/history/hsm" historyi "go.temporal.io/server/service/history/interfaces" + "go.temporal.io/server/service/history/notification" "go.temporal.io/server/service/history/queues" "go.temporal.io/server/service/history/shard" "go.temporal.io/server/service/history/tasks" @@ -187,13 +188,14 @@ func (s *transferQueueStandbyTaskExecutorSuite) SetupTest() { s.mockArchivalMetadata.SetVisibilityEnabledByDefault() h := &historyEngineImpl{ - currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), - shardContext: s.mockShard, - clusterMetadata: s.mockClusterMetadata, - executionManager: s.mockExecutionMgr, - logger: s.logger, - tokenSerializer: tasktoken.NewSerializer(), - metricsHandler: s.mockShard.GetMetricsHandler(), + currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), + shardContext: s.mockShard, + clusterMetadata: s.mockClusterMetadata, + executionManager: s.mockExecutionMgr, + logger: s.logger, + tokenSerializer: tasktoken.NewSerializer(), + metricsHandler: s.mockShard.GetMetricsHandler(), + fastForwardNotifier: notification.NoopTimeSkippingFastForwardNotifier, } s.mockShard.SetEngineForTesting(h) s.clusterName = cluster.TestAlternativeClusterName diff --git a/service/history/visibility_queue_task_executor_test.go b/service/history/visibility_queue_task_executor_test.go index ad0bfd25c44..e26ebdcd898 100644 --- a/service/history/visibility_queue_task_executor_test.go +++ b/service/history/visibility_queue_task_executor_test.go @@ -43,6 +43,7 @@ import ( "go.temporal.io/server/service/history/events" "go.temporal.io/server/service/history/hsm" historyi "go.temporal.io/server/service/history/interfaces" + "go.temporal.io/server/service/history/notification" "go.temporal.io/server/service/history/queues" "go.temporal.io/server/service/history/shard" "go.temporal.io/server/service/history/tasks" @@ -164,14 +165,15 @@ func (s *visibilityQueueTaskExecutorSuite) SetupTest() { s.logger = s.mockShard.GetLogger() h := &historyEngineImpl{ - currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), - shardContext: s.mockShard, - clusterMetadata: mockClusterMetadata, - executionManager: s.mockExecutionMgr, - logger: s.logger, - tokenSerializer: tasktoken.NewSerializer(), - metricsHandler: s.mockShard.GetMetricsHandler(), - eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + currentClusterName: s.mockShard.Resource.GetClusterMetadata().GetCurrentClusterName(), + shardContext: s.mockShard, + clusterMetadata: mockClusterMetadata, + executionManager: s.mockExecutionMgr, + logger: s.logger, + tokenSerializer: tasktoken.NewSerializer(), + metricsHandler: s.mockShard.GetMetricsHandler(), + eventNotifier: events.NewNotifier(clock.NewRealTimeSource(), metrics.NoopMetricsHandler, func(namespace.ID, string) int32 { return 1 }), + fastForwardNotifier: notification.NoopTimeSkippingFastForwardNotifier, } s.mockShard.SetEngineForTesting(h) diff --git a/service/history/workflow/context.go b/service/history/workflow/context.go index fd355721c85..e8aa1577cb2 100644 --- a/service/history/workflow/context.go +++ b/service/history/workflow/context.go @@ -539,7 +539,7 @@ func (c *ContextImpl) CreateWorkflowExecution( if err != nil { return err } - NotifyOnExecutionSnapshot(engine, newWorkflow) + NotifyOnExecutionSnapshot(engine, c.archetypeID, newWorkflow) emitStateTransitionCount(c.metricsHandler, shardContext.GetClusterMetadata(), newMutableState) return nil diff --git a/service/history/workflow/mutable_state_impl.go b/service/history/workflow/mutable_state_impl.go index c67458c0353..cd20e1a1539 100644 --- a/service/history/workflow/mutable_state_impl.go +++ b/service/history/workflow/mutable_state_impl.go @@ -3198,9 +3198,7 @@ func (ms *MutableStateImpl) ApplyWorkflowExecutionStartedEvent( ms.executionInfo.Priority = event.Priority if tsc, stateProp := event.GetTimeSkippingConfig(), event.GetTimeSkippingStatePropagation(); tsc != nil || stateProp.GetInitialSkippedDuration().AsDuration() > 0 { - if err := ms.initTimeSkippingInfo(tsc, stateProp); err != nil { - return err - } + ms.initTimeSkippingInfo(tsc, stateProp) } ms.approximateSize += ms.executionInfo.Size() @@ -5950,13 +5948,9 @@ func (ms *MutableStateImpl) ApplyWorkflowExecutionOptionsUpdatedEvent(event *his tsc := attributes.GetTimeSkippingConfig() tsi := ms.GetExecutionInfo().GetTimeSkippingInfo() if tsi == nil { - if err := ms.initTimeSkippingInfo(tsc, nil); err != nil { - return err - } + ms.initTimeSkippingInfo(tsc, nil) } else { - if err := ms.updateTimeSkippingInfo(tsc); err != nil { - return err - } + ms.updateTimeSkippingInfo(tsc) } } diff --git a/service/history/workflow/mutable_state_impl_test.go b/service/history/workflow/mutable_state_impl_test.go index 26614641020..b806b54ede4 100644 --- a/service/history/workflow/mutable_state_impl_test.go +++ b/service/history/workflow/mutable_state_impl_test.go @@ -4461,8 +4461,8 @@ func (s *mutableStateSuite) TestAddStartChildWorkflowExecutionInitiatedEvent_Tim name: "MaxElapsedDuration is never propagated to children → cleared from child snapshot", parentTSI: &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(3 * time.Hour)}, + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(3 * time.Hour)}}, AccumulatedSkippedDuration: durationpb.New(time.Hour), }, expectCfg: &commonpb.TimeSkippingConfig{Enabled: true}, @@ -7716,8 +7716,8 @@ func (s *mutableStateSuite) TestCloseTransactionPrepareTasks() { // rebuild flows. func (s *mutableStateSuite) TestApplyWorkflowExecutionStartedEvent_TimeSkippingConfig() { inputConfig := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(time.Hour)} + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour)}} testCases := []struct { name string @@ -7795,11 +7795,11 @@ func (s *mutableStateSuite) TestApplyWorkflowExecutionStartedEvent_TimeSkippingC func (s *mutableStateSuite) TestApplyWorkflowExecutionOptionsUpdatedEvent_TimeSkippingConfig() { initialConfig := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(time.Hour)} + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour)}} updatedConfig := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(2 * time.Hour)} + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(2 * time.Hour)}} testCases := []struct { name string diff --git a/service/history/workflow/task_generator.go b/service/history/workflow/task_generator.go index f5bc7ec84b6..70d11408ccd 100644 --- a/service/history/workflow/task_generator.go +++ b/service/history/workflow/task_generator.go @@ -1039,7 +1039,7 @@ func isPathAffectedByDelete(deletePath []hsm.Key, timerPath []*persistencespb.St // refreshTasksForTimeSkipping). func (r *TaskGeneratorImpl) RegenerateTimerTasksForTimeSkipping() error { - if accumulatedSkippedDuration(r.mutableState.GetExecutionInfo()) <= 0 { + if NewTimeSkippingInfoUtil(r.mutableState.GetExecutionInfo().GetTimeSkippingInfo()).GetAccumulatedSkippedDuration() <= 0 { return nil } @@ -1103,7 +1103,7 @@ func (r *TaskGeneratorImpl) RegenerateTimerTasksForTimeSkipping() error { // TaskID is set by shard WorkflowKey: r.mutableState.GetWorkflowKey(), VisibilityTimestamp: fastForward.GetTargetTime().AsTime(), - VersionedTransition: fastForward.GetLastUpdateVersionedTransition(), + VersionedTransition: tsi.GetFastForwardInfoLastUpdateVersionedTransition(), ArchetypeID: r.mutableState.ChasmTree().ArchetypeID(), }) } diff --git a/service/history/workflow/task_generator_test.go b/service/history/workflow/task_generator_test.go index 77217fe6173..934168631c1 100644 --- a/service/history/workflow/task_generator_test.go +++ b/service/history/workflow/task_generator_test.go @@ -1493,14 +1493,14 @@ func TestTaskGeneratorImpl_RegenerateTimerTasksForTimeSkipping_FastForwardTimer( name: "fast-forward configured and unreached emits task", tsi: &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(2 * time.Hour)}, + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(2 * time.Hour)}}, AccumulatedSkippedDuration: durationpb.New(time.Hour), FastForwardInfo: &persistencespb.FastForwardInfo{ - TargetTime: timestamppb.New(fastForwardTarget), - LastUpdateVersionedTransition: fastForwardVT, - HasReached: false, + TargetTime: timestamppb.New(fastForwardTarget), + HasReached: false, }, + FastForwardInfoLastUpdateVersionedTransition: fastForwardVT, }, wantFastForwardTask: &wantTask{visibilityTimestamp: fastForwardTarget, versionedTransition: fastForwardVT}, }, @@ -1508,28 +1508,28 @@ func TestTaskGeneratorImpl_RegenerateTimerTasksForTimeSkipping_FastForwardTimer( name: "HasReached=true skips task emission", tsi: &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(2 * time.Hour)}, + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(2 * time.Hour)}}, AccumulatedSkippedDuration: durationpb.New(time.Hour), FastForwardInfo: &persistencespb.FastForwardInfo{ - TargetTime: timestamppb.New(fastForwardTarget), - LastUpdateVersionedTransition: fastForwardVT, - HasReached: true, + TargetTime: timestamppb.New(fastForwardTarget), + HasReached: true, }, + FastForwardInfoLastUpdateVersionedTransition: fastForwardVT, }, }, { name: "Enabled=false skips task emission", tsi: &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: false, - FastForward: durationpb.New(2 * time.Hour)}, + Enabled: false, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(2 * time.Hour)}}, AccumulatedSkippedDuration: durationpb.New(time.Hour), FastForwardInfo: &persistencespb.FastForwardInfo{ - TargetTime: timestamppb.New(fastForwardTarget), - LastUpdateVersionedTransition: fastForwardVT, - HasReached: false, + TargetTime: timestamppb.New(fastForwardTarget), + HasReached: false, }, + FastForwardInfoLastUpdateVersionedTransition: fastForwardVT, }, }, { @@ -1871,10 +1871,10 @@ func TestTaskGeneratorImpl_RegenerateTimerTasksForTimeSkipping_AllFieldsPopulate AccumulatedSkippedDuration: durationpb.New(time.Hour), Config: &commonpb.TimeSkippingConfig{Enabled: true}, FastForwardInfo: &persistencespb.FastForwardInfo{ - TargetTime: timestamppb.New(now.Add(2 * time.Hour)), - LastUpdateVersionedTransition: ffVersionedTransition, - HasReached: false, + TargetTime: timestamppb.New(now.Add(2 * time.Hour)), + HasReached: false, }, + FastForwardInfoLastUpdateVersionedTransition: ffVersionedTransition, }, }).AnyTimes() mutableState.EXPECT().GetPendingTimerInfos().Return(map[string]*persistencespb.TimerInfo{ diff --git a/service/history/workflow/timeskipping.go b/service/history/workflow/timeskipping.go index 80f9a45d875..3eeaabab210 100644 --- a/service/history/workflow/timeskipping.go +++ b/service/history/workflow/timeskipping.go @@ -25,38 +25,34 @@ import ( func (ms *MutableStateImpl) initTimeSkippingInfo( config *commonpb.TimeSkippingConfig, timeSkippingStatePropagation *commonpb.TimeSkippingStatePropagation, -) error { +) { initialSkip := timeSkippingStatePropagation.GetInitialSkippedDuration() if config == nil && initialSkip == nil { - return nil - } - - if ms.executionInfo.TimeSkippingInfo != nil { - return serviceerror.NewInternal("time skipping info already initialized") + return } ms.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ Config: config, AccumulatedSkippedDuration: initialSkip, + SessionSkipCount: timeSkippingStatePropagation.GetInitialSkipCount(), } ms.wrapTimeSourceWithTimeSkipping() ms.wrapExecutionTimes(initialSkip) ms.applyFastForward(timeSkippingStatePropagation.GetFastForwardTargetTime()) ms.timeSkippingInfoUpdated = true - return nil } func (ms *MutableStateImpl) updateTimeSkippingInfo( config *commonpb.TimeSkippingConfig, -) error { +) { tsi := ms.executionInfo.GetTimeSkippingInfo() if tsi == nil { - return serviceerror.NewInternal("time skipping info not initialized when updating") + return } ms.executionInfo.TimeSkippingInfo.Config = config ms.applyFastForward(nil) ms.timeSkippingInfoUpdated = true - return nil + tsi.SessionSkipCount = 0 } // applyFastForward (re)computes the FastForwardInfo using the new TimeSkippingConfig (TSC) and propagated time-skippingstates. @@ -67,9 +63,9 @@ func (ms *MutableStateImpl) applyFastForward(propagatedTargetTime *timestamppb.T tsc := ms.GetExecutionInfo().GetTimeSkippingInfo().GetConfig() tsi := ms.executionInfo.TimeSkippingInfo - if !tsc.GetEnabled() || tsc.GetFastForward().AsDuration() <= 0 { + if !tsc.GetEnabled() || tsc.GetFastForwardConfig().GetDuration().AsDuration() <= 0 { if tsi.FastForwardInfo != nil { - tsi.FastForwardInfo = nil + ms.setAndStampFastForwardInfo(nil) } return } @@ -80,27 +76,35 @@ func (ms *MutableStateImpl) applyFastForward(propagatedTargetTime *timestamppb.T } else { // if there is no propagated target time, // fast-forward refers to a new duration from now. - targetTime = ms.Now().Add(tsc.GetFastForward().AsDuration()) - } - - currentVersionedTransition := &persistencespb.VersionedTransition{ - NamespaceFailoverVersion: ms.GetCurrentVersion(), - TransitionCount: ms.NextTransitionCount(), + targetTime = ms.Now().Add(tsc.GetFastForwardConfig().GetDuration().AsDuration()) } - tsi.FastForwardInfo = &persistencespb.FastForwardInfo{ - TargetTime: timestamppb.New(targetTime), - HasReached: false, - LastUpdateVersionedTransition: currentVersionedTransition, - } + ffVersionedTransition := ms.setAndStampFastForwardInfo(&persistencespb.FastForwardInfo{ + TargetTime: timestamppb.New(targetTime), + HasReached: false, + }) ms.AddTasks(&tasks.TimeSkippingTimerTask{ WorkflowKey: ms.GetWorkflowKey(), VisibilityTimestamp: targetTime, - VersionedTransition: currentVersionedTransition, + VersionedTransition: ffVersionedTransition, ArchetypeID: ms.ChasmTree().ArchetypeID(), }) } +// setAndStampFastForwardInfo sets the fast-forward info and stamps it with the current transaction's versioned transition. +// Nothing else may write these two fields; routing every update through here is what keeps them in lockstep. +func (ms *MutableStateImpl) setAndStampFastForwardInfo( + ffInfo *persistencespb.FastForwardInfo, +) *persistencespb.VersionedTransition { + tsi := ms.executionInfo.TimeSkippingInfo + tsi.FastForwardInfo = ffInfo + tsi.FastForwardInfoLastUpdateVersionedTransition = &persistencespb.VersionedTransition{ + NamespaceFailoverVersion: ms.GetCurrentVersion(), + TransitionCount: ms.NextTransitionCount(), + } + return tsi.FastForwardInfoLastUpdateVersionedTransition +} + func (ms *MutableStateImpl) wrapExecutionTimes(initialSkippedDuration *durationpb.Duration) { if initialSkippedDuration == nil || initialSkippedDuration.AsDuration() == 0 { return @@ -131,59 +135,76 @@ func (ms *MutableStateImpl) wrapExecutionTimes(initialSkippedDuration *durationp func propagateTimeSkippingToNextRun( source *persistencespb.WorkflowExecutionInfo, ) (*commonpb.TimeSkippingConfig, *commonpb.TimeSkippingStatePropagation) { - previousTSC := source.GetTimeSkippingInfo().GetConfig() + + tsi := source.GetTimeSkippingInfo() + util := NewTimeSkippingInfoUtil(tsi) // if disabled, we just return nil for the new TSC var newTSC *commonpb.TimeSkippingConfig - if previousTSC.GetEnabled() { - newTSC = common.CloneProto(previousTSC) + if util.IsEnabled() { + newTSC = common.CloneProto(tsi.GetConfig()) } + // state propagation (virtual time, fast forward, skip count) var stateProp *commonpb.TimeSkippingStatePropagation - if accum := accumulatedSkippedDuration(source); accum > 0 { + if accum := util.GetAccumulatedSkippedDuration(); accum > 0 { stateProp = &commonpb.TimeSkippingStatePropagation{ InitialSkippedDuration: durationpb.New(accum), } } - - if ff := source.GetTimeSkippingInfo().GetFastForwardInfo(); ff != nil && !ff.GetHasReached() { + if util.HasPendingFastForward() { if stateProp == nil { stateProp = &commonpb.TimeSkippingStatePropagation{} } - stateProp.FastForwardTargetTime = ff.GetTargetTime() + stateProp.FastForwardTargetTime = tsi.GetFastForwardInfo().GetTargetTime() + } + // The skip count is scoped to a session; once time skipping is disabled the session has + // ended, so the count does not carry to the next run. + if skipCount := tsi.GetSessionSkipCount(); skipCount > 0 && util.IsEnabled() { + if stateProp == nil { + stateProp = &commonpb.TimeSkippingStatePropagation{} + } + stateProp.InitialSkipCount = skipCount } return newTSC, stateProp } -// propagateTimeSkippingToChild makes sure the start time of the child workflow execution -// is shifted forward by the accumulated skipped duration. -// FastForward is never propagated to children. +// propagateTimeSkippingToChild snapshots the parent's time skipping into a child workflow. The +// child shares the parent's virtual clock (its start time is shifted forward by the parent's +// accumulated skipped duration) but is otherwise a fresh execution: +// - FastForward is per-execution and is never propagated to children. +// - The child starts a fresh skip session: SessionSkipCount is not propagated (starts at 0). +// - It does inherit the parent's per-session budget (MaxSkipPerSession). Children start +// internally, bypassing the frontend that populates the default budget, so inheriting the +// parent's already-resolved limit avoids a 0 that would disable skipping on the first skip. func propagateTimeSkippingToChild( source *persistencespb.WorkflowExecutionInfo, ) (*commonpb.TimeSkippingConfig, *commonpb.TimeSkippingStatePropagation) { - accum := accumulatedSkippedDuration(source) + tsi := source.GetTimeSkippingInfo() + tsc := tsi.GetConfig() + util := NewTimeSkippingInfoUtil(tsi) + + accum := util.GetAccumulatedSkippedDuration() var stateProp *commonpb.TimeSkippingStatePropagation if accum > 0 { stateProp = &commonpb.TimeSkippingStatePropagation{ InitialSkippedDuration: durationpb.New(accum), + InitialSkipCount: 0, } } - enabled := source.GetTimeSkippingInfo().GetConfig().GetEnabled() - disablePropagation := source.GetTimeSkippingInfo().GetConfig().GetDisablePropagation() - if !enabled || disablePropagation { + // only propagates state + if !util.IsEnabled() || tsc.GetDisablePropagation() { return nil, stateProp } + // propagate both config and state return &commonpb.TimeSkippingConfig{ - Enabled: enabled, + Enabled: true, + MaxSessionSkipCount: tsc.GetMaxSessionSkipCount(), }, stateProp } -func accumulatedSkippedDuration(source *persistencespb.WorkflowExecutionInfo) time.Duration { - return source.GetTimeSkippingInfo().GetAccumulatedSkippedDuration().AsDuration() -} - // ============================================================================= // Time Skipping Runtime Time Impacts // ============================================================================= @@ -201,7 +222,7 @@ func (ms *MutableStateImpl) wrapTimeSourceWithTimeSkipping() { } func (ms *MutableStateImpl) accumulatedSkippedDuration() time.Duration { - return accumulatedSkippedDuration(ms.executionInfo) + return NewTimeSkippingInfoUtil(ms.GetExecutionInfo().GetTimeSkippingInfo()).GetAccumulatedSkippedDuration() } // ============================================================================= @@ -263,14 +284,21 @@ func (t *timeSkippingTransition) GateByFastForward(ff *persistencespb.FastForwar // Time Skipping Utility Functions // ============================================================================= +func NewTimeSkippingInfoUtil(tsi *persistencespb.TimeSkippingInfo) *TimeSkippingInfoUtil { + return &TimeSkippingInfoUtil{tsi: tsi} +} + // TimeSkippingInfoUtil provides read-only helpers over a TimeSkippingInfo, guarding against nil // info/config/fast-forward so callers don't have to repeat the nil checks. type TimeSkippingInfoUtil struct { tsi *persistencespb.TimeSkippingInfo } -func NewTimeSkippingInfoUtil(tsi *persistencespb.TimeSkippingInfo) *TimeSkippingInfoUtil { - return &TimeSkippingInfoUtil{tsi: tsi} +func (util *TimeSkippingInfoUtil) GetAccumulatedSkippedDuration() time.Duration { + if util == nil || util.tsi == nil { + return 0 + } + return util.tsi.GetAccumulatedSkippedDuration().AsDuration() } // HasPendingFastForward reports whether time skipping is enabled and carries a fast-forward that has @@ -291,12 +319,39 @@ func (util *TimeSkippingInfoUtil) HasPendingFastForward() bool { // IsEnabled reports whether time skipping is enabled. Nil-safe: a nil util, nil info, or nil config // all yield false. func (util *TimeSkippingInfoUtil) IsEnabled() bool { - if util == nil { + if util == nil || util.tsi == nil { return false } return util.tsi.GetConfig().GetEnabled() } +func (util *TimeSkippingInfoUtil) ToDescribeInfo(currentTime time.Time) *commonpb.TimeSkippingInfo { + if util == nil || util.tsi == nil { + return nil + } + return &commonpb.TimeSkippingInfo{ + CurrentTime: timestamppb.New(currentTime), + EffectiveConfig: common.CloneProto(util.tsi.GetConfig()), + } +} + +func (util *TimeSkippingInfoUtil) ToFastForwardInfo() *commonpb.TimeSkippingFastForwardInfo { + if util == nil || util.tsi == nil { + return nil + } + ff := util.tsi.GetFastForwardInfo() + if ff == nil { + return nil + } + config := util.tsi.GetConfig() + return &commonpb.TimeSkippingFastForwardInfo{ + TargetTime: ff.GetTargetTime(), + HasCompleted: ff.GetHasReached(), + FastForwardId: config.GetFastForwardConfig().GetId(), + FastForwardDuration: config.GetFastForwardConfig().GetDuration(), + } +} + // ============================================================================= // Time Skipping Runtime Methods for Workflow-based Executions // ============================================================================= @@ -479,7 +534,7 @@ func (ms *MutableStateImpl) AddWorkflowExecutionTimeSkippingTransitionedEvent( } func (ms *MutableStateImpl) ApplyWorkflowExecutionTimeSkippingTransitionedEvent(ctx context.Context, event *historypb.HistoryEvent) error { - + // todo: merge with chasm time skipping attr := event.GetWorkflowExecutionTimeSkippingTransitionedEventAttributes() tsi := ms.executionInfo.GetTimeSkippingInfo() @@ -500,9 +555,16 @@ func (ms *MutableStateImpl) ApplyWorkflowExecutionTimeSkippingTransitionedEvent( tsi.AccumulatedSkippedDuration = durationpb.New(asd) } // update enabled state - tsi.Config.Enabled = !attr.GetDisabledAfterFastForward() if attr.GetDisabledAfterFastForward() && tsi.GetFastForwardInfo() != nil { - tsi.FastForwardInfo.HasReached = true + reachedFFInfo := tsi.GetFastForwardInfo() + reachedFFInfo.HasReached = true + ms.setAndStampFastForwardInfo(reachedFFInfo) + tsi.Config.Enabled = false + } + // update skip + tsi.SessionSkipCount += 1 + if tsi.SessionSkipCount >= tsi.Config.GetMaxSessionSkipCount() && tsi.Config.Enabled { + tsi.Config.Enabled = false } ms.timeSkippingInfoUpdated = true diff --git a/service/history/workflow/timeskipping_test.go b/service/history/workflow/timeskipping_test.go index 3d8cdc3fb53..8a404b3aed1 100644 --- a/service/history/workflow/timeskipping_test.go +++ b/service/history/workflow/timeskipping_test.go @@ -30,8 +30,8 @@ func (s *mutableStateSuite) TestPropagateTimeSkippingToNextRun_FastForwardInfo() src := &persistencespb.WorkflowExecutionInfo{ TimeSkippingInfo: &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(3 * time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(3 * time.Hour)}, }, AccumulatedSkippedDuration: durationpb.New(time.Hour), FastForwardInfo: &persistencespb.FastForwardInfo{ @@ -51,8 +51,8 @@ func (s *mutableStateSuite) TestPropagateTimeSkippingToNextRun_FastForwardInfo() src := &persistencespb.WorkflowExecutionInfo{ TimeSkippingInfo: &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: false, - FastForward: durationpb.New(3 * time.Hour), + Enabled: false, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(3 * time.Hour)}, }, AccumulatedSkippedDuration: durationpb.New(time.Hour), FastForwardInfo: &persistencespb.FastForwardInfo{ @@ -72,8 +72,8 @@ func (s *mutableStateSuite) TestPropagateTimeSkippingToNextRun_FastForwardInfo() src := &persistencespb.WorkflowExecutionInfo{ TimeSkippingInfo: &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: false, - FastForward: durationpb.New(3 * time.Hour), + Enabled: false, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(3 * time.Hour)}, }, AccumulatedSkippedDuration: durationpb.New(time.Hour), FastForwardInfo: &persistencespb.FastForwardInfo{ @@ -107,8 +107,8 @@ func (s *mutableStateSuite) TestPropagateTimeSkippingToNextRun_FastForwardInfo() src := &persistencespb.WorkflowExecutionInfo{ TimeSkippingInfo: &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(3 * time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(3 * time.Hour)}, }, // no AccumulatedSkippedDuration FastForwardInfo: &persistencespb.FastForwardInfo{ @@ -138,23 +138,68 @@ func (s *mutableStateSuite) TestPropagateTimeSkippingToNextRun_FastForwardInfo() s.Nil(stateProp, "no accumulated time and no active fast-forward means nothing to propagate") }) - s.Run("NilTimeSkippingInfoPropagatesNothing", func() { + s.Run("NoTimeSkippingInfoPropagatesNothing", func() { tsc, stateProp := propagateTimeSkippingToNextRun(&persistencespb.WorkflowExecutionInfo{}) s.Nil(tsc) s.Nil(stateProp) }) + + s.Run("SkipCountAloneStillPropagates", func() { + tsc, stateProp := propagateTimeSkippingToNextRun(&persistencespb.WorkflowExecutionInfo{ + TimeSkippingInfo: &persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{Enabled: true, MaxSessionSkipCount: 50}, + SessionSkipCount: 2, + }, + }) + s.Require().NotNil(tsc) + s.Require().NotNil(stateProp) + s.Nil(stateProp.GetInitialSkippedDuration()) + s.Equal(int32(2), stateProp.GetInitialSkipCount()) + s.Equal(int32(50), tsc.MaxSessionSkipCount) + }) + + s.Run("FullPropagationOfAllTimeSippingStates", func() { + tsc, stateProp := propagateTimeSkippingToNextRun(&persistencespb.WorkflowExecutionInfo{ + TimeSkippingInfo: &persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{Enabled: true, MaxSessionSkipCount: 50}, + AccumulatedSkippedDuration: durationpb.New(time.Hour), + SessionSkipCount: 3, + }, + }) + s.Require().NotNil(tsc) + s.Equal(int32(50), tsc.GetMaxSessionSkipCount()) + s.Require().NotNil(stateProp) + s.Equal(int32(3), stateProp.GetInitialSkipCount()) + }) + + s.Run("SessionSkipCountWontPropagateWhenTimeSkippingDisabled", func() { + tsc, stateProp := propagateTimeSkippingToNextRun(&persistencespb.WorkflowExecutionInfo{ + TimeSkippingInfo: &persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{Enabled: false, MaxSessionSkipCount: 50}, + AccumulatedSkippedDuration: durationpb.New(time.Hour), + SessionSkipCount: 3, + }, + }) + s.Nil(tsc) + s.Require().NotNil(stateProp) + s.Equal(int32(0), stateProp.GetInitialSkipCount()) + }) } func (s *mutableStateSuite) TestSnapshotTimeSkippingInfo_ForChildWorkflows() { + accumSkip := time.Hour + sessionMaxSkipCount := int32(10) newSource := func() *persistencespb.WorkflowExecutionInfo { s.mutableState.timeSource = clock.NewEventTimeSource() return &persistencespb.WorkflowExecutionInfo{ TimeSkippingInfo: &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(3 * time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(3 * time.Hour)}, + MaxSessionSkipCount: sessionMaxSkipCount, }, - AccumulatedSkippedDuration: durationpb.New(time.Hour), + AccumulatedSkippedDuration: durationpb.New(accumSkip), + SessionSkipCount: 3, FastForwardInfo: &persistencespb.FastForwardInfo{ TargetTime: timestamppb.New(s.mutableState.timeSource.Now().Add(3 * time.Hour)), HasReached: false, @@ -163,12 +208,25 @@ func (s *mutableStateSuite) TestSnapshotTimeSkippingInfo_ForChildWorkflows() { } } - s.Run("child workflows have no fast-forward", func() { - tsc, propagatedState := propagateTimeSkippingToChild(newSource()) + s.Run("nil-safe check", func() { + tsc, propagatedState := propagateTimeSkippingToChild(&persistencespb.WorkflowExecutionInfo{}) + s.Nil(tsc) + s.Nil(propagatedState) + }) + + s.Run("a valid full propagation of config and virtual time", func() { + src := newSource() + tsc, propagatedState := propagateTimeSkippingToChild(src) s.Require().NotNil(tsc) s.True(tsc.GetEnabled()) - s.Nil(tsc.GetFastForward()) - s.Equal(time.Hour, propagatedState.GetInitialSkippedDuration().AsDuration()) + // no fast-forward + s.Nil(tsc.GetFastForwardConfig().GetDuration()) + // has virtual time + s.Equal(accumSkip, propagatedState.GetInitialSkippedDuration().AsDuration()) + // has config of max skip + s.Equal(sessionMaxSkipCount, tsc.GetMaxSessionSkipCount()) + // no accumulated session skip + s.Equal(int32(0), propagatedState.GetInitialSkipCount()) }) s.Run("child workflow propagation can be turned off", func() { @@ -177,8 +235,9 @@ func (s *mutableStateSuite) TestSnapshotTimeSkippingInfo_ForChildWorkflows() { tsc, propagatedState := propagateTimeSkippingToChild(src) s.Nil(tsc) s.Require().NotNil(propagatedState) - s.Equal(time.Hour, propagatedState.GetInitialSkippedDuration().AsDuration(), - "virtual time is always propagated, even when config propagation is disabled") + s.Equal(accumSkip, propagatedState.GetInitialSkippedDuration().AsDuration()) + s.Equal(int32(0), propagatedState.GetInitialSkipCount()) + s.Nil(propagatedState.GetFastForwardTargetTime()) }) s.Run("nil config still propagates virtual time", func() { @@ -187,12 +246,13 @@ func (s *mutableStateSuite) TestSnapshotTimeSkippingInfo_ForChildWorkflows() { tsc, propagatedState := propagateTimeSkippingToChild(src) s.Nil(tsc) s.Require().NotNil(propagatedState) + s.Equal(int32(0), propagatedState.GetInitialSkipCount()) s.Equal(time.Hour, propagatedState.GetInitialSkippedDuration().AsDuration(), "virtual time is always propagated, even when config propagation is disabled") s.Nil(propagatedState.GetFastForwardTargetTime()) }) - s.Run("disablePropagation still propagates virtual time", func() { + s.Run("disabled propagation still propagates virtual time", func() { src := newSource() src.TimeSkippingInfo.Config.Enabled = false tsc, propagatedState := propagateTimeSkippingToChild(src) @@ -201,7 +261,7 @@ func (s *mutableStateSuite) TestSnapshotTimeSkippingInfo_ForChildWorkflows() { s.Equal(time.Hour, propagatedState.GetInitialSkippedDuration().AsDuration(), "virtual time is always propagated, even when config propagation is disabled") s.Nil(propagatedState.GetFastForwardTargetTime()) - + s.Equal(int32(0), propagatedState.GetInitialSkipCount()) }) } @@ -360,7 +420,7 @@ func (s *mutableStateSuite) TestInitTimeSkippingInfo() { s.mutableState.timeSource = clock.NewEventTimeSource() baseTime := s.mutableState.timeSource.Now() s.NotPanics(func() { - s.Require().NoError(s.mutableState.initTimeSkippingInfo(nil, nil)) + s.mutableState.initTimeSkippingInfo(nil, nil) }) s.Nil(s.mutableState.executionInfo.TimeSkippingInfo) s.Equal(baseTime, s.mutableState.Now()) @@ -373,10 +433,10 @@ func (s *mutableStateSuite) TestInitTimeSkippingInfo() { nextVT := &persistencespb.VersionedTransition{NamespaceFailoverVersion: tests.Version, TransitionCount: 6} s.mutableState.executionInfo.TransitionHistory = []*persistencespb.VersionedTransition{currentVT} cfg := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(3 * time.Hour)} + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(3 * time.Hour)}} - s.Require().NoError(s.mutableState.initTimeSkippingInfo(cfg, nil)) + s.mutableState.initTimeSkippingInfo(cfg, nil) s.Equal(baseTime, s.mutableState.Now()) tsi := s.mutableState.executionInfo.GetTimeSkippingInfo() s.Require().NotNil(tsi) @@ -387,7 +447,7 @@ func (s *mutableStateSuite) TestInitTimeSkippingInfo() { s.Require().Equal(baseTime.Add(3*time.Hour).UTC(), tsi.GetFastForwardInfo().GetTargetTime().AsTime()) // the fast-forward records the current versioned transition - s.Require().True(proto.Equal(nextVT, tsi.GetFastForwardInfo().GetLastUpdateVersionedTransition())) + s.Require().True(proto.Equal(nextVT, tsi.GetFastForwardInfoLastUpdateVersionedTransition())) }) s.Run("InitWithPropagation_ForExecutionsWithTSStartedByPropagation", func() { @@ -402,14 +462,14 @@ func (s *mutableStateSuite) TestInitTimeSkippingInfo() { cfg := &commonpb.TimeSkippingConfig{ Enabled: true, - FastForward: durationpb.New(fastForward), + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForward)}, DisablePropagation: true, } propagation := &commonpb.TimeSkippingStatePropagation{ InitialSkippedDuration: durationpb.New(hasSkipped), FastForwardTargetTime: timestamppb.New(targetTime), } - s.Require().NoError(s.mutableState.initTimeSkippingInfo(cfg, propagation)) + s.mutableState.initTimeSkippingInfo(cfg, propagation) tsi := s.mutableState.executionInfo.GetTimeSkippingInfo() s.Require().NotNil(tsi) @@ -418,20 +478,44 @@ func (s *mutableStateSuite) TestInitTimeSkippingInfo() { s.Equal(targetTime.UTC(), tsi.GetFastForwardInfo().GetTargetTime().AsTime()) }) + + s.Run("SessionSkipCountSeededFromPropagation", func() { + s.mutableState.timeSource = clock.NewEventTimeSource() + s.mutableState.executionInfo.TimeSkippingInfo = nil + cfg := &commonpb.TimeSkippingConfig{Enabled: true} + propagation := &commonpb.TimeSkippingStatePropagation{InitialSkipCount: 4} + + s.mutableState.initTimeSkippingInfo(cfg, propagation) + + tsi := s.mutableState.executionInfo.GetTimeSkippingInfo() + s.Require().NotNil(tsi) + s.Equal(int32(4), tsi.GetSessionSkipCount()) + }) + + s.Run("SessionSkipCountDefaultsToZeroWithoutPropagation", func() { + s.mutableState.timeSource = clock.NewEventTimeSource() + s.mutableState.executionInfo.TimeSkippingInfo = nil + cfg := &commonpb.TimeSkippingConfig{Enabled: true} + + s.mutableState.initTimeSkippingInfo(cfg, nil) + + tsi := s.mutableState.executionInfo.GetTimeSkippingInfo() + s.Require().NotNil(tsi) + s.Equal(int32(0), tsi.GetSessionSkipCount()) + }) } func (s *mutableStateSuite) TestUpdateTimeSkippingInfo() { // update assumes the TS info was already initialized; updating a workflow that never - // started time skipping is an internal error and must not initialize it as a side effect. - s.Run("ErrorWhenTimeSkippingInfoNil", func() { + // started time skipping is a no-op and must not initialize it as a side effect. + s.Run("NoopWhenTimeSkippingInfoNil", func() { s.mutableState.executionInfo.TimeSkippingInfo = nil s.mutableState.timeSkippingInfoUpdated = false - err := s.mutableState.updateTimeSkippingInfo(&commonpb.TimeSkippingConfig{Enabled: true}) - s.Error(err) + s.mutableState.updateTimeSkippingInfo(&commonpb.TimeSkippingConfig{Enabled: true}) s.Nil(s.mutableState.executionInfo.GetTimeSkippingInfo(), "must not initialize TS info on update") - s.False(s.mutableState.timeSkippingInfoUpdated, "must not mark updated on a failed update") + s.False(s.mutableState.timeSkippingInfoUpdated, "must not mark updated when there is nothing to update") }) s.Run("UpdateTimeSkippingInfo_UpdateWithNil", func() { @@ -439,23 +523,26 @@ func (s *mutableStateSuite) TestUpdateTimeSkippingInfo() { baseTime := s.mutableState.timeSource.Now() currentTSI := &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour)}, }, AccumulatedSkippedDuration: durationpb.New(time.Hour), FastForwardInfo: &persistencespb.FastForwardInfo{ - TargetTime: timestamppb.New(baseTime.Add(time.Hour)), - LastUpdateVersionedTransition: &persistencespb.VersionedTransition{NamespaceFailoverVersion: 1, TransitionCount: 7}, - HasReached: false, + TargetTime: timestamppb.New(baseTime.Add(time.Hour)), + HasReached: false, }, + FastForwardInfoLastUpdateVersionedTransition: &persistencespb.VersionedTransition{NamespaceFailoverVersion: 1, TransitionCount: 7}, } s.mutableState.executionInfo.TimeSkippingInfo = currentTSI s.mutableState.timeSkippingInfoUpdated = false - s.Require().NoError(s.mutableState.updateTimeSkippingInfo(nil)) + s.mutableState.updateTimeSkippingInfo(nil) newTSI := s.mutableState.executionInfo.GetTimeSkippingInfo() s.Require().NotNil(newTSI) s.Nil(newTSI.GetConfig()) s.Nil(newTSI.GetFastForwardInfo()) + // clearing the fast-forward still stamps the versioned transition; the field is never cleared + s.Require().NotNil(newTSI.GetFastForwardInfoLastUpdateVersionedTransition()) + s.Equal(s.mutableState.NextTransitionCount(), newTSI.GetFastForwardInfoLastUpdateVersionedTransition().GetTransitionCount()) s.Equal(currentTSI.GetAccumulatedSkippedDuration(), newTSI.GetAccumulatedSkippedDuration()) s.True(s.mutableState.timeSkippingInfoUpdated) }) @@ -477,10 +564,10 @@ func (s *mutableStateSuite) TestUpdateTimeSkippingInfo() { // new config newConfig := &commonpb.TimeSkippingConfig{ Enabled: true, - FastForward: durationpb.New(2 * time.Hour), + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(2 * time.Hour)}, DisablePropagation: true, } - s.Require().NoError(s.mutableState.updateTimeSkippingInfo(newConfig)) + s.mutableState.updateTimeSkippingInfo(newConfig) newTSI := s.mutableState.executionInfo.GetTimeSkippingInfo() s.Require().NotNil(newTSI) @@ -488,7 +575,7 @@ func (s *mutableStateSuite) TestUpdateTimeSkippingInfo() { s.Require().NotNil(newTSI.GetFastForwardInfo()) // re-installing the fast-forward records the current versioned transition, so a task // emitted here validates against a stable reference after a failover. - s.True(proto.Equal(nextVT, newTSI.GetFastForwardInfo().GetLastUpdateVersionedTransition())) + s.True(proto.Equal(nextVT, newTSI.GetFastForwardInfoLastUpdateVersionedTransition())) s.Equal(baseTime.Add(2*time.Hour).UTC(), newTSI.GetFastForwardInfo().GetTargetTime().AsTime()) s.False(newTSI.GetFastForwardInfo().GetHasReached()) s.Equal(time.Hour, newTSI.GetAccumulatedSkippedDuration().AsDuration()) @@ -502,31 +589,31 @@ func (s *mutableStateSuite) TestUpdateTimeSkippingInfo() { s.mutableState.executionInfo.TransitionHistory = []*persistencespb.VersionedTransition{currentVT} currentTSI := persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour)}, }, AccumulatedSkippedDuration: durationpb.New(time.Hour), FastForwardInfo: &persistencespb.FastForwardInfo{ - TargetTime: timestamppb.New(baseTime.Add(time.Hour)), - LastUpdateVersionedTransition: &persistencespb.VersionedTransition{NamespaceFailoverVersion: 3, TransitionCount: 5}, - HasReached: false, + TargetTime: timestamppb.New(baseTime.Add(time.Hour)), + HasReached: false, }, + FastForwardInfoLastUpdateVersionedTransition: &persistencespb.VersionedTransition{NamespaceFailoverVersion: 3, TransitionCount: 5}, } s.mutableState.executionInfo.TimeSkippingInfo = ¤tTSI // update with new config with a new FF tsc2 := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(2 * time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(2 * time.Hour)}, } - s.Require().NoError(s.mutableState.updateTimeSkippingInfo(tsc2)) + s.mutableState.updateTimeSkippingInfo(tsc2) tsc2TSI := s.mutableState.executionInfo.GetTimeSkippingInfo() s.Require().NotNil(tsc2TSI) s.True(proto.Equal(tsc2, tsc2TSI.GetConfig())) s.Require().NotNil(tsc2TSI.GetFastForwardInfo()) // re-applying records the current versioned transition, replacing the previous one - s.True(proto.Equal(nextVT, tsc2TSI.GetFastForwardInfo().GetLastUpdateVersionedTransition())) + s.True(proto.Equal(nextVT, tsc2TSI.GetFastForwardInfoLastUpdateVersionedTransition())) s.Equal(baseTime.Add(2*time.Hour).UTC(), tsc2TSI.GetFastForwardInfo().GetTargetTime().AsTime()) s.False(tsc2TSI.GetFastForwardInfo().GetHasReached()) s.Equal(time.Hour, tsc2TSI.GetAccumulatedSkippedDuration().AsDuration()) @@ -535,14 +622,35 @@ func (s *mutableStateSuite) TestUpdateTimeSkippingInfo() { tsc3 := &commonpb.TimeSkippingConfig{ Enabled: false, } - s.Require().NoError(s.mutableState.updateTimeSkippingInfo(tsc3)) + s.mutableState.updateTimeSkippingInfo(tsc3) tsc3TSI := s.mutableState.executionInfo.GetTimeSkippingInfo() s.Require().NotNil(tsc3TSI) s.True(proto.Equal(tsc3, tsc3TSI.GetConfig())) s.Nil(tsc3TSI.GetFastForwardInfo()) + s.True(proto.Equal(nextVT, tsc3TSI.GetFastForwardInfoLastUpdateVersionedTransition()), + "clearing the fast-forward re-stamps the versioned transition rather than clearing it") s.Equal(time.Hour, tsc3TSI.GetAccumulatedSkippedDuration().AsDuration()) }) + + // Updating the config restarts the skip session: the per-session skip counter from the + // previous session must be cleared. + s.Run("ClearsSessionSkipCount", func() { + s.mutableState.timeSource = clock.NewEventTimeSource() + s.mutableState.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{ + Enabled: false, + MaxSessionSkipCount: 10, + }, + SessionSkipCount: 7, + } + + s.mutableState.updateTimeSkippingInfo(&commonpb.TimeSkippingConfig{Enabled: true, MaxSessionSkipCount: 10}) + + tsi := s.mutableState.executionInfo.GetTimeSkippingInfo() + s.Require().NotNil(tsi) + s.Equal(int32(0), tsi.GetSessionSkipCount()) + }) } // TestWrapExecutionTimes covers the three invariants of wrapExecutionTimes: @@ -593,8 +701,8 @@ func (s *mutableStateSuite) TestApplyFastForward() { s.mutableState.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(fastForwardDuration)}, + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForwardDuration)}}, AccumulatedSkippedDuration: durationpb.New(time.Hour), } s.mutableState.applyFastForward(nil) @@ -602,7 +710,7 @@ func (s *mutableStateSuite) TestApplyFastForward() { s.Require().NotNil(fastForward) s.False(fastForward.GetHasReached()) s.WithinDuration(fastForward.GetTargetTime().AsTime(), baseTime.Add(fastForwardDuration), 1*time.Second) - s.True(proto.Equal(nextVT, fastForward.GetLastUpdateVersionedTransition())) + s.True(proto.Equal(nextVT, s.mutableState.executionInfo.TimeSkippingInfo.GetFastForwardInfoLastUpdateVersionedTransition())) }) s.Run("FastForward_WithPropagatedTargetTime", func() { @@ -611,8 +719,8 @@ func (s *mutableStateSuite) TestApplyFastForward() { s.mutableState.timeSource = clock.NewEventTimeSource().Update(fixed) s.mutableState.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(3 * time.Hour)}, + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(3 * time.Hour)}}, AccumulatedSkippedDuration: durationpb.New(time.Hour), } s.mutableState.applyFastForward(timestamppb.New(propagatedTarget)) @@ -620,7 +728,7 @@ func (s *mutableStateSuite) TestApplyFastForward() { s.Require().NotNil(fastForward) s.Equal(propagatedTarget, fastForward.GetTargetTime().AsTime(), "propagated target used directly, not recomputed from ff duration") - s.NotNil(fastForward.GetLastUpdateVersionedTransition()) + s.NotNil(s.mutableState.executionInfo.TimeSkippingInfo.GetFastForwardInfoLastUpdateVersionedTransition()) }) s.Run("FastForward_NilDuration_NoOp", func() { @@ -633,33 +741,53 @@ func (s *mutableStateSuite) TestApplyFastForward() { }) s.Run("FastForward_ClearsExistingInfo", func() { + staleVT := &persistencespb.VersionedTransition{NamespaceFailoverVersion: 1, TransitionCount: 7} s.mutableState.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{Enabled: false}, FastForwardInfo: &persistencespb.FastForwardInfo{ - TargetTime: timestamppb.New(s.mutableState.Now().Add(time.Hour)), - LastUpdateVersionedTransition: &persistencespb.VersionedTransition{NamespaceFailoverVersion: 1, TransitionCount: 7}, + TargetTime: timestamppb.New(s.mutableState.Now().Add(time.Hour)), }, + FastForwardInfoLastUpdateVersionedTransition: staleVT, } s.mutableState.applyFastForward(nil) - s.Nil(s.mutableState.executionInfo.TimeSkippingInfo.GetFastForwardInfo()) + tsi := s.mutableState.executionInfo.TimeSkippingInfo + s.Nil(tsi.GetFastForwardInfo()) + ffVT := tsi.GetFastForwardInfoLastUpdateVersionedTransition() + s.Require().NotNil(ffVT) + s.False(proto.Equal(staleVT, ffVT), "removing the fast-forward re-stamps the versioned transition") + s.Equal(s.mutableState.NextTransitionCount(), ffVT.GetTransitionCount()) + }) + + s.Run("FastForward_AlreadyNil_LeavesVersionedTransitionUntouched", func() { + staleVT := &persistencespb.VersionedTransition{NamespaceFailoverVersion: 1, TransitionCount: 7} + s.mutableState.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{Enabled: false}, + FastForwardInfoLastUpdateVersionedTransition: staleVT, + } + s.mutableState.applyFastForward(nil) + tsi := s.mutableState.executionInfo.TimeSkippingInfo + s.Nil(tsi.GetFastForwardInfo()) + s.True(proto.Equal(staleVT, tsi.GetFastForwardInfoLastUpdateVersionedTransition())) }) s.Run("FastForward_OverridesExistingInfo", func() { fixed := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC) s.mutableState.timeSource = clock.NewEventTimeSource().Update(fixed) s.mutableState.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ - Config: &commonpb.TimeSkippingConfig{Enabled: true, FastForward: durationpb.New(time.Hour)}, + Config: &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour)}}, FastForwardInfo: &persistencespb.FastForwardInfo{ - TargetTime: timestamppb.New(fixed), // stale bound that must be overridden - LastUpdateVersionedTransition: &persistencespb.VersionedTransition{NamespaceFailoverVersion: 3, TransitionCount: 5}, + TargetTime: timestamppb.New(fixed), // stale bound that must be overridden }, + FastForwardInfoLastUpdateVersionedTransition: &persistencespb.VersionedTransition{NamespaceFailoverVersion: 3, TransitionCount: 5}, } s.mutableState.applyFastForward(nil) - ff := s.mutableState.executionInfo.TimeSkippingInfo.GetFastForwardInfo() + tsi := s.mutableState.executionInfo.TimeSkippingInfo + ff := tsi.GetFastForwardInfo() s.Require().NotNil(ff) s.Equal(fixed.Add(time.Hour), ff.GetTargetTime().AsTime(), "stale bound is replaced by now+FastForward") - s.Equal(tests.Version, ff.GetLastUpdateVersionedTransition().NamespaceFailoverVersion) - s.Equal(s.mutableState.NextTransitionCount(), ff.GetLastUpdateVersionedTransition().TransitionCount) + ffVT := tsi.GetFastForwardInfoLastUpdateVersionedTransition() + s.Equal(tests.Version, ffVT.GetNamespaceFailoverVersion()) + s.Equal(s.mutableState.NextTransitionCount(), ffVT.GetTransitionCount()) }) } @@ -1245,6 +1373,90 @@ func (s *mutableStateSuite) TestApplyWorkflowExecutionTimeSkippingTransitionedEv accumulated := s.mutableState.GetExecutionInfo().TimeSkippingInfo.AccumulatedSkippedDuration s.Require().Equal(2*time.Hour, accumulated.AsDuration()) }) + + // Regression: a fast-forward-completion transition must both disable the config and record + // the fast-forward as reached. A previous bug left Enabled unchanged on this path. + s.Run("FastForwardCompletionDisables", func() { + targetTime := baseTime.Add(2 * time.Hour) + staleVT := &persistencespb.VersionedTransition{NamespaceFailoverVersion: 1, TransitionCount: 3} + s.mutableState.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{ + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(2 * time.Hour)}, + }, + FastForwardInfo: &persistencespb.FastForwardInfo{ + TargetTime: timestamppb.New(targetTime), + HasReached: false, + }, + FastForwardInfoLastUpdateVersionedTransition: staleVT, + } + + err := s.mutableState.ApplyWorkflowExecutionTimeSkippingTransitionedEvent( + context.Background(), + makeEvent(baseTime, &targetTime, true), + ) + s.Require().NoError(err) + + tsi := s.mutableState.GetExecutionInfo().TimeSkippingInfo + s.Require().False(tsi.GetConfig().GetEnabled()) + s.Require().True(tsi.GetFastForwardInfo().GetHasReached()) + // reaching the target is an update to the fast-forward, so the stamp advances too + ffVT := tsi.GetFastForwardInfoLastUpdateVersionedTransition() + s.Require().NotNil(ffVT) + s.False(proto.Equal(staleVT, ffVT)) + s.Equal(s.mutableState.NextTransitionCount(), ffVT.GetTransitionCount()) + }) + + // Every applied transition increments SessionSkipCount by one, and once the count reaches + // the per-session cap the config is disabled. + s.Run("SessionSkipCountIncrementsUntilCapDisables", func() { + s.mutableState.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{ + Enabled: true, + MaxSessionSkipCount: 2, + }, + } + targetTime := baseTime.Add(time.Hour) + + // first skip: count 1, below cap, stays enabled + err := s.mutableState.ApplyWorkflowExecutionTimeSkippingTransitionedEvent( + context.Background(), + makeEvent(baseTime, &targetTime, false), + ) + s.Require().NoError(err) + tsi := s.mutableState.GetExecutionInfo().TimeSkippingInfo + s.Require().Equal(int32(1), tsi.GetSessionSkipCount()) + s.Require().True(tsi.GetConfig().GetEnabled()) + + // second skip: count reaches the cap of 2, disables + err = s.mutableState.ApplyWorkflowExecutionTimeSkippingTransitionedEvent( + context.Background(), + makeEvent(baseTime, &targetTime, false), + ) + s.Require().NoError(err) + s.Require().Equal(int32(2), tsi.GetSessionSkipCount()) + s.Require().False(tsi.GetConfig().GetEnabled()) + }) + + // A MaxSkipPerSession of 0 caps on the very first skip (1 >= 0), disabling immediately. + // The frontend guarantees a populated config carries at least 1, so a 0 reaching the + // runtime (e.g. a config that never had the cap populated) kills skipping right away. + s.Run("SessionSkipCountCapsImmediatelyWhenMaxUnset", func() { + s.mutableState.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{Enabled: true}, + } + targetTime := baseTime.Add(time.Hour) + + err := s.mutableState.ApplyWorkflowExecutionTimeSkippingTransitionedEvent( + context.Background(), + makeEvent(baseTime, &targetTime, false), + ) + s.Require().NoError(err) + + tsi := s.mutableState.GetExecutionInfo().TimeSkippingInfo + s.Require().Equal(int32(1), tsi.GetSessionSkipCount()) + s.Require().False(tsi.GetConfig().GetEnabled()) + }) } func (s *mutableStateSuite) TestWrapTimeSourceWithTimeSkipping() { @@ -1338,6 +1550,14 @@ func (s *mutableStateSuite) TestTimeSkippingInfoUtil() { s.False(util.IsEnabled()) }) + s.Run("NilReceiverSafe", func() { + var util *TimeSkippingInfoUtil + s.False(util.IsEnabled()) + s.False(util.HasPendingFastForward()) + s.Zero(util.GetAccumulatedSkippedDuration()) + s.Nil(util.ToDescribeInfo(time.Now())) + }) + s.Run("NilFFISafe", func() { s.mutableState.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{Enabled: true}, @@ -1347,16 +1567,32 @@ func (s *mutableStateSuite) TestTimeSkippingInfoUtil() { s.False(util.HasPendingFastForward()) }) + s.Run("NoPendingFastForwardWhenTargetTimeNil", func() { + util := NewTimeSkippingInfoUtil(&persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{Enabled: true}, + FastForwardInfo: &persistencespb.FastForwardInfo{HasReached: false, TargetTime: nil}, + }) + s.False(util.HasPendingFastForward()) + }) + + s.Run("NoPendingFastForwardWhenTargetTimeZero", func() { + util := NewTimeSkippingInfoUtil(&persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{Enabled: true}, + FastForwardInfo: &persistencespb.FastForwardInfo{HasReached: false, TargetTime: timestamppb.New(time.Time{})}, + }) + s.False(util.HasPendingFastForward()) + }) + s.Run("NoPendingFastForwardWhenHasReached", func() { s.mutableState.executionInfo.TimeSkippingInfo = &persistencespb.TimeSkippingInfo{ Config: &commonpb.TimeSkippingConfig{Enabled: true}, FastForwardInfo: &persistencespb.FastForwardInfo{ HasReached: true, TargetTime: timestamppb.New(time.Now()), - LastUpdateVersionedTransition: &persistencespb.VersionedTransition{ - TransitionCount: 1, - NamespaceFailoverVersion: 1, - }, + }, + FastForwardInfoLastUpdateVersionedTransition: &persistencespb.VersionedTransition{ + TransitionCount: 1, + NamespaceFailoverVersion: 1, }, } util := NewTimeSkippingInfoUtil(s.mutableState.executionInfo.TimeSkippingInfo) @@ -1369,10 +1605,10 @@ func (s *mutableStateSuite) TestTimeSkippingInfoUtil() { FastForwardInfo: &persistencespb.FastForwardInfo{ HasReached: false, TargetTime: timestamppb.New(time.Now()), - LastUpdateVersionedTransition: &persistencespb.VersionedTransition{ - TransitionCount: 1, - NamespaceFailoverVersion: 1, - }, + }, + FastForwardInfoLastUpdateVersionedTransition: &persistencespb.VersionedTransition{ + TransitionCount: 1, + NamespaceFailoverVersion: 1, }, } util := NewTimeSkippingInfoUtil(s.mutableState.executionInfo.TimeSkippingInfo) @@ -1385,10 +1621,10 @@ func (s *mutableStateSuite) TestTimeSkippingInfoUtil() { FastForwardInfo: &persistencespb.FastForwardInfo{ HasReached: false, TargetTime: timestamppb.New(time.Now()), - LastUpdateVersionedTransition: &persistencespb.VersionedTransition{ - TransitionCount: 1, - NamespaceFailoverVersion: 1, - }, + }, + FastForwardInfoLastUpdateVersionedTransition: &persistencespb.VersionedTransition{ + TransitionCount: 1, + NamespaceFailoverVersion: 1, }, } util := NewTimeSkippingInfoUtil(s.mutableState.executionInfo.TimeSkippingInfo) @@ -1402,4 +1638,46 @@ func (s *mutableStateSuite) TestTimeSkippingInfoUtil() { util := NewTimeSkippingInfoUtil(s.mutableState.executionInfo.TimeSkippingInfo) s.True(util.IsEnabled()) }) + + s.Run("IsEnabledNilConfig", func() { + util := NewTimeSkippingInfoUtil(&persistencespb.TimeSkippingInfo{Config: nil}) + s.False(util.IsEnabled()) + }) + + s.Run("GetAccumulatedSkippedDuration", func() { + s.Zero(NewTimeSkippingInfoUtil(nil).GetAccumulatedSkippedDuration(), "nil info") + s.Zero(NewTimeSkippingInfoUtil(&persistencespb.TimeSkippingInfo{}).GetAccumulatedSkippedDuration(), "unset duration") + util := NewTimeSkippingInfoUtil(&persistencespb.TimeSkippingInfo{ + AccumulatedSkippedDuration: durationpb.New(90 * time.Minute), + }) + s.Equal(90*time.Minute, util.GetAccumulatedSkippedDuration()) + }) + + s.Run("ToDescribeInfoNilTSI", func() { + s.Nil(NewTimeSkippingInfoUtil(nil).ToDescribeInfo(time.Now())) + }) + + s.Run("ToDescribeInfoEnabled", func() { + now := time.Date(2027, 1, 1, 12, 0, 0, 0, time.UTC) + util := NewTimeSkippingInfoUtil(&persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{Enabled: true}, + }) + info := util.ToDescribeInfo(now) + s.NotNil(info) + s.True(info.GetEffectiveConfig().GetEnabled()) + s.Equal(now, info.GetCurrentTime().AsTime()) + }) + + s.Run("ToDescribeInfoDisabled", func() { + util := NewTimeSkippingInfoUtil(&persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{Enabled: false}, + AccumulatedSkippedDuration: durationpb.New(2 * time.Hour), + }) + msNow := time.Date(2027, 1, 1, 12, 0, 0, 0, time.UTC) + info := util.ToDescribeInfo(msNow) + s.NotNil(info) + s.False(info.GetEffectiveConfig().GetEnabled()) + s.Equal(msNow, info.GetCurrentTime().AsTime()) + }) + } diff --git a/service/history/workflow/transaction_impl.go b/service/history/workflow/transaction_impl.go index 5f0fa73e883..bd7a7c35ffb 100644 --- a/service/history/workflow/transaction_impl.go +++ b/service/history/workflow/transaction_impl.go @@ -6,6 +6,8 @@ import ( commonpb "go.temporal.io/api/common/v1" enumspb "go.temporal.io/api/enums/v1" "go.temporal.io/api/serviceerror" + enumsspb "go.temporal.io/server/api/enums/v1" + persistencespb "go.temporal.io/server/api/persistence/v1" "go.temporal.io/server/chasm" "go.temporal.io/server/common" "go.temporal.io/server/common/log" @@ -13,8 +15,10 @@ import ( "go.temporal.io/server/common/metrics" "go.temporal.io/server/common/namespace" "go.temporal.io/server/common/persistence" + "go.temporal.io/server/common/persistence/transitionhistory" "go.temporal.io/server/service/history/events" historyi "go.temporal.io/server/service/history/interfaces" + "go.temporal.io/server/service/history/notification" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -76,7 +80,7 @@ func (t *TransactionImpl) CreateWorkflowExecution( isWorkflow, ) if persistence.OperationPossiblySucceeded(err) { - NotifyOnExecutionSnapshot(engine, newWorkflowSnapshot) + NotifyOnExecutionSnapshot(engine, archetypeID, newWorkflowSnapshot) } if err != nil { return 0, err @@ -131,9 +135,9 @@ func (t *TransactionImpl) ConflictResolveWorkflowExecution( isWorkflow, ) if persistence.OperationPossiblySucceeded(err) { - NotifyOnExecutionSnapshot(engine, resetWorkflowSnapshot) - NotifyOnExecutionSnapshot(engine, newWorkflowSnapshot) - NotifyOnExecutionMutation(engine, currentWorkflowMutation) + NotifyOnExecutionSnapshot(engine, archetypeID, resetWorkflowSnapshot) + NotifyOnExecutionSnapshot(engine, archetypeID, newWorkflowSnapshot) + NotifyOnExecutionMutation(engine, archetypeID, currentWorkflowMutation) } if err != nil { return 0, 0, 0, err @@ -195,8 +199,8 @@ func (t *TransactionImpl) UpdateWorkflowExecution( isWorkflow, ) if persistence.OperationPossiblySucceeded(err) { - NotifyOnExecutionMutation(engine, currentWorkflowMutation) - NotifyOnExecutionSnapshot(engine, newWorkflowSnapshot) + NotifyOnExecutionMutation(engine, archetypeID, currentWorkflowMutation) + NotifyOnExecutionSnapshot(engine, archetypeID, newWorkflowSnapshot) } if err != nil { return 0, 0, err @@ -233,7 +237,7 @@ func (t *TransactionImpl) SetWorkflowExecution( SetWorkflowSnapshot: *workflowSnapshot, }) if persistence.OperationPossiblySucceeded(err) { - NotifyOnExecutionSnapshot(engine, workflowSnapshot) + NotifyOnExecutionSnapshot(engine, archetypeID, workflowSnapshot) } if err != nil { return err @@ -584,6 +588,7 @@ func setWorkflowExecution( func NotifyOnExecutionSnapshot( engine historyi.Engine, + archetypeID chasm.ArchetypeID, workflowSnapshot *persistence.WorkflowSnapshot, ) { if workflowSnapshot == nil { @@ -597,10 +602,12 @@ func NotifyOnExecutionSnapshot( RunID: workflowSnapshot.ExecutionState.RunId, }, nil) } + notifyFastForwardUpdate(engine, archetypeID, workflowSnapshot.ExecutionInfo, workflowSnapshot.ExecutionState) } func NotifyOnExecutionMutation( engine historyi.Engine, + archetypeID chasm.ArchetypeID, workflowMutation *persistence.WorkflowMutation, ) { if workflowMutation == nil { @@ -615,6 +622,35 @@ func NotifyOnExecutionMutation( RunID: workflowMutation.ExecutionState.RunId, }, nil) } + notifyFastForwardUpdate(engine, archetypeID, workflowMutation.ExecutionInfo, workflowMutation.ExecutionState) +} + +func notifyFastForwardUpdate( + engine historyi.Engine, + archetypeID chasm.ArchetypeID, + executionInfo *persistencespb.WorkflowExecutionInfo, + executionState *persistencespb.WorkflowExecutionState, +) { + tsi := executionInfo.GetTimeSkippingInfo() + if tsi == nil { + return + } + completed := executionState.GetState() == enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED && + executionInfo.GetNewExecutionRunId() == "" + msVT := transitionhistory.LastVersionedTransition(executionInfo.GetTransitionHistory()) + ffVT := executionInfo.GetTimeSkippingInfo().GetFastForwardInfoLastUpdateVersionedTransition() + ffUpdated := transitionhistory.Compare(msVT, ffVT) == 0 + + // we only notify if the chain of runs ends OR fast-forward info is updated in last transaction + if !completed && !ffUpdated { + return + } + + key := notification.NewTimeSkippingNotificationKey(executionInfo.GetNamespaceId(), executionInfo.GetWorkflowId(), archetypeID) + engine.NotifyFastForwardUpdate(key, ¬ification.TimeSkippingFastForwardNotification{ + TimeSkippingInfo: common.CloneProto(executionInfo.GetTimeSkippingInfo()), + WorkflowExecutionCompleted: completed, + }) } func NotifyNewHistorySnapshotEvent( diff --git a/service/history/workflow/transaction_impl_test.go b/service/history/workflow/transaction_impl_test.go new file mode 100644 index 00000000000..024a22db34f --- /dev/null +++ b/service/history/workflow/transaction_impl_test.go @@ -0,0 +1,115 @@ +package workflow + +import ( + "testing" + + "github.com/stretchr/testify/require" + commonpb "go.temporal.io/api/common/v1" + enumsspb "go.temporal.io/server/api/enums/v1" + persistencespb "go.temporal.io/server/api/persistence/v1" + "go.temporal.io/server/chasm" + "go.temporal.io/server/service/history/interfaces" + "go.temporal.io/server/service/history/notification" + "go.uber.org/mock/gomock" +) + +func TestNotifyFastForwardUpdate(t *testing.T) { + const ( + namespaceID = "namespace-id" + workflowID = "workflow-id" + ) + archetypeID := chasm.WorkflowArchetypeID + currentVT := &persistencespb.VersionedTransition{NamespaceFailoverVersion: 1, TransitionCount: 9} + staleVT := &persistencespb.VersionedTransition{NamespaceFailoverVersion: 1, TransitionCount: 4} + + newExecutionInfo := func( + ffVT *persistencespb.VersionedTransition, + transitionHistory []*persistencespb.VersionedTransition, + ) *persistencespb.WorkflowExecutionInfo { + return &persistencespb.WorkflowExecutionInfo{ + NamespaceId: namespaceID, + WorkflowId: workflowID, + TransitionHistory: transitionHistory, + TimeSkippingInfo: &persistencespb.TimeSkippingInfo{ + Config: &commonpb.TimeSkippingConfig{Enabled: true}, + FastForwardInfo: &persistencespb.FastForwardInfo{HasReached: false}, + FastForwardInfoLastUpdateVersionedTransition: ffVT, + }, + } + } + runningState := &persistencespb.WorkflowExecutionState{ + State: enumsspb.WORKFLOW_EXECUTION_STATE_RUNNING, + } + + for _, tc := range []struct { + name string + executionInfo *persistencespb.WorkflowExecutionInfo + executionState *persistencespb.WorkflowExecutionState + wantNotify bool + wantCompleted bool + }{ + { + name: "NoTimeSkippingInfo", + executionInfo: &persistencespb.WorkflowExecutionInfo{ + NamespaceId: namespaceID, + WorkflowId: workflowID, + }, + executionState: runningState, + }, + { + name: "StampedThisTransaction", + executionInfo: newExecutionInfo(currentVT, []*persistencespb.VersionedTransition{staleVT, currentVT}), + executionState: runningState, + wantNotify: true, + }, + { + name: "StampedInEarlierTransaction", + executionInfo: newExecutionInfo(staleVT, []*persistencespb.VersionedTransition{staleVT, currentVT}), + executionState: runningState, + }, + { + name: "StaleStampButRunChainEnded", + executionInfo: newExecutionInfo(staleVT, []*persistencespb.VersionedTransition{staleVT, currentVT}), + executionState: &persistencespb.WorkflowExecutionState{ + State: enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED, + }, + wantNotify: true, + wantCompleted: true, + }, + { + name: "StaleStampAndRunChainContinues", + executionInfo: func() *persistencespb.WorkflowExecutionInfo { + ei := newExecutionInfo(staleVT, []*persistencespb.VersionedTransition{staleVT, currentVT}) + ei.NewExecutionRunId = "next-run-id" + return ei + }(), + executionState: &persistencespb.WorkflowExecutionState{ + State: enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED, + }, + }, + { + // Time skipping requires transition history; with none there is no versioned + // transition to match the stamp against, so nothing is delivered. + name: "NoTransitionHistorySuppresses", + executionInfo: newExecutionInfo(staleVT, nil), + executionState: runningState, + }, + } { + t.Run(tc.name, func(t *testing.T) { + controller := gomock.NewController(t) + engine := interfaces.NewMockEngine(controller) + + if tc.wantNotify { + engine.EXPECT().NotifyFastForwardUpdate( + notification.NewTimeSkippingNotificationKey(namespaceID, workflowID, archetypeID), + gomock.Any(), + ).Do(func(_ any, notif *notification.TimeSkippingFastForwardNotification) { + require.Equal(t, tc.wantCompleted, notif.WorkflowExecutionCompleted) + require.NotSame(t, tc.executionInfo.GetTimeSkippingInfo(), notif.GetTimeSkippingInfo()) + }).Times(1) + } + + notifyFastForwardUpdate(engine, archetypeID, tc.executionInfo, tc.executionState) + }) + } +} diff --git a/tests/mixedbrain/go.mod b/tests/mixedbrain/go.mod index fb124232118..72ea05c4495 100644 --- a/tests/mixedbrain/go.mod +++ b/tests/mixedbrain/go.mod @@ -7,7 +7,7 @@ require ( github.com/siderolabs/grpc-proxy v0.5.2 github.com/stretchr/testify v1.11.1 github.com/temporalio/omes v0.0.0-20260529203146-c6ee1f56c726 - go.temporal.io/api v1.63.4 + go.temporal.io/api v1.63.5-0.20260730194729-ba94140a7640 go.temporal.io/server v0.0.0-00010101000000-000000000000 google.golang.org/grpc v1.80.0 google.golang.org/protobuf v1.36.11 diff --git a/tests/mixedbrain/go.sum b/tests/mixedbrain/go.sum index 60eb4f41b1a..2552183b323 100644 --- a/tests/mixedbrain/go.sum +++ b/tests/mixedbrain/go.sum @@ -55,8 +55,8 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.temporal.io/api v1.63.4 h1:p4dVIAP3dJop0MfcyH9QSzjU7+V/ttLDhxFhSRUar58= -go.temporal.io/api v1.63.4/go.mod h1:SrlW2JMwVlDP4nRWSNznUFqnSHd+YeMDS1BkYo63HCQ= +go.temporal.io/api v1.63.5-0.20260730194729-ba94140a7640 h1:jA7eUGPMrhXi35QHD0FM+bqPCmajFgoTUjn8cVnDRAY= +go.temporal.io/api v1.63.5-0.20260730194729-ba94140a7640/go.mod h1:SrlW2JMwVlDP4nRWSNznUFqnSHd+YeMDS1BkYo63HCQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= diff --git a/tests/timeskipping_fast_forward_test.go b/tests/timeskipping_fast_forward_test.go index 8ac5325cfc4..1239ee560cc 100644 --- a/tests/timeskipping_fast_forward_test.go +++ b/tests/timeskipping_fast_forward_test.go @@ -1,6 +1,7 @@ package tests import ( + "context" "testing" "time" @@ -49,6 +50,17 @@ func (s *TimeSkippingFastForwardFunctionalSuite) getMutableState(env *testcore.T return ms } +// getMutableStateByWorkflowID resolves the current run of a workflow (the newest run of a +// continue-as-new / retry / cron chain) and returns its persistence-layer mutable state. +func (s *TimeSkippingFastForwardFunctionalSuite) getMutableStateByWorkflowID(ctx context.Context, env *testcore.TestEnv, workflowID string) *persistence.GetWorkflowExecutionResponse { + desc, err := env.FrontendClient().DescribeWorkflowExecution(ctx, &workflowservice.DescribeWorkflowExecutionRequest{ + Namespace: env.Namespace().String(), + Execution: &commonpb.WorkflowExecution{WorkflowId: workflowID}, + }) + s.NoError(err) + return s.getMutableState(env, workflowID, desc.WorkflowExecutionInfo.Execution.RunId) +} + func (s *TimeSkippingFastForwardFunctionalSuite) findTransitionedEvents(history []*historypb.HistoryEvent) []*historypb.HistoryEvent { var out []*historypb.HistoryEvent for _, e := range history { @@ -76,7 +88,7 @@ func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_WithActivity() // B3 not fixed: fast-forward disable fires regardless of in-flight activity. Update this // test when B3 lands so it asserts the disable is deferred to the next idle moment. env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -88,8 +100,8 @@ func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_WithActivity() ) cfg := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(fastForward)} + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForward), Id: "ff-id"}} startResp, err := env.FrontendClient().StartWorkflowExecution(ctx, fastForwardStartReq(env, tv, 24*time.Hour, cfg)) s.NoError(err) runID := startResp.RunId @@ -210,7 +222,7 @@ func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_WithActivity() // (a) skip-to-timer1 (DisabledAfterBound=false), (b) fast-forward-disable. func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_PauseLifecycle() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) env.OverrideDynamicConfig(dynamicconfig.WorkflowPauseEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -222,8 +234,8 @@ func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_PauseLifecycle( ) cfg := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(fastForward)} + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForward), Id: "ff-id"}} startResp, err := env.FrontendClient().StartWorkflowExecution(ctx, fastForwardStartReq(env, tv, 24*time.Hour, cfg)) s.NoError(err) runID := startResp.RunId @@ -336,9 +348,414 @@ func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_PauseLifecycle( s.True(ff.GetHasReached(), "HasReached must be true after fastForward timer fired") } +// TestFastForward_PollWakesOnCompletion exercises the PollWorkflowExecutionTimeSkipping +// long poll end-to-end through the notifier: the poll must NOT be answered by the +// initial short-circuit read, it must park in the notifier wait, and it must be woken +// when the fast-forward completes. +// +// Setup keeps the workflow non-idle (a pending activity) so close-tx time skipping +// cannot fire and the fast-forward stays pending when the poll starts — forcing the +// wait path. Completing the activity and its follow-up WFT drives the workflow idle, +// so the idle close-tx skips to the fast-forward target and disables it. That persisted +// mutation fires the notifier, which wakes the blocked poll with FAST_FORWARD_COMPLETED. +func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_PollWakesOnCompletion() { + env := testcore.NewEnv(s.T()) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) + tv := testvars.New(s.T()) + ctx := s.Context() + + // Large fast-forward: the wall-anchored wake-up timer will not fire during the test, + // so the only way the fast-forward completes is the idle close-tx skip we drive below. + const fastForward = 30 * time.Minute + + cfg := &commonpb.TimeSkippingConfig{ + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForward), Id: "ff-id"}} + startResp, err := env.FrontendClient().StartWorkflowExecution(ctx, fastForwardStartReq(env, tv, 24*time.Hour, cfg)) + s.NoError(err) + runID := startResp.RunId + + // WT1: schedule an activity. The pending, dispatchable activity keeps the workflow + // non-idle, so no close-tx skip can fire and the fast-forward stays pending. + _, err = env.TaskPoller().PollAndHandleWorkflowTask(tv, func(_ *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + return &workflowservice.RespondWorkflowTaskCompletedRequest{ + Commands: []*commandpb.Command{scheduleActivityCmd(tv)}, + }, nil + }) + s.NoError(err) + + // Sanity: the fast-forward is pending (not reached) while the activity is in flight, + // so the poll's initial read cannot short-circuit. + ms := s.getMutableState(env, tv.WorkflowID(), runID) + ff := ms.State.ExecutionInfo.GetTimeSkippingInfo().GetFastForwardInfo() + s.NotNil(ff) + if ff != nil { + s.False(ff.GetHasReached()) + } + + type pollResult struct { + resp *workflowservice.PollWorkflowExecutionTimeSkippingResponse + err error + } + resultCh := make(chan pollResult, 1) + go func() { + resp, pollErr := env.FrontendClient().PollWorkflowExecutionTimeSkipping(ctx, &workflowservice.PollWorkflowExecutionTimeSkippingRequest{ + Namespace: env.Namespace().String(), + WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: tv.WorkflowID(), RunId: runID}, + FastForwardId: "ff-id", + }) + resultCh <- pollResult{resp, pollErr} + }() + + // The poll must block in the notifier wait: the fast-forward is pending, so the initial + // read returns "keep waiting" rather than a terminal result. A prompt return here would + // mean it short-circuited, which is exactly what this test guards against. + select { + case r := <-resultCh: + s.Failf("poll returned before the fast-forward completed", + "result=%v err=%v", r.resp.GetFastForwardPollingResult(), r.err) + case <-time.After(2 * time.Second): + } + + // Drive the workflow idle so the close-tx skip fires. Complete the activity, then + // complete the follow-up WFT with no commands: the idle close-tx skips to the + // fast-forward target and disables it (HasReached=true), waking the poll. + _, err = env.TaskPoller().PollAndHandleActivityTask(tv, taskpoller.CompleteActivityTask(tv)) + s.NoError(err) + _, err = env.TaskPoller().PollAndHandleWorkflowTask(tv, func(_ *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + }) + s.NoError(err) + + // The notifier must wake the poll with FAST_FORWARD_COMPLETED. + select { + case r := <-resultCh: + s.NoError(r.err) + s.Equal( + enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED, + r.resp.GetFastForwardPollingResult()) + s.NotNil(r.resp.GetFastForwardInfo()) + s.True(r.resp.GetFastForwardInfo().GetHasCompleted()) + s.Equal("ff-id", r.resp.GetFastForwardInfo().GetFastForwardId()) + case <-time.After(30 * time.Second): + s.Fail("poll did not wake after the fast-forward completed") + } + + // A subsequent poll for the same fast-forward returns the same result immediately: + // the fast-forward has completed, so the initial read short-circuits without ever + // entering the notifier wait. + resp2, err := env.FrontendClient().PollWorkflowExecutionTimeSkipping(ctx, &workflowservice.PollWorkflowExecutionTimeSkippingRequest{ + Namespace: env.Namespace().String(), + WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: tv.WorkflowID(), RunId: runID}, + FastForwardId: "ff-id", + }) + s.NoError(err) + s.Equal( + enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED, + resp2.GetFastForwardPollingResult()) + s.NotNil(resp2.GetFastForwardInfo()) + s.True(resp2.GetFastForwardInfo().GetHasCompleted()) + s.Equal("ff-id", resp2.GetFastForwardInfo().GetFastForwardId()) + + _, _ = env.FrontendClient().TerminateWorkflowExecution(ctx, &workflowservice.TerminateWorkflowExecutionRequest{ + Namespace: env.Namespace().String(), + WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: tv.WorkflowID(), RunId: runID}, + Reason: "test cleanup", + }) +} + +// TestFastForward_PollWakesAcrossContinueAsNew proves the subscription really spans a chain of +// runs. The notification key carries no RunID, so a poll that parks while run 1 is alive must be +// woken by run 2's fast-forward completion after continue-as-new — even though the poll named +// run 1 explicitly. This exercises two things together: the propagated fast-forward target keeps +// the same id across the chain, and the publisher on run 2 lands on the key run 1's waiter holds. +func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_PollWakesAcrossContinueAsNew() { + env := testcore.NewEnv(s.T()) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) + tv := testvars.New(s.T()) + ctx := s.Context() + + // Large enough that the wall-anchored wake-up timer cannot fire during the test: the only way + // the fast-forward completes is the idle close-tx skip we drive on run 2. + const fastForward = 30 * time.Minute + const ffID = "ff-across-can" + + cfg := &commonpb.TimeSkippingConfig{ + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForward), Id: ffID}} + startResp, err := env.FrontendClient().StartWorkflowExecution(ctx, fastForwardStartReq(env, tv, 24*time.Hour, cfg)) + s.NoError(err) + run1ID := startResp.RunId + + // WT1 schedules an activity, keeping run 1 non-idle so no close-tx skip can fire. The + // fast-forward is therefore still pending when the poll parks below. + _, err = env.TaskPoller().PollAndHandleWorkflowTask(tv, func(*workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + return cmdsResponse(scheduleActivityCmd(tv)), nil + }) + s.NoError(err) + + run1FF := s.getMutableState(env, tv.WorkflowID(), run1ID).State.ExecutionInfo.GetTimeSkippingInfo().GetFastForwardInfo() + s.NotNil(run1FF) + if run1FF != nil { + s.False(run1FF.GetHasReached(), "fast-forward must still be pending on run 1") + } + + type pollResult struct { + resp *workflowservice.PollWorkflowExecutionTimeSkippingResponse + err error + } + resultCh := make(chan pollResult, 1) + go func() { + resp, pollErr := env.FrontendClient().PollWorkflowExecutionTimeSkipping(ctx, &workflowservice.PollWorkflowExecutionTimeSkippingRequest{ + Namespace: env.Namespace().String(), + WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: tv.WorkflowID(), RunId: run1ID}, + FastForwardId: ffID, + }) + resultCh <- pollResult{resp, pollErr} + }() + + // The poll must park in the notifier wait rather than short-circuit on its initial read. + select { + case r := <-resultCh: + s.Failf("poll returned before continue-as-new", + "result=%v err=%v", r.resp.GetFastForwardPollingResult(), r.err) + case <-time.After(2 * time.Second): + } + + // Complete the activity, then continue-as-new on the follow-up WFT. Run 1 is closing, so it + // cannot skip; the still-pending fast-forward target propagates to run 2 instead. + _, err = env.TaskPoller().PollAndHandleActivityTask(tv, taskpoller.CompleteActivityTask(tv)) + s.NoError(err) + _, err = env.TaskPoller().PollAndHandleWorkflowTask(tv, func(*workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + return cmdsResponse(continueAsNewCmd(tv.WorkflowType(), tv.TaskQueue())), nil + }) + s.NoError(err) + + // Run 2 must be a different run that inherited the pending fast-forward under the same id, + // otherwise the wake-up below would not prove anything about crossing the chain. + run2MS := s.getMutableStateByWorkflowID(ctx, env, tv.WorkflowID()) + run2ID := run2MS.State.ExecutionState.RunId + s.NotEqual(run1ID, run2ID, "continue-as-new should have produced a new run") + run2TSI := run2MS.State.ExecutionInfo.GetTimeSkippingInfo() + s.NotNil(run2TSI) + if run2TSI != nil { + s.Equal(ffID, run2TSI.GetConfig().GetFastForwardConfig().GetId(), + "run 2 should inherit the same fast-forward id") + s.NotNil(run2TSI.GetFastForwardInfo()) + if run2TSI.GetFastForwardInfo() != nil { + s.False(run2TSI.GetFastForwardInfo().GetHasReached(), + "run 2 should still have the fast-forward pending") + } + } + + // An empty WFT drives run 2 idle, so its close-tx skip advances to the inherited target and + // completes the fast-forward. The publisher is run 2; the waiter subscribed naming run 1. + _, err = env.TaskPoller().PollAndHandleWorkflowTask(tv, func(*workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + }) + s.NoError(err) + + select { + case r := <-resultCh: + s.NoError(r.err) + s.Equal( + enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED, + r.resp.GetFastForwardPollingResult()) + s.NotNil(r.resp.GetFastForwardInfo()) + if r.resp.GetFastForwardInfo() != nil { + s.True(r.resp.GetFastForwardInfo().GetHasCompleted()) + s.Equal(ffID, r.resp.GetFastForwardInfo().GetFastForwardId()) + } + case <-time.After(30 * time.Second): + s.Fail("poll did not wake when the fast-forward completed on the next run of the chain") + } + + _, _ = env.FrontendClient().TerminateWorkflowExecution(ctx, &workflowservice.TerminateWorkflowExecutionRequest{ + Namespace: env.Namespace().String(), + WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: tv.WorkflowID()}, + Reason: "test cleanup", + }) +} + +// TestFastForward_DescribeFollowsChain verifies DescribeWorkflowExecution keeps answering for the +// current run after a continue-as-new, and reports the *virtual* clock inherited across the chain +// rather than wall clock. +func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_DescribeFollowsChain() { + env := testcore.NewEnv(s.T()) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) + tv := testvars.New(s.T()) + ctx := s.Context() + + const timer1 = time.Hour + const timer2 = 2 * time.Hour + + startResp, err := env.FrontendClient().StartWorkflowExecution(ctx, + fastForwardStartReq(env, tv, 24*time.Hour, &commonpb.TimeSkippingConfig{Enabled: true})) + s.NoError(err) + run1ID := startResp.RunId + + // Run 1 skips timer1 then continues-as-new; run 2 skips timer2 then completes. The chain + // accumulates timer1+timer2 of virtual time. + state := 0 + handler := func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + fired := firedTimers(task) + switch { + case state == 0: + state = 1 + return cmdsResponse(timerCmd("t1", timer1)), nil + case state == 1 && fired["t1"]: + state = 2 + return cmdsResponse(continueAsNewCmd(tv.WorkflowType(), tv.TaskQueue())), nil + case state == 2: + state = 3 + return cmdsResponse(timerCmd("t2", timer2)), nil + case state == 3 && fired["t2"]: + state = 4 + return cmdsResponse(completeCmd()), nil + } + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + } + for range 20 { + if _, err := env.TaskPoller().PollAndHandleWorkflowTask(tv, handler); err != nil { + continue // long-poll emptiness between skips is expected + } + if state == 4 { + break + } + } + s.Equal(4, state, "the continue-as-new chain should have run to completion") + + // Describe with no RunID must resolve to run 2, not the run the chain started on. + wallBeforeDescribe := time.Now() + desc, err := env.FrontendClient().DescribeWorkflowExecution(ctx, &workflowservice.DescribeWorkflowExecutionRequest{ + Namespace: env.Namespace().String(), + Execution: &commonpb.WorkflowExecution{WorkflowId: tv.WorkflowID()}, + }) + s.NoError(err) + s.NotEqual(run1ID, desc.GetWorkflowExecutionInfo().GetExecution().GetRunId(), + "describe should follow the chain to the current run") + + tsInfo := desc.GetWorkflowExtendedInfo().GetTimeSkippingInfo() + s.NotNil(tsInfo, "describe must report time-skipping info for the current run of the chain") + if tsInfo == nil { + return + } + s.True(tsInfo.GetEffectiveConfig().GetEnabled(), + "the propagated config should still be enabled on the current run") + + // CurrentTime is virtual, so it runs ahead of wall clock by everything the chain skipped. + // Comparing against the persisted accumulation keeps this independent of test duration. + accumulated := s.getMutableStateByWorkflowID(ctx, env, tv.WorkflowID()). + State.ExecutionInfo.GetTimeSkippingInfo().GetAccumulatedSkippedDuration().AsDuration() + s.InDelta(float64(timer1+timer2), float64(accumulated), float64(time.Minute), + "the chain should have accumulated both timers of virtual time") + s.InDelta(float64(accumulated), float64(tsInfo.GetCurrentTime().AsTime().Sub(wallBeforeDescribe)), + float64(time.Minute), "describe's CurrentTime should be wall clock plus the accumulated skip") +} + +// TestFastForward_PollWakesOnWorkflowEnd verifies that a PollWorkflowExecutionTimeSkipping long +// poll parked on a still-pending fast-forward is woken — instantly and with the correct terminal +// result — when the whole workflow execution ends before the fast-forward completes. Terminating +// the run (state COMPLETED, status TERMINATED, no continuation) is the whole-execution-end signal; +// canceling is analogous. +func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_PollWakesOnWorkflowEnd() { + env := testcore.NewEnv(s.T()) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) + tv := testvars.New(s.T()) + ctx := s.Context() + + // Large fast-forward so it cannot complete on its own during the test; the only terminal + // event will be the termination we drive below. + const fastForward = 30 * time.Minute + + cfg := &commonpb.TimeSkippingConfig{ + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForward), Id: "ff-id"}} + startResp, err := env.FrontendClient().StartWorkflowExecution(ctx, fastForwardStartReq(env, tv, 24*time.Hour, cfg)) + s.NoError(err) + runID := startResp.RunId + + // WT1: schedule an activity. The pending, dispatchable activity keeps the workflow non-idle, + // so no close-tx skip fires and the fast-forward stays pending while the poll is parked. + _, err = env.TaskPoller().PollAndHandleWorkflowTask(tv, func(_ *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + return &workflowservice.RespondWorkflowTaskCompletedRequest{ + Commands: []*commandpb.Command{scheduleActivityCmd(tv)}, + }, nil + }) + s.NoError(err) + + // Sanity: the fast-forward is pending, so the poll's initial read cannot short-circuit. + ms := s.getMutableState(env, tv.WorkflowID(), runID) + ff := ms.State.ExecutionInfo.GetTimeSkippingInfo().GetFastForwardInfo() + s.NotNil(ff) + if ff != nil { + s.False(ff.GetHasReached()) + } + + type pollResult struct { + resp *workflowservice.PollWorkflowExecutionTimeSkippingResponse + err error + } + resultCh := make(chan pollResult, 1) + go func() { + resp, pollErr := env.FrontendClient().PollWorkflowExecutionTimeSkipping(ctx, &workflowservice.PollWorkflowExecutionTimeSkippingRequest{ + Namespace: env.Namespace().String(), + WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: tv.WorkflowID(), RunId: runID}, + FastForwardId: "ff-id", + }) + resultCh <- pollResult{resp, pollErr} + }() + + // The poll must park in the notifier wait: the fast-forward is pending, so a prompt return + // here would mean it short-circuited. + select { + case r := <-resultCh: + s.Failf("poll returned before the workflow ended", "result=%v err=%v", r.resp.GetFastForwardPollingResult(), r.err) + case <-time.After(2 * time.Second): + } + + // End the whole execution while the poll is parked. Termination closes the run with no + // continuation, which fires the notifier with WorkflowExecutionCompleted=true. + _, err = env.FrontendClient().TerminateWorkflowExecution(ctx, &workflowservice.TerminateWorkflowExecutionRequest{ + Namespace: env.Namespace().String(), + WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: tv.WorkflowID(), RunId: runID}, + Reason: "end while polling", + }) + s.NoError(err) + + // The notifier must wake the poll with a failure whose reason names the ended execution. The + // fast-forward info is still the pending (never-completed) one. + select { + case r := <-resultCh: + s.NoError(r.err) + s.Equal( + enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, + r.resp.GetFastForwardPollingResult()) + s.NotEmpty(r.resp.GetFailedReason(), "a failed poll must explain why") + s.NotNil(r.resp.GetFastForwardInfo()) + s.False(r.resp.GetFastForwardInfo().GetHasCompleted()) + s.Equal("ff-id", r.resp.GetFastForwardInfo().GetFastForwardId()) + case <-time.After(30 * time.Second): + s.Fail("poll did not wake after the workflow ended") + } + + // A subsequent poll returns the same terminal result immediately via the initial-read + // short-circuit (the run is closed with no continuation). + resp2, err := env.FrontendClient().PollWorkflowExecutionTimeSkipping(ctx, &workflowservice.PollWorkflowExecutionTimeSkippingRequest{ + Namespace: env.Namespace().String(), + WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: tv.WorkflowID(), RunId: runID}, + FastForwardId: "ff-id", + }) + s.NoError(err) + s.Equal( + enumspb.FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_FAILED, + resp2.GetFastForwardPollingResult()) + s.NotEmpty(resp2.GetFailedReason()) +} + func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_NoUserTimer() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -349,8 +766,8 @@ func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_NoUserTimer() { ) cfg := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(fastForward)} + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForward), Id: "ff-id"}} startResp, err := env.FrontendClient().StartWorkflowExecution(ctx, fastForwardStartReq(env, tv, 24*time.Hour, cfg)) s.NoError(err) runID := startResp.RunId @@ -392,7 +809,7 @@ func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_NoUserTimer() { func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_EqualsRunTimeout() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) ctx := s.Context() const ( @@ -405,8 +822,8 @@ func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_EqualsRunTimeou }, workflow.RegisterOptions{Name: "sleepEqualsTimeoutWorkflow"}) cfg := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(fastForward), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForward), Id: "ff-id"}, } workflowID := uuid.NewString() startResp, err := env.FrontendClient().StartWorkflowExecution(ctx, &workflowservice.StartWorkflowExecutionRequest{ @@ -445,7 +862,7 @@ func (s *TimeSkippingFastForwardFunctionalSuite) TestFastForward_EqualsRunTimeou func (s *TimeSkippingFastForwardFunctionalSuite) TestEventsOrderOfFastForwardTimerFiredDuringStartedWorkflowTask() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -454,7 +871,7 @@ func (s *TimeSkippingFastForwardFunctionalSuite) TestEventsOrderOfFastForwardTim timer1Dur = 45 * time.Second ) - cfg := &commonpb.TimeSkippingConfig{Enabled: true, FastForward: durationpb.New(fastForward)} + cfg := &commonpb.TimeSkippingConfig{Enabled: true, FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForward), Id: "ff-id"}} startResp, err := env.FrontendClient().StartWorkflowExecution(ctx, &workflowservice.StartWorkflowExecutionRequest{ RequestId: uuid.NewString(), Namespace: env.Namespace().String(), diff --git a/tests/timeskipping_propagation_test.go b/tests/timeskipping_propagation_test.go index d922ebac222..781f046fe83 100644 --- a/tests/timeskipping_propagation_test.go +++ b/tests/timeskipping_propagation_test.go @@ -100,7 +100,7 @@ func TestTimeSkippingPropagationTestSuite(t *testing.T) { // - Wall-clock elapsed is nowhere near 5h of virtual time. func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_Basic() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -122,8 +122,8 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_Basic() { // FastForward never cascades into children; the parent's 2h of own skipping // stays under this budget so the parent's flow is unaffected by it. TimeSkippingConfig: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(3 * time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(3 * time.Hour), Id: "ff-id"}, }, }) s.NoError(err) @@ -182,7 +182,7 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_Basic() { s.Equal(enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED, parentMS.State.ExecutionState.State) parentTSI := parentMS.State.ExecutionInfo.GetTimeSkippingInfo() s.NotNil(parentTSI) - s.Equal(3*time.Hour, parentTSI.GetConfig().GetFastForward().AsDuration(), + s.Equal(3*time.Hour, parentTSI.GetConfig().GetFastForwardConfig().GetDuration().AsDuration(), "parent keeps its own MaxElapsedDuration; child propagation must not mutate it") s.approxDuration(2*time.Hour, parentTSI.GetAccumulatedSkippedDuration().AsDuration(), "parent should accumulate 1h (t1) + 1h (t2 after child) = 2h") @@ -195,7 +195,8 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_Basic() { childCfg := childTSI.GetConfig() s.NotNil(childCfg) s.True(childCfg.GetEnabled(), "child TSC.Enabled propagated from parent") - s.Nil(childCfg.GetFastForward(), "MaxElapsedDuration never cascades into children") + s.Nil(childCfg.GetFastForwardConfig().GetDuration(), "MaxElapsedDuration never cascades into children") + s.Empty(childCfg.GetFastForwardConfig().GetId(), "fast_forward_id is per-execution and never cascades into children") s.approxDuration(4*time.Hour, childTSI.GetAccumulatedSkippedDuration().AsDuration(), "child AccumulatedSkippedDuration == parent's accumulated at child-start (1h) + child's own skip for tc (3h) = 4h — exceeding the parent's 3h FastForward, possible only because the budget did not cascade") @@ -208,11 +209,130 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_Basic() { initTSC := initAttrs.GetTimeSkippingConfig() s.NotNil(initTSC, "initiated event should carry TimeSkippingConfig snapshot") s.True(initTSC.GetEnabled(), "snapshot mirrors parent's Enabled flag") - s.Nil(initTSC.GetFastForward(), "initiated-event snapshot excludes the parent's MaxElapsedDuration") + s.Nil(initTSC.GetFastForwardConfig().GetDuration(), "initiated-event snapshot excludes the parent's MaxElapsedDuration") + s.Empty(initTSC.GetFastForwardConfig().GetId(), "initiated-event snapshot excludes the parent's fast_forward_id") s.approxDuration(time.Hour, initAttrs.GetTimeSkippingStatePropagation().GetInitialSkippedDuration().AsDuration(), "initiated event InitialSkippedDuration == parent's AccumulatedSkippedDuration at command time (1h)") } +// TestTSPInChildWf_InheritsBudgetFreshCount verifies that a child inherits the parent's +// per-session budget (MaxSkipPerSession) but starts a fresh skip count. Children start +// internally, bypassing the frontend that populates the default budget — so without inheriting +// the parent's limit the child's config would carry MaxSkipPerSession=0 and time skipping would +// disable after its very first skip. The child here skips TWICE, which is only possible because +// it inherited the parent's budget (5) rather than 0. +func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_InheritsBudgetFreshCount() { + env := testcore.NewEnv(s.T()) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) + tv := testvars.New(s.T()) + ctx := s.Context() + + parentWFType := tv.WorkflowType() + childWFType := &commonpb.WorkflowType{Name: parentWFType.Name + "-child"} + parentWFID := tv.WorkflowID() + childWFID := parentWFID + "-child" + + // A distinctive budget (not the dynamic-config default of 100) so the value observed on the + // child unambiguously came from the parent's config. Small, but above each run's skip count. + const maxSkip = 5 + + startWall := time.Now() + + parentStart, err := env.FrontendClient().StartWorkflowExecution(ctx, &workflowservice.StartWorkflowExecutionRequest{ + RequestId: uuid.NewString(), + Namespace: env.Namespace().String(), + WorkflowId: parentWFID, + WorkflowType: parentWFType, + TaskQueue: tv.TaskQueue(), + WorkflowRunTimeout: durationpb.New(24 * time.Hour), + WorkflowTaskTimeout: durationpb.New(10 * time.Second), + TimeSkippingConfig: &commonpb.TimeSkippingConfig{Enabled: true, MaxSessionSkipCount: maxSkip}, + }) + s.NoError(err) + parentRunID := parentStart.RunId + + ns := env.Namespace().String() + tq := tv.TaskQueue() + + parentStarted, parentKickedOff, parentDone := false, false, false + parentHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + fired := firedTimers(task) + if !parentStarted { + parentStarted = true + return cmdsResponse(timerCmd("t1", time.Hour)), nil + } + if fired["t1"] && !parentKickedOff { + parentKickedOff = true + return cmdsResponse( + childCmd(ns, childWFID, childWFType, tq), + timerCmd("t2", time.Hour), + ), nil + } + if fired["t2"] && !parentDone { + parentDone = true + return cmdsResponse(completeCmd()), nil + } + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + } + + // The child skips twice: tc1 then tc2. Under the old bug (MaxSkipPerSession=0) skipping would + // disable after tc1 and tc2 would never be skipped, hanging the test. + childStarted, childSecond, childDone := false, false, false + childHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + fired := firedTimers(task) + if !childStarted { + childStarted = true + return cmdsResponse(timerCmd("tc1", 2*time.Hour)), nil + } + if fired["tc1"] && !childSecond { + childSecond = true + return cmdsResponse(timerCmd("tc2", 2*time.Hour)), nil + } + if fired["tc2"] && !childDone { + childDone = true + return cmdsResponse(completeCmd()), nil + } + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + } + + dispatch := typeDispatch(map[string]wftHandler{ + parentWFType.Name: parentHandler, + childWFType.Name: childHandler, + }) + + s.drivePollsUntilClosed(ctx, env, tv, dispatch, parentWFID, parentRunID, 20) + + elapsed := time.Since(startWall) + s.Less(elapsed, 2*time.Minute, "wall-clock elapsed (%s) should be far less than the virtual time skipped", elapsed) + + // ---- Child: inherited the budget, ran a fresh session, skipped twice ---- + childMS := s.getMutableStateByID(ctx, env, childWFID) + s.Equal(enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED, childMS.State.ExecutionState.State) + childTSI := childMS.State.ExecutionInfo.GetTimeSkippingInfo() + s.NotNil(childTSI) + s.True(childTSI.GetConfig().GetEnabled(), "child skipping stayed enabled across both skips") + s.Equal(int32(maxSkip), childTSI.GetConfig().GetMaxSessionSkipCount(), + "child inherits the parent's per-session budget") + s.Equal(int32(2), childTSI.GetSessionSkipCount(), + "child ran a fresh session: only its own two skips, not the parent's count") + + // ---- Parent: its own budget and count are independent of the child's ---- + parentMS := s.getMutableState(env, parentWFID, parentRunID) + s.Equal(enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED, parentMS.State.ExecutionState.State) + parentTSI := parentMS.State.ExecutionInfo.GetTimeSkippingInfo() + s.Equal(int32(maxSkip), parentTSI.GetConfig().GetMaxSessionSkipCount()) + s.Equal(int32(2), parentTSI.GetSessionSkipCount(), "parent skipped t1 and t2") + + // The initiated event's TimeSkippingConfig snapshot carries the inherited budget but no count. + initEvents := s.initiatedChildEvents(ctx, env, parentWFID, parentRunID) + s.Len(initEvents, 1) + initAttrs := initEvents[0].GetStartChildWorkflowExecutionInitiatedEventAttributes() + s.Equal(int32(maxSkip), initAttrs.GetTimeSkippingConfig().GetMaxSessionSkipCount(), + "initiated-event snapshot carries the inherited budget") + s.Equal(int32(0), initAttrs.GetTimeSkippingStatePropagation().GetInitialSkipCount(), + "child does not inherit the parent's running skip count") +} + // TestTSPInChildWf_TwoChildren verifies that: // - All children started in the same WFT inherit the parent's TimeSkippingConfig // and have their AccumulatedSkippedDuration seeded to the parent's accumulated @@ -232,7 +352,7 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_Basic() { // - Parent completes. func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_TwoChildren() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -364,7 +484,7 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_TwoChildren() { // Final state: C.accum == 4h (3h inherited + 1h own); P.accum == 4h; G.accum == 2h. func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_ThreeGenerations() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -535,7 +655,7 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_AdmissionTimestampsS env := testcore.NewEnv( s.T(), testcore.WithHistoryTaskRecorder(), - testcore.WithDynamicConfig(dynamicconfig.TimeSkippingEnabled, true), + testcore.WithDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true), ) tv := testvars.New(s.T()) ctx := s.Context() @@ -961,7 +1081,7 @@ func (s *TimeSkippingPropagationTestSuite) firstWorkflowTaskCompletedEventID( // behavior so a future change would surface it. func (s *TimeSkippingPropagationTestSuite) TestTSPInReset() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -1105,7 +1225,7 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInReset() { // - Wall-clock elapsed is nowhere near 3h of virtual time. func (s *TimeSkippingPropagationTestSuite) TestTSPInCaN() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -1194,6 +1314,96 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInCaN() { "started event retains InitialSkippedDuration=1h (run 1's accumulated at CaN time); applyTimeSkippingConfig uses it to seed AccumulatedSkippedDuration on the MS but the event snapshot is unchanged") } +// TestTSPInCaN_SkipSessionPropagated verifies that continue-as-new carries the whole skip +// session forward: the per-session budget (MaxSkipPerSession, via the cloned config) AND the +// running SessionSkipCount (via InitialSkipCount). Run 1 skips once (count 1) then CaNs with a +// budget of 10; run 2 boots with the count seeded to 1, skips once more (count 2), and still +// sees the same budget. +func (s *TimeSkippingPropagationTestSuite) TestTSPInCaN_SkipSessionPropagated() { + env := testcore.NewEnv(s.T()) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) + tv := testvars.New(s.T()) + ctx := s.Context() + + wfType := tv.WorkflowType() + wfID := tv.WorkflowID() + tq := tv.TaskQueue() + + // A distinctive budget (not the dynamic-config default of 100) so a propagated value is + // unambiguously the one from run 1's config. 10 is high enough that neither run hits the cap. + const maxSkip = 10 + + start1, err := env.FrontendClient().StartWorkflowExecution(ctx, &workflowservice.StartWorkflowExecutionRequest{ + RequestId: uuid.NewString(), + Namespace: env.Namespace().String(), + WorkflowId: wfID, + WorkflowType: wfType, + TaskQueue: tq, + WorkflowRunTimeout: durationpb.New(24 * time.Hour), + WorkflowTaskTimeout: durationpb.New(10 * time.Second), + TimeSkippingConfig: &commonpb.TimeSkippingConfig{Enabled: true, MaxSessionSkipCount: maxSkip}, + }) + s.NoError(err) + run1ID := start1.RunId + + state := 0 + handler := func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + fired := firedTimers(task) + switch { + case state == 0: + state = 1 + return cmdsResponse(timerCmd("t1", time.Hour)), nil + case state == 1 && fired["t1"]: + state = 2 + return cmdsResponse(continueAsNewCmd(wfType, tq)), nil + case state == 2: + state = 3 + return cmdsResponse(timerCmd("t2", 2*time.Hour)), nil + case state == 3 && fired["t2"]: + state = 4 + return cmdsResponse(completeCmd()), nil + } + return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil + } + + // Empty runID so describe follows the chain to the current run (run 2 after CaN). + s.drivePollsUntilClosed(ctx, env, tv, handler, wfID, "", 20) + + // ---- Run 1: skipped once (t1), CaN'd with the budget still enabled ---- + run1MS := s.getMutableState(env, wfID, run1ID) + s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW, run1MS.State.ExecutionState.Status) + run1TSI := run1MS.State.ExecutionInfo.GetTimeSkippingInfo() + s.NotNil(run1TSI) + s.Equal(int32(maxSkip), run1TSI.GetConfig().GetMaxSessionSkipCount()) + s.Equal(int32(1), run1TSI.GetSessionSkipCount(), "run 1 skipped once for t1") + + // ---- Run 2: inherits both the budget and the running count ---- + run2MS := s.getMutableStateByID(ctx, env, wfID) + run2ID := run2MS.State.ExecutionState.RunId + s.NotEqual(run1ID, run2ID, "run 2 should have a new run ID") + s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, run2MS.State.ExecutionState.Status) + run2TSI := run2MS.State.ExecutionInfo.GetTimeSkippingInfo() + s.NotNil(run2TSI, "run 2 should have TimeSkippingInfo propagated from run 1") + s.Equal(int32(maxSkip), run2TSI.GetConfig().GetMaxSessionSkipCount(), + "run 2 inherits the same per-session budget from run 1's cloned config") + s.Equal(int32(2), run2TSI.GetSessionSkipCount(), + "run 2 count = 1 inherited from run 1 + 1 for its own skip (t2)") + + // Run 2's WorkflowExecutionStarted event carries both in the propagated snapshot. + hist2, err := env.FrontendClient().GetWorkflowExecutionHistory(ctx, &workflowservice.GetWorkflowExecutionHistoryRequest{ + Namespace: env.Namespace().String(), + Execution: &commonpb.WorkflowExecution{WorkflowId: wfID, RunId: run2ID}, + }) + s.NoError(err) + s.NotEmpty(hist2.History.Events) + startedAttr := hist2.History.Events[0].GetWorkflowExecutionStartedEventAttributes() + s.NotNil(startedAttr) + s.Equal(int32(maxSkip), startedAttr.GetTimeSkippingConfig().GetMaxSessionSkipCount(), + "started event TSC carries the propagated budget") + s.Equal(int32(1), startedAttr.GetTimeSkippingStatePropagation().GetInitialSkipCount(), + "started event carries run 1's SessionSkipCount as InitialSkipCount") +} + // TestTSPInRetry verifies that a retried run is a same-lineage continuation: it inherits // the previous attempt's current TimeSkippingConfig AND its in-flight accumulated skip. // @@ -1224,7 +1434,7 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInCaN() { // ContinuedExecutionRunId. func (s *TimeSkippingPropagationTestSuite) TestTSPInRetry() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -1244,8 +1454,8 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInRetry() { WorkflowTaskTimeout: durationpb.New(10 * time.Second), // FastForward is part of the inherited config: it caps the whole chain of attempts. TimeSkippingConfig: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(4 * time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(4 * time.Hour), Id: "ff-id"}, }, RetryPolicy: &commonpb.RetryPolicy{ InitialInterval: durationpb.New(time.Second), @@ -1301,8 +1511,10 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInRetry() { attempt2Cfg := attempt2TSI.GetConfig() s.NotNil(attempt2Cfg) s.True(attempt2Cfg.GetEnabled(), "attempt 2 TSC.Enabled propagated from attempt 1's current config") - s.Equal(4*time.Hour, attempt2Cfg.GetFastForward().AsDuration(), + s.Equal(4*time.Hour, attempt2Cfg.GetFastForwardConfig().GetDuration().AsDuration(), "attempt 2 inherits MaxElapsedDuration verbatim — the budget caps the whole chain, not each attempt") + s.Equal("ff-id", attempt2Cfg.GetFastForwardConfig().GetId(), + "attempt 2 inherits fast_forward_id — the fast-forward continues across the retry chain") s.approxDuration(3*time.Hour, attempt2TSI.GetAccumulatedSkippedDuration().AsDuration(), "attempt 2 carries attempt 1's 1h in-flight skip forward plus its own 2h from t2 = 3h") s.LessOrEqual(attempt2TSI.GetAccumulatedSkippedDuration().AsDuration(), 4*time.Hour, @@ -1324,8 +1536,10 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInRetry() { startedTSC := startedAttr.GetTimeSkippingConfig() s.NotNil(startedTSC, "attempt 2's WorkflowExecutionStarted must carry the TSC snapshot") s.True(startedTSC.GetEnabled(), "started event TSC.Enabled mirrors attempt 1's current config") - s.Equal(4*time.Hour, startedTSC.GetFastForward().AsDuration(), + s.Equal(4*time.Hour, startedTSC.GetFastForwardConfig().GetDuration().AsDuration(), "started event carries the inherited MaxElapsedDuration — the snapshot of attempt 1's current config") + s.Equal("ff-id", startedTSC.GetFastForwardConfig().GetId(), + "started event carries the inherited fast_forward_id from attempt 1's current config") s.approxDuration(time.Hour, startedAttr.GetTimeSkippingStatePropagation().GetInitialSkippedDuration().AsDuration(), "started event InitialSkippedDuration carries attempt 1's in-flight accumulated skip (1h) at retry time") } @@ -1358,7 +1572,7 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInRetry() { // InitialSkippedDuration ≈ 50min, initiator=CRON_SCHEDULE. func (s *TimeSkippingPropagationTestSuite) TestTSPInCron() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -1367,8 +1581,9 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInCron() { tq := tv.TaskQueue() inputCfg := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(time.Hour), + Enabled: true, + MaxSessionSkipCount: 50, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour), Id: uuid.NewString()}, } startWall := time.Now() @@ -1492,7 +1707,7 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInCron() { // - run2 history has exactly one transition, carrying DisabledAfterFastForward=true. func (s *TimeSkippingPropagationTestSuite) TestTSPInCaN_BudgetCapOverChain() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -1511,8 +1726,8 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInCaN_BudgetCapOverChain() { WorkflowRunTimeout: durationpb.New(24 * time.Hour), WorkflowTaskTimeout: durationpb.New(10 * time.Second), TimeSkippingConfig: &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(3 * time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(3 * time.Hour), Id: "ff-id"}, }, }) s.NoError(err) @@ -1618,7 +1833,7 @@ func (s *TimeSkippingPropagationTestSuite) TestTSPInCaN_BudgetCapOverChain() { // but InitialSkippedDuration == 1h. func (s *TimeSkippingPropagationTestSuite) TestTSPInChildWf_PropagationDisabled() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() diff --git a/tests/timeskipping_test.go b/tests/timeskipping_test.go index 11e9df36412..8f3af78a449 100644 --- a/tests/timeskipping_test.go +++ b/tests/timeskipping_test.go @@ -32,6 +32,11 @@ import ( "google.golang.org/protobuf/types/known/fieldmaskpb" ) +// defaultMaxSkipPerSession mirrors the compiled default of +// dynamicconfig.WorkflowTimeSkippingMaxSkipPerSession. The frontend populates a request's +// unset MaxSkipPerSession with this value, so tests that leave it empty expect it back. +const defaultMaxSkipPerSession = 200 + type TimeSkippingTestSuite struct { parallelsuite.Suite[*TimeSkippingTestSuite] } @@ -44,7 +49,7 @@ func TestTimeSkippingTestSuite(t *testing.T) { // returns an error when the feature flag is off for the namespace. func (s *TimeSkippingTestSuite) TestTimeSkipping_FeatureDisabled() { env := testcore.NewEnv(s.T()) - // TimeSkippingEnabled defaults to false; no override needed. + // WorkflowTimeSkippingEnabled defaults to false; no override needed. id := "functional-timeskipping-feature-disabled" tl := "functional-timeskipping-feature-disabled-tq" @@ -65,12 +70,13 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_FeatureDisabled() { // TimeSkippingConfig persists the config in mutable state when the feature flag is on. func (s *TimeSkippingTestSuite) TestTimeSkipping_StartWorkflow_DCEnabled() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) + // Request leaves MaxSkipPerSession empty; the frontend populates it from dynamic config. inputConfig := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour), Id: "ff-id"}, } resp, err := env.FrontendClient().StartWorkflowExecution(s.Context(), &workflowservice.StartWorkflowExecutionRequest{ @@ -87,6 +93,7 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_StartWorkflow_DCEnabled() { ms := s.getMutableState(env, tv.WorkflowID(), resp.RunId) s.True(ms.State.ExecutionInfo.GetTimeSkippingInfo().GetConfig().GetEnabled()) + inputConfig.MaxSessionSkipCount = defaultMaxSkipPerSession // frontend populated this from dynamic config s.True(proto.Equal(inputConfig, ms.State.ExecutionInfo.GetTimeSkippingInfo().GetConfig())) } @@ -94,12 +101,13 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_StartWorkflow_DCEnabled() { // with TimeSkippingConfig persists the config in mutable state when the feature flag is on. func (s *TimeSkippingTestSuite) TestTimeSkipping_SignalWithStart_DCEnabled() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) + // Request leaves MaxSkipPerSession empty; the frontend populates it from dynamic config. inputConfig := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour), Id: "ff-id"}, } resp, err := env.FrontendClient().SignalWithStartWorkflowExecution(s.Context(), &workflowservice.SignalWithStartWorkflowExecutionRequest{ @@ -116,6 +124,7 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_SignalWithStart_DCEnabled() { s.NoError(err) ms := s.getMutableState(env, tv.WorkflowID(), resp.RunId) + inputConfig.MaxSessionSkipCount = defaultMaxSkipPerSession // frontend populated this from dynamic config s.True(proto.Equal(inputConfig, ms.State.ExecutionInfo.GetTimeSkippingInfo().GetConfig())) } @@ -124,13 +133,14 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_SignalWithStart_DCEnabled() { // when the feature flag is on. func (s *TimeSkippingTestSuite) TestTimeSkipping_ExecuteMultiOperation_DCEnabled() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) maxElapsedDuration := time.Hour + // Request leaves MaxSkipPerSession empty; the frontend populates it from dynamic config. inputConfig := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(maxElapsedDuration), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(maxElapsedDuration), Id: "ff-id"}, } resp, err := env.FrontendClient().ExecuteMultiOperation(s.Context(), &workflowservice.ExecuteMultiOperationRequest{ @@ -168,6 +178,7 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_ExecuteMultiOperation_DCEnabled runID := resp.GetResponses()[0].GetStartWorkflow().GetRunId() ms := s.getMutableState(env, tv.WorkflowID(), runID) + inputConfig.MaxSessionSkipCount = defaultMaxSkipPerSession // frontend populated this from dynamic config s.True(proto.Equal(inputConfig, ms.State.ExecutionInfo.GetTimeSkippingInfo().GetConfig())) } @@ -180,7 +191,7 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_ExecuteMultiOperation_DCEnabled // 5. Assert exactly 3 WorkflowExecutionOptionsUpdated events appear in history. func (s *TimeSkippingTestSuite) TestTimeSkipping_UpdateWorkflowOptions_DCEnabled() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) // Start a workflow without any time-skipping config. @@ -225,12 +236,14 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_UpdateWorkflowOptions_DCEnabled ms := s.getMutableState(env, tv.WorkflowID(), runID) s.Nil(ms.State.ExecutionInfo.GetTimeSkippingInfo().GetConfig()) - // First update: enable with a max_elapsed_duration. + // First update: enable with a max_elapsed_duration. MaxSkipPerSession is left empty and + // populated by the frontend from dynamic config; the persisted config and event carry it. config1 := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour), Id: "ff-id"}, } updateOptions(config1) + config1.MaxSessionSkipCount = defaultMaxSkipPerSession ms = s.getMutableState(env, tv.WorkflowID(), runID) s.True(proto.Equal(config1, ms.State.ExecutionInfo.GetTimeSkippingInfo().GetConfig())) @@ -240,10 +253,11 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_UpdateWorkflowOptions_DCEnabled // Second update: change the max_elapsed_duration duration. config2 := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(2 * time.Hour), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(2 * time.Hour), Id: "ff-id"}, } updateOptions(config2) + config2.MaxSessionSkipCount = defaultMaxSkipPerSession ms = s.getMutableState(env, tv.WorkflowID(), runID) s.True(proto.Equal(config2, ms.State.ExecutionInfo.GetTimeSkippingInfo().GetConfig())) @@ -251,9 +265,11 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_UpdateWorkflowOptions_DCEnabled s.Len(events, 2) s.True(proto.Equal(config2, events[1].GetWorkflowExecutionOptionsUpdatedEventAttributes().GetTimeSkippingConfig())) - // Third update: disable time-skipping. + // Third update: disable time-skipping. The frontend's default-populate runs even for a + // disabled config, so MaxSkipPerSession still comes back set. config3 := &commonpb.TimeSkippingConfig{Enabled: false} updateOptions(config3) + config3.MaxSessionSkipCount = defaultMaxSkipPerSession ms = s.getMutableState(env, tv.WorkflowID(), runID) s.True(proto.Equal(config3, ms.State.ExecutionInfo.GetTimeSkippingInfo().GetConfig())) @@ -268,7 +284,7 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_UpdateWorkflowOptions_DCEnabled // attributes carry the full config. func (s *TimeSkippingTestSuite) TestTimeSkipping_ResetWithUpdateOptions() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -305,8 +321,8 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_ResetWithUpdateOptions() { // Reset with PostResetOperations that sets TimeSkippingConfig. inputConfig := &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(time.Hour)} + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(time.Hour), Id: "ff-id"}} resetResp, err := env.FrontendClient().ResetWorkflowExecution(ctx, &workflowservice.ResetWorkflowExecutionRequest{ Namespace: env.Namespace().String(), WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: tv.WorkflowID(), RunId: runID}, @@ -327,8 +343,9 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_ResetWithUpdateOptions() { s.NoError(err) newRunID := resetResp.RunId - // New run's mutable state must have the config. + // New run's mutable state must have the config (MaxSkipPerSession populated by the frontend). ms := s.getMutableState(env, tv.WorkflowID(), newRunID) + inputConfig.MaxSessionSkipCount = defaultMaxSkipPerSession s.True(proto.Equal(inputConfig, ms.State.ExecutionInfo.GetTimeSkippingInfo().GetConfig())) // New run's history must contain a WorkflowExecutionOptionsUpdated event with the config. @@ -445,7 +462,7 @@ func hasEventType(events []*historypb.HistoryEvent, t enumspb.EventType) bool { // WT3 → complete workflow (timer has fired) func (s *TimeSkippingTestSuite) TestTimeSkipping_TimerAndActivity() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) // Run timeout must exceed the 1h timer; otherwise skip shifts the run-timeout @@ -505,7 +522,7 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_TimerAndActivity() { // WT2 → activity completed → complete the workflow. func (s *TimeSkippingTestSuite) TestTimeSkipping_ActivityRetryBackoff() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) wallStart := time.Now() @@ -576,7 +593,7 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_ActivityRetryBackoff() { func (s *TimeSkippingTestSuite) TestTimeSkipping_PendingSignalExternalBlocksSkip() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() @@ -692,7 +709,7 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_StartWithDelay() { env := testcore.NewEnv( s.T(), testcore.WithHistoryTaskRecorder(), - testcore.WithDynamicConfig(dynamicconfig.TimeSkippingEnabled, true), + testcore.WithDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true), ) tv := testvars.New(s.T()) @@ -771,7 +788,7 @@ func (s *TimeSkippingTestSuite) TestTimeSkipping_StartWithDelay() { // Verify: AccumulatedSkippedDuration ≈ 3h (NOT 1h+4h=5h from canceled timer-B) func (s *TimeSkippingTestSuite) TestTimeSkipping_CanceledTimerNotUsedAsSkipTarget() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) const ( @@ -894,7 +911,7 @@ func (s *TimeSkippingTestSuite) TestWorkflowLifecycle_VirtualTimeContract() { env := testcore.NewEnv( s.T(), testcore.WithHistoryTaskRecorder(), - testcore.WithDynamicConfig(dynamicconfig.TimeSkippingEnabled, true), + testcore.WithDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true), ) tv := testvars.New(s.T()) @@ -1204,7 +1221,7 @@ func (s *TimeSkippingTestSuite) TestWorkflowLifecycle_VirtualTimeContract() { func (s *TimeSkippingFastForwardFunctionalSuite) TestTimeSkipping_ExecutionTimeoutTimesOutIdleWorkflow() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) ctx := s.Context() const executionTimeout = 5 * time.Minute @@ -1249,7 +1266,7 @@ func (s *TimeSkippingFastForwardFunctionalSuite) TestTimeSkipping_ExecutionTimeo func (s *TimeSkippingTestSuite) TestTimeSkippingTransitionEventOrdersAfterOptionsUpdated() { env := testcore.NewEnv(s.T()) - env.OverrideDynamicConfig(dynamicconfig.TimeSkippingEnabled, true) + env.OverrideDynamicConfig(dynamicconfig.WorkflowTimeSkippingEnabled, true) tv := testvars.New(s.T()) ctx := s.Context() diff --git a/tests/xdc/timeskipping_replication_test.go b/tests/xdc/timeskipping_replication_test.go index 048386aaabb..cea975da397 100644 --- a/tests/xdc/timeskipping_replication_test.go +++ b/tests/xdc/timeskipping_replication_test.go @@ -37,7 +37,7 @@ func TestTimeSkippingReplicationSuite(t *testing.T) { func (s *timeSkippingReplicationSuite) SetupSuite() { s.dynamicConfigOverrides = map[dynamicconfig.Key]any{ - dynamicconfig.TimeSkippingEnabled.Key(): true, + dynamicconfig.WorkflowTimeSkippingEnabled.Key(): true, } // Drive the state-based replication path so TimeSkippingInfo replicates via the // generic ExecutionInfo merge and PartialRefresh re-stamps timer tasks on the standby. @@ -208,8 +208,8 @@ func (s *timeSkippingReplicationSuite) TestFastForwardDisablePropagates() { ) runID := s.startSkippingWorkflow(ctx, ns, wfID, tq, 24*time.Hour, 0, &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(fastForward), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForward), Id: "ff-id"}, }, ) @@ -289,8 +289,8 @@ func (s *timeSkippingReplicationSuite) TestStandbyTimeSkippingTimerTaskAcksOnRea const fastForward = 30 * time.Minute runID := s.startSkippingWorkflow(ctx, ns, wfID, tq, 24*time.Hour, 0, &commonpb.TimeSkippingConfig{ - Enabled: true, - FastForward: durationpb.New(fastForward), + Enabled: true, + FastForwardConfig: &commonpb.FastForwardConfig{Duration: durationpb.New(fastForward), Id: "ff-id"}, }, )