Skip to content

Time Skipping V2 - #1959

Merged
GregoryTravis merged 94 commits into
mainfrom
gmt/sdk-5479-ts-python
Oct 15, 2026
Merged

GregoryTravis merged 94 commits into
mainfrom
gmt/sdk-5479-ts-python

Conversation

@GregoryTravis

@GregoryTravis GregoryTravis commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Time Skipping V2

Per-workflow virtual-clock time skipping for the Python SDK. An alternative to
the V1 Java test server, has a single global virtual clock and does not run on
Linux or Windows ARM.

Public API

Testing-env entry points on temporalio.testing.WorkflowEnvironment:

  • WorkflowEnvironment.start_time_skipping_v2(...) — start a CLI dev server
    with time skipping enabled; return an environment whose client adds
    TimeSkippingConfig to every workflow it starts.
  • env.fast_forward(handle, duration=None) — advance one workflow's virtual
    clock by a timedelta/float, or (with None) skip to termination.
    Returns True on completion, False when the workflow terminates first,
    raises RuntimeError when a later fast-forward supersedes it.
  • env.get_current_time(handle) — read a workflow's current virtual time.
  • env.get_time_skipping_info(handle) — fetch TimeSkippingInfo
    (effective config, current time, in-flight fast-forward).
  • env.with_time_skipping_disabled() — context manager that suspends
    time skipping on new start_workflow calls inside the block.
  • env.supports_time_skipping_v1 / env.supports_time_skipping_v2.

Direct, non-test entry points on temporalio.testing:

  • TimeSkipper(client, config=TimeSkippingConfig(...)) — wrap an existing
    Client with per-workflow time skipping.
  • TimeSkippingConfig(enabled, fast_forward_config, disable_propagation, max_session_skip_count).
  • FastForwardConfig(id, duration).

Example — test environment

from datetime import timedelta
from temporalio.testing import WorkflowEnvironment

async with await WorkflowEnvironment.start_time_skipping_v2() as env:
    async with Worker(env.client, task_queue="tq", workflows=[MyWorkflow]):
        handle = await env.client.start_workflow(
            MyWorkflow.run, id="wf-1", task_queue="tq",
        )
        # Advance this workflow's virtual clock by 1h; nothing else moves.
        assert await env.fast_forward(handle, timedelta(hours=1))
        now = await env.get_current_time(handle)
        await handle.result()

Tests

  • 22 new V2 tests, covering the full V2 surface:
    • 20 in tests/testing/test_timeskipping_v2.py (dedicated V2 behavior).
    • 1 in tests/worker/test_workflow.py (a _ts_v2 version of
      test_workflow_cancel_signal_and_timer_fired_in_same_task, added
      alongside the V1 version in the same file).
    • 1 in tests/testing/test_workflow.py
      (test_get_time_skipping_info_raises_on_non_ts_v2_env).
  • 4 ports of existing V1 tests in tests/testing/test_workflow_ts_v2.py:
    three ports of V1 tests (basic, manual, disabled) plus one new case
    (basic_via_update_v2). The V1 heartbeat_timeout test is deliberately
    not ported since it relies on V1-specific behavior.
  • 197 tests disabled on V1 run fine on V2

…t_forward_then_unbounded, which is now failing
Comment thread AGENTS.md Outdated
Comment thread tests/testing/test_timeskipping_v2.py
Comment thread tests/testing/conftest.py Outdated
Comment thread temporalio/testing/_workflow.py
Comment thread temporalio/testing/_workflow.py Outdated
Workflows started via :py:attr:`client` during the block do not
receive a ``time_skipping_config`` on their start request. Existing
workflows and V1 auto-behavior are unaffected. No-op on non-V2
environments.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to say the same thing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe redundant, but I want to keep it since it's specific about exactly what is being suspended

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just don't understand it. What's the difference between unaffected and noop?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the V1 mention is redundant. Changed to "Existing workflows are unaffected. No-op on non-V2 environments." 'Unaffected' means it doesn't affect already-started workflows, vs "no-op" meaning on non-v2 it does nothing at all.


async def get_current_time(
self,
handle: temporalio.client.WorkflowHandle[Any, Any] | None = None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this being optional but required at runtime for v2. Is there anything we can do, like getting the handle from the context by default or something like that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best we can do here is to use the sole handle, if there is only one, but I don't really like the idea that if you add another workflow (or more likely copy a single-workflow test and add another workflow, or SAA, or schedule) it will surprisingly fail. I want to emphasize that V2 has a different clock for every workflow (or SAA or schedule) and you have to be specific about it.

Making it required here will force the user to fix it during migration and mostly confine the learning process to that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe when we deprecate/remove v1, we can make the handle required on the type.

Comment thread temporalio/testing/_timeskipping.py
Comment thread temporalio/testing/_workflow.py
@GregoryTravis
GregoryTravis enabled auto-merge (squash) October 15, 2026 16:06
@GregoryTravis
GregoryTravis merged commit 875dc24 into main Oct 15, 2026
47 of 49 checks passed
@GregoryTravis
GregoryTravis deleted the gmt/sdk-5479-ts-python branch October 15, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants