diff --git a/changelog/fixed/relay-racing-raccoon.md b/changelog/fixed/relay-racing-raccoon.md new file mode 100644 index 000000000..d2d84df6b --- /dev/null +++ b/changelog/fixed/relay-racing-raccoon.md @@ -0,0 +1 @@ +Setting `Worker.client` no longer raises "New client is not on the same runtime as the existing client" when both clients connected without a `runtime`, since both use the default runtime. The check now compares the runtime each client actually connected with. diff --git a/temporalio/service.py b/temporalio/service.py index 926dfbd46..5c390f92a 100644 --- a/temporalio/service.py +++ b/temporalio/service.py @@ -418,6 +418,8 @@ def __init__(self, config: ConnectConfig) -> None: self._bridge_config = config._to_bridge_config() self._bridge_client: temporalio.bridge.client.Client | None = None self._bridge_client_connect_lock = asyncio.Lock() + # Runtime we connected with, so the default one if none was set + self._runtime: temporalio.runtime.Runtime | None = None async def _connected_client(self) -> temporalio.bridge.client.Client: # Fast path avoids touching the lock once connected. This keeps the @@ -433,6 +435,7 @@ async def _connected_client(self) -> temporalio.bridge.client.Client: runtime._core_runtime, self._bridge_config, ) + self._runtime = runtime return self._bridge_client @property diff --git a/temporalio/worker/_worker.py b/temporalio/worker/_worker.py index 39faf615a..3c6effd80 100644 --- a/temporalio/worker/_worker.py +++ b/temporalio/worker/_worker.py @@ -491,7 +491,9 @@ def _init_from_config(self, client: temporalio.client.Client, config: WorkerConf self._activity_worker: _ActivityWorker | None = None self._runtime = ( - bridge_client.config.runtime or temporalio.runtime.Runtime.default() + bridge_client._runtime + or bridge_client.config.runtime + or temporalio.runtime.Runtime.default() ) activities = config.get("activities") if activities: @@ -743,7 +745,7 @@ def client(self, value: temporalio.client.Client) -> None: must be using the same runtime as the current client. """ bridge_client = _extract_bridge_client_for_worker(value) - if self._runtime is not bridge_client.config.runtime: + if self._runtime is not bridge_client._runtime: raise ValueError( "New client is not on the same runtime as the existing client" ) diff --git a/tests/worker/test_workflow.py b/tests/worker/test_workflow.py index 80ac3d27b..01a2fcea8 100644 --- a/tests/worker/test_workflow.py +++ b/tests/worker/test_workflow.py @@ -6669,6 +6669,33 @@ async def test_workflow_replace_worker_client_diff_runtimes_fail( worker.client = other_client +async def test_workflow_replace_worker_client_implicit_default_runtime( + env: WorkflowEnvironment, +): + # Neither client sets a runtime, so both are on the default one + worker_client = await env.connect_client(runtime=None) + new_client = await env.connect_client(runtime=None) + async with new_worker(worker_client, HelloWorkflow) as worker: + worker.client = new_client + assert worker.client is new_client + + +async def test_workflow_replace_worker_client_implicit_default_runtime_mismatch_fail( + env: WorkflowEnvironment, +): + # No runtime means the default one, which isn't the worker's + worker_client = await env.connect_client( + runtime=Runtime(telemetry=TelemetryConfig()) + ) + default_runtime_client = await env.connect_client(runtime=None) + async with new_worker(worker_client, HelloWorkflow) as worker: + with pytest.raises( + ValueError, + match="New client is not on the same runtime as the existing client", + ): + worker.client = default_runtime_client + + @activity.defn(dynamic=True) async def return_name_activity(_args: Sequence[RawValue]) -> str: return activity.info().activity_type