|
| 1 | +import asyncio |
1 | 2 | import inspect |
2 | 3 | import os |
3 | 4 | import re |
| 5 | +import socket |
4 | 6 | from collections.abc import Callable, Mapping |
5 | 7 | from datetime import timedelta |
6 | 8 | from typing import Any |
@@ -331,3 +333,58 @@ async def test_service( |
331 | 333 | proto_service="Health", |
332 | 334 | target_service_name="health_service", |
333 | 335 | ) |
| 336 | + |
| 337 | + |
| 338 | +@pytest.mark.parametrize("timeout", [None, timedelta(0), timedelta(milliseconds=1250)]) |
| 339 | +def test_connect_config_connect_timeout(timeout: timedelta | None): |
| 340 | + config = temporalio.service.ConnectConfig( |
| 341 | + target_host="localhost:7233", connect_timeout=timeout |
| 342 | + ) |
| 343 | + assert config._to_bridge_config().connect_timeout_millis == ( |
| 344 | + None if timeout is None else int(timeout.total_seconds() * 1000) |
| 345 | + ) |
| 346 | + |
| 347 | + |
| 348 | +async def test_connect_timeout_bounds_tcp_connection(): |
| 349 | + listener = socket.socket() |
| 350 | + connections: list[socket.socket] = [] |
| 351 | + try: |
| 352 | + listener.bind(("127.0.0.1", 0)) |
| 353 | + listener.listen(1) |
| 354 | + address = listener.getsockname() |
| 355 | + loop = asyncio.get_running_loop() |
| 356 | + # Fill the accept queue without accepting connections, so subsequent TCP |
| 357 | + # attempts stall locally instead of depending on an unreachable remote IP. |
| 358 | + for _ in range(128): |
| 359 | + connection = socket.socket() |
| 360 | + connections.append(connection) |
| 361 | + connection.setblocking(False) |
| 362 | + try: |
| 363 | + await asyncio.wait_for(loop.sock_connect(connection, address), 0.1) |
| 364 | + except asyncio.TimeoutError: |
| 365 | + break |
| 366 | + else: |
| 367 | + pytest.fail("Could not fill the listener's accept queue") |
| 368 | + |
| 369 | + with pytest.raises(RuntimeError, match="tcp connect error.*TimedOut"): |
| 370 | + await asyncio.wait_for( |
| 371 | + Client.connect( |
| 372 | + f"127.0.0.1:{address[1]}", |
| 373 | + connect_timeout=timedelta(milliseconds=100), |
| 374 | + ), |
| 375 | + timeout=5, |
| 376 | + ) |
| 377 | + finally: |
| 378 | + for connection in connections: |
| 379 | + connection.close() |
| 380 | + listener.close() |
| 381 | + |
| 382 | + |
| 383 | +async def test_connect_timeout_allows_healthy_connection(client: Client): |
| 384 | + connected = await Client.connect( |
| 385 | + client.service_client.config.target_host, |
| 386 | + connect_timeout=timedelta(seconds=5), |
| 387 | + ) |
| 388 | + await connected.workflow_service.get_system_info( |
| 389 | + temporalio.api.workflowservice.v1.GetSystemInfoRequest() |
| 390 | + ) |
0 commit comments