diff --git a/Makefile b/Makefile index dc8ab5025..02aaeeb41 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ $(PROTO_OUT): mkdir $(PROTO_OUT) ##### Compile proto files for go ##### -grpc: buf-lint api-linter buf-breaking clean go-grpc fix-path +grpc: buf-lint api-linter clean go-grpc fix-path go-grpc: clean $(PROTO_OUT) printf $(COLOR) "Compile for go-gRPC..." diff --git a/openapi/openapiv2.json b/openapi/openapiv2.json index 97ff181c9..6705d1fc5 100644 --- a/openapi/openapiv2.json +++ b/openapi/openapiv2.json @@ -4427,6 +4427,55 @@ ] } }, + "/api/v1/namespaces/{namespace}/workflows/{workflowExecution.workflowId}/time-skipping/poll": { + "get": { + "operationId": "PollWorkflowExecutionTimeSkipping2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1PollWorkflowExecutionTimeSkippingResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "workflowExecution.workflowId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "workflowExecution.runId", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "fastForwardId", + "description": "Required. Identifies the fast-forward whose completion the caller wants to wait for.\nMust match the `fast_forward_id` set in the execution's TimeSkippingConfig.", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "WorkflowService" + ] + } + }, "/api/v1/namespaces/{namespace}/workflows/{workflowExecution.workflowId}/update-options": { "post": { "summary": "UpdateWorkflowExecutionOptions partially updates the WorkflowExecutionOptions of an existing workflow execution.", @@ -9961,6 +10010,55 @@ ] } }, + "/namespaces/{namespace}/workflows/{workflowExecution.workflowId}/time-skipping/poll": { + "get": { + "operationId": "PollWorkflowExecutionTimeSkipping", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1PollWorkflowExecutionTimeSkippingResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "workflowExecution.workflowId", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "workflowExecution.runId", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "fastForwardId", + "description": "Required. Identifies the fast-forward whose completion the caller wants to wait for.\nMust match the `fast_forward_id` set in the execution's TimeSkippingConfig.", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "WorkflowService" + ] + } + }, "/namespaces/{namespace}/workflows/{workflowExecution.workflowId}/update-options": { "post": { "summary": "UpdateWorkflowExecutionOptions partially updates the WorkflowExecutionOptions of an existing workflow execution.", @@ -15832,6 +15930,32 @@ } } }, + "v1FastForwardConfig": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "A client-supplied ID, required field, set alongside `duration`. It is used to poll for\nfast-forward completion via PollWorkflowExecutionTimeSkipping.\nThe server performs no idempotency check on this ID; the client is responsible for managing it." + }, + "duration": { + "type": "string", + "description": "Fast-forward the current execution by this duration ahead of the current execution time; required field.\nThe duration yields a target time (current execution time + duration), surfaced as `target_time` in\nTimeSkippingFastForwardInfo. Once virtual time reaches that target, the fast-forward completes, time\nskipping is disabled, and no further time is skipped. Time skipping can be resumed either\nby updating the TimeSkippingConfig with a new FastForwardConfig, or by clearing the FastForwardConfig\nto skip through to the end of the execution.\n\nIf this duration exceeds the remaining execution timeout, time will not pass beyond the end\nof the execution, and the fast-forward won't have a chance to complete." + } + } + }, + "v1FastForwardPollingResult": { + "type": "string", + "enum": [ + "FAST_FORWARD_POLLING_RESULT_UNSPECIFIED", + "FAST_FORWARD_POLLING_RESULT_POLL_TIMEOUT", + "FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED", + "FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_ID_MISMATCH", + "FAST_FORWARD_POLLING_RESULT_EXECUTION_ENDED_BEFORE_FAST_FORWARD_COMPLETION", + "FAST_FORWARD_POLLING_RESULT_TIME_SKIPPING_DISABLED_BEFORE_FAST_FORWARD_COMPLETION" + ], + "default": "FAST_FORWARD_POLLING_RESULT_UNSPECIFIED", + "description": "FastForwardPollingResult is the result of polling and waiting for a fast-forward to complete\non a time-skipping execution.\nFAST_FORWARD_POLLING_RESULT_POLL_TIMEOUT and FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED\nare the normal poll outcomes; the remaining values indicate potential improper usage of fast-forward on the client side.\n\n - FAST_FORWARD_POLLING_RESULT_UNSPECIFIED: Never returned; guards against an unset result.\n - FAST_FORWARD_POLLING_RESULT_POLL_TIMEOUT: The poll timed out server-side before the fast-forward completed. The caller may poll again.\n - FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED: The fast-forward identified by the request's `fast_forward_id` reached its target time and completed.\n - FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_ID_MISMATCH: The request's `fast_forward_id` does not match the execution's current `fast_forward_id`.\n - FAST_FORWARD_POLLING_RESULT_EXECUTION_ENDED_BEFORE_FAST_FORWARD_COMPLETION: The request's `fast_forward_id` matched the execution's current `fast_forward_id`,\nbut the execution (the entire chain of runs) completed before the fast-forward\nhad a chance to complete.\n - FAST_FORWARD_POLLING_RESULT_TIME_SKIPPING_DISABLED_BEFORE_FAST_FORWARD_COMPLETION: The request's `fast_forward_id` matched the execution's current `fast_forward_id`,\nbut time skipping was disabled (e.g. `max_session_skip_count` was reached) before the\nfast-forward could complete." + }, "v1FetchWorkerConfigResponse": { "type": "object", "properties": { @@ -17971,6 +18095,18 @@ } } }, + "v1PollWorkflowExecutionTimeSkippingResponse": { + "type": "object", + "properties": { + "fastForwardPollingResult": { + "$ref": "#/definitions/v1FastForwardPollingResult", + "description": "The outcome of the poll for the fast-forward identified by the request's `fast_forward_id`." + }, + "fastForwardInfo": { + "$ref": "#/definitions/v1TimeSkippingFastForwardInfo" + } + } + }, "v1PollWorkflowTaskQueueResponse": { "type": "object", "properties": { @@ -19892,31 +20028,88 @@ "type": "boolean", "description": "Enables or disables time skipping for this workflow execution." }, - "fastForward": { - "type": "string", - "description": "Optionally fast-forward the current workflow execution by this duration ahead of current workflow execution time.\nAfter the fast-forward completes, time skipping is disabled, and this\naction is recorded in the WorkflowExecutionTimeSkippingTransitionedEvent. It can be re-enabled by\nsetting `enabled` to true or setting `fast_forward` again via UpdateWorkflowExecutionOptions.\nThe current workflow execution is a chain of runs (retries, cron, continue-as-new);\nchild workflows are separate executions, so this fast_forward won't affect them.\n\nFor a given workflow execution, only one active fast-forward is allowed at a time.\nIf a new fast-forward is set via UpdateWorkflowExecutionOptions before the previous\none completes, the new one will override the previous one.\nIf the fast-forward duration exceeds the remaining execution timeout, time will only\nbe fast-forwarded up to the end of the execution." + "fastForwardConfig": { + "$ref": "#/definitions/v1FastForwardConfig", + "description": "An optional opt-in to control time-skipping behavior through fast-forward; see its definition for details." }, "disablePropagation": { "type": "boolean", - "description": "By default, executions started by another execution (e.g. a child workflow of a parent workflow or\na schedule with the timeskipping policy enabled), inherit the \"enabled\" flag and skip time when possible.\nThis flag disables that inheritance." + "description": "By default, executions started by another execution (e.g. a child workflow of a parent workflow or\na schedule with the time-skipping policy enabled) inherit the \"enabled\" flag and skip time when possible.\nThis flag disables that inheritance." + }, + "maxSessionSkipCount": { + "type": "integer", + "format": "int32", + "description": "The maximum number of skips allowed every time this field is updated. It protects the execution from\nsituations like unlimited retries when backoff is skipped.\n\nEvery time the execution skips time, the skip count is incremented by one, and when it reaches\nmax_session_skip_count, time skipping stops. Whenever this config field is updated, the accumulated\nskip count is cleared, marking the start of a new session.\nFor an execution with a chain of runs (retry, cron, continue-as-new), the count is accumulated\nacross all runs within the same session.\n\nIf this field is not set, the server applies a large default value (e.g. 100). The default can\nbe changed through dynamic config, and is overridden by this field when set." + } + }, + "description": "The configuration for time skipping of an execution.\nWhen time skipping is enabled, virtual time advances automatically whenever there is no in-flight work.\nOptions like fast_forward, disable_propagation, and max_session_skip_count are provided for granular\ncontrol of the execution's time skipping behavior. See each field's comment for a detailed explanation.\n\nAn example of workflows with time skipping:\nFor workflows, an execution is a chain of runs including retries, cron, and continue-as-new.\nIn-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations, etc.\nUser timers are not classified as in-flight work and will be skipped over; the virtual clock may also skip to the\ntime point of the registered fast-forward when there is no in-flight work.\nWhenever time is skipped, the skip count is incremented by one; max_session_skip_count bounds the number of skips allowed within a single time-skipping session.\nFor child workflows, by default, if the parent execution is skipping time, the child execution will also skip time,\nbut a parent's fast_forward won't affect its child's execution. A flag is provided to disable propagation of the\n\"enabled\" flag to child workflows; regardless of that flag, a child workflow inherits the virtual time from the\nparent execution as its start time." + }, + "v1TimeSkippingFastForwardInfo": { + "type": "object", + "properties": { + "fastForwardDuration": { + "type": "string", + "description": "The client-supplied `fast_forward` duration." + }, + "fastForwardId": { + "type": "string", + "description": "The client-supplied ID set alongside `fast_forward` duration." + }, + "targetTime": { + "type": "string", + "format": "date-time", + "description": "The target virtual time at which the fast-forward completes." + }, + "hasCompleted": { + "type": "boolean", + "description": "True once `target_time` has been reached." } }, - "description": "The configuration for time skipping of a workflow execution (a chain of runs including retries, cron, continue-as-new).\nWhen time skipping is enabled, virtual time advances automatically whenever there is no in-flight work.\nIn-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations,\nand possibly other features added in the future.\nUser timers are not classified as in-flight work and will be skipped over; the virtual clock may also skip to the\ntime point of the registered fast forward when there is no in-flight work.\nWhen time is skipped, a WorkflowExecutionTimeSkippingTransitionedEvent will be\nadded to the workflow history to capture the state changes.\n\nFor child workflows, by default, if the parent execution is skipping time, the child execution will also skip time,\nbut a parent's fast_forward won't affect its child's execution. A flag is provided to disable propagation of the\n\"enabled\" flag to child workflows; regardless of that flag, a child workflow inherits the virtual time from the\nparent execution as its start time." + "description": "TimeSkippingFastForwardInfo describes the current time-skipping fast-forward on an execution." + }, + "v1TimeSkippingInfo": { + "type": "object", + "properties": { + "currentTime": { + "type": "string", + "format": "date-time", + "description": "Current virtual time of the execution. If the execution hasn't skipped\nany time yet, it will be the same as wall clock time." + }, + "effectiveConfig": { + "$ref": "#/definitions/v1TimeSkippingConfig", + "description": "The current effective time-skipping config, which can differ from the config the user last set:\ninternally-defaulted fields are populated, and `enabled` reflects whether the execution is still\nskipping time — e.g. it is set to false once `max_session_skip_count` is reached, the fast-forward\ncompletes, or a client call disables time skipping." + }, + "fastForwardInfo": { + "$ref": "#/definitions/v1TimeSkippingFastForwardInfo", + "description": "The execution's current fast-forward, if any. Unset if time skipping is enabled without a fast-forward." + }, + "currentSessionSkipCount": { + "type": "integer", + "format": "int32", + "description": "The number of skips accumulated in the current session, bounded by `max_session_skip_count`.\nA new session begins — and this resets to 0 — each time `max_session_skip_count` is updated." + } + }, + "description": "Describes the current time-skipping state of a workflow execution." }, "v1TimeSkippingStatePropagation": { "type": "object", "properties": { "initialSkippedDuration": { "type": "string", - "description": "The time skipped by the previous execution that started this workflow.\nIt can happen in child workflows and a chain of runs (CaN, cron, retry)." + "description": "The time skipped by the previous run. It is propagated both to executions started by the\ncurrent execution and through a chain of runs (CaN, cron, retry)." }, "fastForwardTargetTime": { "type": "string", "format": "date-time", - "description": "If there is a fast-forward action set for the previous run in a chain of runs,\nthe target time should be propagated to the next run as well." + "description": "The fast-forward target time. It only propagates across a chain of runs within the same execution." + }, + "initialSkipCount": { + "type": "integer", + "format": "int32", + "description": "The initial skip count. It only propagates across a chain of runs within the same execution." } }, - "description": "The time-skipping state that needs to be propagated from a parent workflow to a child workflow,\nor through a chain of runs." + "description": "The time-skipping state that needs to be propagated from one execution to another, or through a chain of runs\nwithin the same execution." }, "v1TimeoutFailureInfo": { "type": "object", @@ -20217,7 +20410,7 @@ "updateTime": { "type": "string", "format": "date-time", - "description": "The Workflow Execution time when the options were updated. When time skipping is\nenabled, this is the workflow's virtual time rather than wall-clock time." + "description": "The Workflow Execution time when the options were updated. When time skipping is\nenabled, this is the workflow's virtual time rather than wall-clock time.\n\nThis timestamp cannot be used for time-skipping fast-forward verification,\nuse `fast_forward_id` in `PollWorkflowExecutionTimeSkippingRequest` instead." } } }, @@ -21086,6 +21279,10 @@ "pauseInfo": { "$ref": "#/definitions/v1WorkflowExecutionPauseInfo", "description": "Information about the workflow execution pause operation." + }, + "timeSkippingInfo": { + "$ref": "#/definitions/v1TimeSkippingInfo", + "description": "Information about time skipping of the workflow execution.\nIf the execution has never enabled time skipping, it will be nil." } }, "description": "Holds all the extra information about workflow execution that is not part of Visibility." @@ -21559,7 +21756,7 @@ }, "disabledAfterFastForward": { "type": "boolean", - "description": "When true, time skipping has been disabled automatically due to a call to fast_forward completing." + "description": "When true, time skipping has been stopped automatically due to a call to fast_forward completing." }, "wallClockTime": { "type": "string", @@ -21567,7 +21764,7 @@ "description": "The wall-clock time when the time-skipping state changed event was generated." } }, - "description": "Attributes for an event indicating that time skipping state changed for a workflow execution,\neither time was advanced or time skipping was disabled automatically due to the fast_forward completing.\nThe worker_may_ignore field in HistoryEvent should always be set true for this event." + "description": "Attributes for an event indicating that time skipping state changed for a workflow execution:\neither time was advanced, or time skipping was stopped automatically due to the fast_forward completing.\nThe worker_may_ignore field in HistoryEvent should always be set true for this event." }, "v1WorkflowExecutionTimedOutEventAttributes": { "type": "object", diff --git a/openapi/openapiv3.yaml b/openapi/openapiv3.yaml index f2081bd9b..ce0151fc3 100644 --- a/openapi/openapiv3.yaml +++ b/openapi/openapiv3.yaml @@ -4572,6 +4572,50 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' + /api/v1/namespaces/{namespace}/workflows/{workflow_execution.workflow_id}/time-skipping/poll: + get: + tags: + - WorkflowService + operationId: PollWorkflowExecutionTimeSkipping + parameters: + - name: namespace + in: path + required: true + schema: + type: string + - name: workflow_execution.workflow_id + in: path + required: true + schema: + type: string + - name: workflowExecution.workflowId + in: query + schema: + type: string + - name: workflowExecution.runId + in: query + schema: + type: string + - name: fastForwardId + in: query + description: |- + Required. Identifies the fast-forward whose completion the caller wants to wait for. + Must match the `fast_forward_id` set in the execution's TimeSkippingConfig. + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PollWorkflowExecutionTimeSkippingResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' /api/v1/namespaces/{namespace}/workflows/{workflow_execution.workflow_id}/update-options: post: tags: @@ -9568,6 +9612,50 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' + /namespaces/{namespace}/workflows/{workflow_execution.workflow_id}/time-skipping/poll: + get: + tags: + - WorkflowService + operationId: PollWorkflowExecutionTimeSkipping + parameters: + - name: namespace + in: path + required: true + schema: + type: string + - name: workflow_execution.workflow_id + in: path + required: true + schema: + type: string + - name: workflowExecution.workflowId + in: query + schema: + type: string + - name: workflowExecution.runId + in: query + schema: + type: string + - name: fastForwardId + in: query + description: |- + Required. Identifies the fast-forward whose completion the caller wants to wait for. + Must match the `fast_forward_id` set in the execution's TimeSkippingConfig. + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PollWorkflowExecutionTimeSkippingResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' /namespaces/{namespace}/workflows/{workflow_execution.workflow_id}/update-options: post: tags: @@ -12254,6 +12342,28 @@ components: $ref: '#/components/schemas/NexusOperationFailureInfo' nexusHandlerFailureInfo: $ref: '#/components/schemas/NexusHandlerFailureInfo' + FastForwardConfig: + type: object + properties: + id: + type: string + description: |- + A client-supplied ID, required field, set alongside `duration`. It is used to poll for + fast-forward completion via PollWorkflowExecutionTimeSkipping. + The server performs no idempotency check on this ID; the client is responsible for managing it. + duration: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + description: |- + Fast-forward the current execution by this duration ahead of the current execution time; required field. + The duration yields a target time (current execution time + duration), surfaced as `target_time` in + TimeSkippingFastForwardInfo. Once virtual time reaches that target, the fast-forward completes, time + skipping is disabled, and no further time is skipped. Time skipping can be resumed either + by updating the TimeSkippingConfig with a new FastForwardConfig, or by clearing the FastForwardConfig + to skip through to the end of the execution. + + If this duration exceeds the remaining execution timeout, time will not pass beyond the end + of the execution, and the fast-forward won't have a chance to complete. FetchWorkerConfigRequest: type: object properties: @@ -14396,6 +14506,22 @@ components: allOf: - $ref: '#/components/schemas/Failure' description: The failure if the operation completed unsuccessfully. + PollWorkflowExecutionTimeSkippingResponse: + type: object + properties: + fastForwardPollingResult: + enum: + - FAST_FORWARD_POLLING_RESULT_UNSPECIFIED + - FAST_FORWARD_POLLING_RESULT_POLL_TIMEOUT + - FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED + - FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_ID_MISMATCH + - FAST_FORWARD_POLLING_RESULT_EXECUTION_ENDED_BEFORE_FAST_FORWARD_COMPLETION + - FAST_FORWARD_POLLING_RESULT_TIME_SKIPPING_DISABLED_BEFORE_FAST_FORWARD_COMPLETION + type: string + description: The outcome of the poll for the fast-forward identified by the request's `fast_forward_id`. + format: enum + fastForwardInfo: + $ref: '#/components/schemas/TimeSkippingFastForwardInfo' PollWorkflowTaskQueueResponse: type: object properties: @@ -17656,42 +17782,93 @@ components: enabled: type: boolean description: Enables or disables time skipping for this workflow execution. - fastForward: - pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ - type: string - description: |- - Optionally fast-forward the current workflow execution by this duration ahead of current workflow execution time. - After the fast-forward completes, time skipping is disabled, and this - action is recorded in the WorkflowExecutionTimeSkippingTransitionedEvent. It can be re-enabled by - setting `enabled` to true or setting `fast_forward` again via UpdateWorkflowExecutionOptions. - The current workflow execution is a chain of runs (retries, cron, continue-as-new); - child workflows are separate executions, so this fast_forward won't affect them. - - For a given workflow execution, only one active fast-forward is allowed at a time. - If a new fast-forward is set via UpdateWorkflowExecutionOptions before the previous - one completes, the new one will override the previous one. - If the fast-forward duration exceeds the remaining execution timeout, time will only - be fast-forwarded up to the end of the execution. + fastForwardConfig: + allOf: + - $ref: '#/components/schemas/FastForwardConfig' + description: An optional opt-in to control time-skipping behavior through fast-forward; see its definition for details. disablePropagation: type: boolean description: |- By default, executions started by another execution (e.g. a child workflow of a parent workflow or - a schedule with the timeskipping policy enabled), inherit the "enabled" flag and skip time when possible. + a schedule with the time-skipping policy enabled) inherit the "enabled" flag and skip time when possible. This flag disables that inheritance. + maxSessionSkipCount: + type: integer + description: |- + The maximum number of skips allowed every time this field is updated. It protects the execution from + situations like unlimited retries when backoff is skipped. + + Every time the execution skips time, the skip count is incremented by one, and when it reaches + max_session_skip_count, time skipping stops. Whenever this config field is updated, the accumulated + skip count is cleared, marking the start of a new session. + For an execution with a chain of runs (retry, cron, continue-as-new), the count is accumulated + across all runs within the same session. + + If this field is not set, the server applies a large default value (e.g. 100). The default can + be changed through dynamic config, and is overridden by this field when set. + format: int32 description: |- - The configuration for time skipping of a workflow execution (a chain of runs including retries, cron, continue-as-new). + The configuration for time skipping of an execution. When time skipping is enabled, virtual time advances automatically whenever there is no in-flight work. - In-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations, - and possibly other features added in the future. - User timers are not classified as in-flight work and will be skipped over; the virtual clock may also skip to the - time point of the registered fast forward when there is no in-flight work. - When time is skipped, a WorkflowExecutionTimeSkippingTransitionedEvent will be - added to the workflow history to capture the state changes. + Options like fast_forward, disable_propagation, and max_session_skip_count are provided for granular + control of the execution's time skipping behavior. See each field's comment for a detailed explanation. + An example of workflows with time skipping: + For workflows, an execution is a chain of runs including retries, cron, and continue-as-new. + In-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations, etc. + User timers are not classified as in-flight work and will be skipped over; the virtual clock may also skip to the + time point of the registered fast-forward when there is no in-flight work. + Whenever time is skipped, the skip count is incremented by one; max_session_skip_count bounds the number of skips allowed within a single time-skipping session. For child workflows, by default, if the parent execution is skipping time, the child execution will also skip time, but a parent's fast_forward won't affect its child's execution. A flag is provided to disable propagation of the "enabled" flag to child workflows; regardless of that flag, a child workflow inherits the virtual time from the parent execution as its start time. + TimeSkippingFastForwardInfo: + type: object + properties: + fastForwardDuration: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + description: The client-supplied `fast_forward` duration. + fastForwardId: + type: string + description: The client-supplied ID set alongside `fast_forward` duration. + targetTime: + type: string + description: The target virtual time at which the fast-forward completes. + format: date-time + hasCompleted: + type: boolean + description: True once `target_time` has been reached. + description: TimeSkippingFastForwardInfo describes the current time-skipping fast-forward on an execution. + TimeSkippingInfo: + type: object + properties: + currentTime: + type: string + description: |- + Current virtual time of the execution. If the execution hasn't skipped + any time yet, it will be the same as wall clock time. + format: date-time + effectiveConfig: + allOf: + - $ref: '#/components/schemas/TimeSkippingConfig' + description: |- + The current effective time-skipping config, which can differ from the config the user last set: + internally-defaulted fields are populated, and `enabled` reflects whether the execution is still + skipping time — e.g. it is set to false once `max_session_skip_count` is reached, the fast-forward + completes, or a client call disables time skipping. + fastForwardInfo: + allOf: + - $ref: '#/components/schemas/TimeSkippingFastForwardInfo' + description: The execution's current fast-forward, if any. Unset if time skipping is enabled without a fast-forward. + currentSessionSkipCount: + type: integer + description: |- + The number of skips accumulated in the current session, bounded by `max_session_skip_count`. + A new session begins — and this resets to 0 — each time `max_session_skip_count` is updated. + format: int32 + description: Describes the current time-skipping state of a workflow execution. TimeSkippingStatePropagation: type: object properties: @@ -17699,17 +17876,19 @@ components: pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ type: string description: |- - The time skipped by the previous execution that started this workflow. - It can happen in child workflows and a chain of runs (CaN, cron, retry). + The time skipped by the previous run. It is propagated both to executions started by the + current execution and through a chain of runs (CaN, cron, retry). fastForwardTargetTime: type: string - description: |- - If there is a fast-forward action set for the previous run in a chain of runs, - the target time should be propagated to the next run as well. + description: The fast-forward target time. It only propagates across a chain of runs within the same execution. format: date-time + initialSkipCount: + type: integer + description: The initial skip count. It only propagates across a chain of runs within the same execution. + format: int32 description: |- - The time-skipping state that needs to be propagated from a parent workflow to a child workflow, - or through a chain of runs. + The time-skipping state that needs to be propagated from one execution to another, or through a chain of runs + within the same execution. TimeoutFailureInfo: type: object properties: @@ -18379,6 +18558,9 @@ components: description: |- The Workflow Execution time when the options were updated. When time skipping is enabled, this is the workflow's virtual time rather than wall-clock time. + + This timestamp cannot be used for time-skipping fast-forward verification, + use `fast_forward_id` in `PollWorkflowExecutionTimeSkippingRequest` instead. format: date-time UpdateWorkflowExecutionRequest: type: object @@ -19635,6 +19817,12 @@ components: allOf: - $ref: '#/components/schemas/WorkflowExecutionPauseInfo' description: Information about the workflow execution pause operation. + timeSkippingInfo: + allOf: + - $ref: '#/components/schemas/TimeSkippingInfo' + description: |- + Information about time skipping of the workflow execution. + If the execution has never enabled time skipping, it will be nil. description: Holds all the extra information about workflow execution that is not part of Visibility. WorkflowExecutionFailedEventAttributes: type: object @@ -20201,7 +20389,7 @@ components: disabledAfterFastForward: type: boolean description: |- - When true, time skipping has been disabled automatically due to a call to fast_forward completing. + When true, time skipping has been stopped automatically due to a call to fast_forward completing. (-- api-linter: core::0140::prepositions=disabled aip.dev/not-precedent: "after" is used to indicate temporal ordering. --) wallClockTime: @@ -20209,8 +20397,8 @@ components: description: The wall-clock time when the time-skipping state changed event was generated. format: date-time description: |- - Attributes for an event indicating that time skipping state changed for a workflow execution, - either time was advanced or time skipping was disabled automatically due to the fast_forward completing. + Attributes for an event indicating that time skipping state changed for a workflow execution: + either time was advanced, or time skipping was stopped automatically due to the fast_forward completing. The worker_may_ignore field in HistoryEvent should always be set true for this event. WorkflowExecutionTimedOutEventAttributes: type: object diff --git a/temporal/api/common/v1/message.proto b/temporal/api/common/v1/message.proto index 1af5b4acc..3e2fc0e1c 100644 --- a/temporal/api/common/v1/message.proto +++ b/temporal/api/common/v1/message.proto @@ -403,52 +403,111 @@ message OnConflictOptions { bool attach_links = 3; } -// The configuration for time skipping of a workflow execution (a chain of runs including retries, cron, continue-as-new). +// The configuration for time skipping of an execution. // When time skipping is enabled, virtual time advances automatically whenever there is no in-flight work. -// In-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations, -// and possibly other features added in the future. -// User timers are not classified as in-flight work and will be skipped over; the virtual clock may also skip to the -// time point of the registered fast forward when there is no in-flight work. -// When time is skipped, a WorkflowExecutionTimeSkippingTransitionedEvent will be -// added to the workflow history to capture the state changes. +// Options like fast_forward, disable_propagation, and max_session_skip_count are provided for granular +// control of the execution's time skipping behavior. See each field's comment for a detailed explanation. // +// An example of workflows with time skipping: +// For workflows, an execution is a chain of runs including retries, cron, and continue-as-new. +// In-flight work includes activities, child workflows, Nexus operations, signal/cancel external workflow operations, etc. +// User timers are not classified as in-flight work and will be skipped over; the virtual clock may also skip to the +// time point of the registered fast-forward when there is no in-flight work. +// Whenever time is skipped, the skip count is incremented by one; max_session_skip_count bounds the number of skips allowed within a single time-skipping session. // For child workflows, by default, if the parent execution is skipping time, the child execution will also skip time, // but a parent's fast_forward won't affect its child's execution. A flag is provided to disable propagation of the // "enabled" flag to child workflows; regardless of that flag, a child workflow inherits the virtual time from the // parent execution as its start time. message TimeSkippingConfig { + // Enables or disables time skipping for this workflow execution. bool enabled = 1; - // Optionally fast-forward the current workflow execution by this duration ahead of current workflow execution time. - // After the fast-forward completes, time skipping is disabled, and this - // action is recorded in the WorkflowExecutionTimeSkippingTransitionedEvent. It can be re-enabled by - // setting `enabled` to true or setting `fast_forward` again via UpdateWorkflowExecutionOptions. - // The current workflow execution is a chain of runs (retries, cron, continue-as-new); - // child workflows are separate executions, so this fast_forward won't affect them. - // - // For a given workflow execution, only one active fast-forward is allowed at a time. - // If a new fast-forward is set via UpdateWorkflowExecutionOptions before the previous - // one completes, the new one will override the previous one. - // If the fast-forward duration exceeds the remaining execution timeout, time will only - // be fast-forwarded up to the end of the execution. - google.protobuf.Duration fast_forward = 2; + // An optional opt-in to control time-skipping behavior through fast-forward; see its definition for details. + FastForwardConfig fast_forward_config = 2; // By default, executions started by another execution (e.g. a child workflow of a parent workflow or - // a schedule with the timeskipping policy enabled), inherit the "enabled" flag and skip time when possible. + // a schedule with the time-skipping policy enabled) inherit the "enabled" flag and skip time when possible. // This flag disables that inheritance. bool disable_propagation = 3; + + // The maximum number of skips allowed every time this field is updated. It protects the execution from + // situations like unlimited retries when backoff is skipped. + // + // Every time the execution skips time, the skip count is incremented by one, and when it reaches + // max_session_skip_count, time skipping stops. Whenever this config field is updated, the accumulated + // skip count is cleared, marking the start of a new session. + // For an execution with a chain of runs (retry, cron, continue-as-new), the count is accumulated + // across all runs within the same session. + // + // If this field is not set, the server applies a large default value (e.g. 100). The default can + // be changed through dynamic config, and is overridden by this field when set. + int32 max_session_skip_count = 4; } -// The time-skipping state that needs to be propagated from a parent workflow to a child workflow, -// or through a chain of runs. +message FastForwardConfig { + // A client-supplied ID, required field, set alongside `duration`. It is used to poll for + // fast-forward completion via PollWorkflowExecutionTimeSkipping. + // The server performs no idempotency check on this ID; the client is responsible for managing it. + string id = 1; + + // Fast-forward the current execution by this duration ahead of the current execution time; required field. + // The duration yields a target time (current execution time + duration), surfaced as `target_time` in + // TimeSkippingFastForwardInfo. Once virtual time reaches that target, the fast-forward completes, time + // skipping is disabled, and no further time is skipped. Time skipping can be resumed either + // by updating the TimeSkippingConfig with a new FastForwardConfig, or by clearing the FastForwardConfig + // to skip through to the end of the execution. + // + // If this duration exceeds the remaining execution timeout, time will not pass beyond the end + // of the execution, and the fast-forward won't have a chance to complete. + google.protobuf.Duration duration = 2; +} + +// The time-skipping state that needs to be propagated from one execution to another, or through a chain of runs +// within the same execution. message TimeSkippingStatePropagation { - // The time skipped by the previous execution that started this workflow. - // It can happen in child workflows and a chain of runs (CaN, cron, retry). + // The time skipped by the previous run. It is propagated both to executions started by the + // current execution and through a chain of runs (CaN, cron, retry). google.protobuf.Duration initial_skipped_duration = 1; - // If there is a fast-forward action set for the previous run in a chain of runs, - // the target time should be propagated to the next run as well. + // The fast-forward target time. It only propagates across a chain of runs within the same execution. google.protobuf.Timestamp fast_forward_target_time = 2; + + // The initial skip count. It only propagates across a chain of runs within the same execution. + int32 initial_skip_count = 3; +} + + +// Describes the current time-skipping state of a workflow execution. +message TimeSkippingInfo { + // Current virtual time of the execution. If the execution hasn't skipped + // any time yet, it will be the same as wall clock time. + google.protobuf.Timestamp current_time = 1; + + // The current effective time-skipping config, which can differ from the config the user last set: + // internally-defaulted fields are populated, and `enabled` reflects whether the execution is still + // skipping time — e.g. it is set to false once `max_session_skip_count` is reached, the fast-forward + // completes, or a client call disables time skipping. + TimeSkippingConfig effective_config = 2; + + // The execution's current fast-forward, if any. Unset if time skipping is enabled without a fast-forward. + TimeSkippingFastForwardInfo fast_forward_info = 4; + + // The number of skips accumulated in the current session, bounded by `max_session_skip_count`. + // A new session begins — and this resets to 0 — each time `max_session_skip_count` is updated. + int32 current_session_skip_count = 6; +} + + +// TimeSkippingFastForwardInfo describes the current time-skipping fast-forward on an execution. +message TimeSkippingFastForwardInfo { + // The client-supplied `fast_forward` duration. + google.protobuf.Duration fast_forward_duration = 1; + // The client-supplied ID set alongside `fast_forward` duration. + string fast_forward_id = 2; + // The target virtual time at which the fast-forward completes. + google.protobuf.Timestamp target_time = 3; + // True once `target_time` has been reached. + bool has_completed = 4; } diff --git a/temporal/api/enums/v1/time_skipping.proto b/temporal/api/enums/v1/time_skipping.proto new file mode 100644 index 000000000..b02af81a1 --- /dev/null +++ b/temporal/api/enums/v1/time_skipping.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package temporal.api.enums.v1; + +option go_package = "go.temporal.io/api/enums/v1;enums"; +option java_package = "io.temporal.api.enums.v1"; +option java_multiple_files = true; +option java_outer_classname = "TimeSkippingProto"; +option ruby_package = "Temporalio::Api::Enums::V1"; +option csharp_namespace = "Temporalio.Api.Enums.V1"; + + +// FastForwardPollingResult is the result of polling and waiting for a fast-forward to complete +// on a time-skipping execution. +// FAST_FORWARD_POLLING_RESULT_POLL_TIMEOUT and FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED +// are the normal poll outcomes; the remaining values indicate potential improper usage of fast-forward on the client side. +enum FastForwardPollingResult { + // Never returned; guards against an unset result. + FAST_FORWARD_POLLING_RESULT_UNSPECIFIED = 0; + // The poll timed out server-side before the fast-forward completed. The caller may poll again. + FAST_FORWARD_POLLING_RESULT_POLL_TIMEOUT = 1; + // The fast-forward identified by the request's `fast_forward_id` reached its target time and completed. + FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_COMPLETED = 2; + // The request's `fast_forward_id` does not match the execution's current `fast_forward_id`. + FAST_FORWARD_POLLING_RESULT_FAST_FORWARD_ID_MISMATCH = 3; + // The request's `fast_forward_id` matched the execution's current `fast_forward_id`, + // but the execution (the entire chain of runs) completed before the fast-forward + // had a chance to complete. + FAST_FORWARD_POLLING_RESULT_EXECUTION_ENDED_BEFORE_FAST_FORWARD_COMPLETION = 5; + // The request's `fast_forward_id` matched the execution's current `fast_forward_id`, + // but time skipping was disabled (e.g. `max_session_skip_count` was reached) before the + // fast-forward could complete. + FAST_FORWARD_POLLING_RESULT_TIME_SKIPPING_DISABLED_BEFORE_FAST_FORWARD_COMPLETION = 6; +} diff --git a/temporal/api/history/v1/message.proto b/temporal/api/history/v1/message.proto index 5331294b1..0211c6f55 100644 --- a/temporal/api/history/v1/message.proto +++ b/temporal/api/history/v1/message.proto @@ -1021,14 +1021,14 @@ message WorkflowExecutionUnpausedEventAttributes { string request_id = 3; } -// Attributes for an event indicating that time skipping state changed for a workflow execution, -// either time was advanced or time skipping was disabled automatically due to the fast_forward completing. +// Attributes for an event indicating that time skipping state changed for a workflow execution: +// either time was advanced, or time skipping was stopped automatically due to the fast_forward completing. // The worker_may_ignore field in HistoryEvent should always be set true for this event. message WorkflowExecutionTimeSkippingTransitionedEventAttributes { // The virtual time point that time skipping advanced to. google.protobuf.Timestamp target_time = 1; - // When true, time skipping has been disabled automatically due to a call to fast_forward completing. + // When true, time skipping has been stopped automatically due to a call to fast_forward completing. // (-- api-linter: core::0140::prepositions=disabled // aip.dev/not-precedent: "after" is used to indicate temporal ordering. --) bool disabled_after_fast_forward = 2; diff --git a/temporal/api/workflow/v1/message.proto b/temporal/api/workflow/v1/message.proto index c09370f7c..1ed33fa4c 100644 --- a/temporal/api/workflow/v1/message.proto +++ b/temporal/api/workflow/v1/message.proto @@ -134,6 +134,10 @@ message WorkflowExecutionExtendedInfo { // Information about the workflow execution pause operation. WorkflowExecutionPauseInfo pause_info = 8; + + // Information about time skipping of the workflow execution. + // If the execution has never enabled time skipping, it will be nil. + temporal.api.common.v1.TimeSkippingInfo time_skipping_info = 9; } // Holds all the information about worker versioning for a particular workflow execution. diff --git a/temporal/api/workflowservice/v1/request_response.proto b/temporal/api/workflowservice/v1/request_response.proto index 66f478c5c..aed44bc34 100644 --- a/temporal/api/workflowservice/v1/request_response.proto +++ b/temporal/api/workflowservice/v1/request_response.proto @@ -19,6 +19,7 @@ import "temporal/api/enums/v1/reset.proto"; import "temporal/api/enums/v1/task_queue.proto"; import "temporal/api/enums/v1/deployment.proto"; import "temporal/api/enums/v1/update.proto"; +import "temporal/api/enums/v1/time_skipping.proto"; import "temporal/api/enums/v1/activity.proto"; import "temporal/api/enums/v1/nexus.proto"; import "temporal/api/activity/v1/message.proto"; @@ -2448,6 +2449,9 @@ message UpdateWorkflowExecutionOptionsResponse { // The Workflow Execution time when the options were updated. When time skipping is // enabled, this is the workflow's virtual time rather than wall-clock time. + // + // This timestamp cannot be used for time-skipping fast-forward verification, + // use `fast_forward_id` in `PollWorkflowExecutionTimeSkippingRequest` instead. google.protobuf.Timestamp update_time = 2; } @@ -3634,3 +3638,21 @@ message DeleteNexusOperationExecutionRequest { message DeleteNexusOperationExecutionResponse { } + +// A long-poll request that blocks according to a time-skipping waiting policy on the workflow +// execution. Currently the only supported policy is waiting for completion of the fast-forward +// identified by `fast_forward_id`; the poll also returns once anything else settles that outcome +// (e.g. the execution ends or time skipping is disabled). +message PollWorkflowExecutionTimeSkippingRequest { + string namespace = 1; + temporal.api.common.v1.WorkflowExecution workflow_execution = 2; + // Required. Identifies the fast-forward whose completion the caller wants to wait for. + // Must match the `fast_forward_id` set in the execution's TimeSkippingConfig. + string fast_forward_id = 3; +} + +message PollWorkflowExecutionTimeSkippingResponse { + // The outcome of the poll for the fast-forward identified by the request's `fast_forward_id`. + temporal.api.enums.v1.FastForwardPollingResult fast_forward_polling_result = 1; + temporal.api.common.v1.TimeSkippingFastForwardInfo fast_forward_info = 2; +} diff --git a/temporal/api/workflowservice/v1/service.proto b/temporal/api/workflowservice/v1/service.proto index bec02b9e5..768553bd9 100644 --- a/temporal/api/workflowservice/v1/service.proto +++ b/temporal/api/workflowservice/v1/service.proto @@ -2039,4 +2039,17 @@ service WorkflowService { // (-- api-linter: core::0127::http-annotation=disabled // aip.dev/not-precedent: Nexus operation deletion not exposed to HTTP, users should use cancel or terminate. --) rpc DeleteNexusOperationExecution (DeleteNexusOperationExecutionRequest) returns (DeleteNexusOperationExecutionResponse) {} + + rpc PollWorkflowExecutionTimeSkipping (PollWorkflowExecutionTimeSkippingRequest) returns (PollWorkflowExecutionTimeSkippingResponse) { + option (google.api.http) = { + get: "/namespaces/{namespace}/workflows/{workflow_execution.workflow_id}/time-skipping/poll" + additional_bindings { + get: "/api/v1/namespaces/{namespace}/workflows/{workflow_execution.workflow_id}/time-skipping/poll" + } + }; + option (temporal.api.protometa.v1.request_header) = { + header: "temporal-resource-id" + value: "workflow:{workflow_execution.workflow_id}" + }; + } }