1919from __future__ import annotations
2020
2121import asyncio
22+ import math
2223import os
2324import time
2425import uuid
@@ -58,6 +59,31 @@ def _default_sdk_version() -> str:
5859
5960
6061DEFAULT_SDK_VERSION = _default_sdk_version ()
62+ _WORKER_POLL_MAX_SERVER_TIMEOUT_SECONDS = 60
63+ _WORKER_POLL_HTTP_TIMEOUT_GRACE_SECONDS = 5.0
64+
65+
66+ def _worker_poll_timeout_seconds (timeout : float | None ) -> int | None :
67+ if timeout is None :
68+ return None
69+
70+ value = float (timeout )
71+ if not math .isfinite (value ) or value < 0 :
72+ raise ValueError ("timeout must be a non-negative finite number" )
73+
74+ seconds = int (math .ceil (value )) if value >= 1 else 0
75+ return min (seconds , _WORKER_POLL_MAX_SERVER_TIMEOUT_SECONDS )
76+
77+
78+ def _worker_poll_http_timeout (timeout : float | None ) -> float | None :
79+ timeout_seconds = _worker_poll_timeout_seconds (timeout )
80+ if timeout_seconds is None :
81+ return None
82+
83+ if timeout_seconds == 0 :
84+ return max (float (timeout ), 1.0 )
85+
86+ return max (float (timeout ), float (timeout_seconds ) + _WORKER_POLL_HTTP_TIMEOUT_GRACE_SECONDS )
6187
6288
6389def _protocol_version_from_env (name : str , default : str ) -> str :
@@ -3217,22 +3243,31 @@ async def poll_workflow_task_response(
32173243 Returns the full worker-protocol response envelope, including
32183244 ``poll_status`` when the server has no task to lease. Worker-plane
32193245 endpoint — most applications use :class:`~durable_workflow.Worker`
3220- rather than calling this directly.
3246+ rather than calling this directly. ``timeout`` controls the server
3247+ long-poll window; the HTTP request uses a small grace margin.
32213248 """
32223249 body : dict [str , Any ] = {
32233250 "worker_id" : worker_id ,
32243251 "task_queue" : task_queue ,
32253252 "poll_request_id" : poll_request_id or f"wf-poll-{ uuid .uuid4 ().hex } " ,
32263253 }
3254+ timeout_seconds = _worker_poll_timeout_seconds (timeout )
3255+ if timeout_seconds is not None :
3256+ body ["timeout_seconds" ] = timeout_seconds
32273257 if build_id :
32283258 body ["build_id" ] = build_id
32293259 if history_page_size is not None :
32303260 body ["history_page_size" ] = history_page_size
3261+ http_timeout = _worker_poll_http_timeout (timeout )
32313262
32323263 for _ in range (2 ):
32333264 try :
32343265 data = await self ._request (
3235- "POST" , "/worker/workflow-tasks/poll" , worker = True , json = body , timeout = timeout
3266+ "POST" ,
3267+ "/worker/workflow-tasks/poll" ,
3268+ worker = True ,
3269+ json = body ,
3270+ timeout = http_timeout ,
32363271 )
32373272 except httpx .TimeoutException :
32383273 continue
@@ -3255,7 +3290,8 @@ async def poll_workflow_task(
32553290
32563291 Returns the task payload, or ``None`` on poll timeout. Worker-plane
32573292 endpoint — most applications use :class:`~durable_workflow.Worker`
3258- rather than calling this directly.
3293+ rather than calling this directly. ``timeout`` controls the server
3294+ long-poll window; the HTTP request uses a small grace margin.
32593295 """
32603296 data = await self .poll_workflow_task_response (
32613297 worker_id = worker_id ,
@@ -3363,19 +3399,29 @@ async def poll_query_task(
33633399
33643400 Query tasks are ephemeral worker-plane requests created when the server
33653401 must route a control-plane query to a non-PHP workflow runtime.
3402+ ``timeout`` controls the server long-poll window; the HTTP request
3403+ uses a small grace margin.
33663404 """
33673405 body : dict [str , Any ] = {
33683406 "worker_id" : worker_id ,
33693407 "task_queue" : task_queue ,
33703408 "poll_request_id" : poll_request_id or f"query-poll-{ uuid .uuid4 ().hex } " ,
33713409 }
3410+ timeout_seconds = _worker_poll_timeout_seconds (timeout )
3411+ if timeout_seconds is not None :
3412+ body ["timeout_seconds" ] = timeout_seconds
33723413 if build_id :
33733414 body ["build_id" ] = build_id
3415+ http_timeout = _worker_poll_http_timeout (timeout )
33743416
33753417 for _ in range (2 ):
33763418 try :
33773419 data = await self ._request (
3378- "POST" , "/worker/query-tasks/poll" , worker = True , json = body , timeout = timeout
3420+ "POST" ,
3421+ "/worker/query-tasks/poll" ,
3422+ worker = True ,
3423+ json = body ,
3424+ timeout = http_timeout ,
33793425 )
33803426 except httpx .TimeoutException :
33813427 continue
@@ -3457,19 +3503,29 @@ async def poll_activity_task(
34573503
34583504 Returns the task payload, or ``None`` on poll timeout. Worker-plane
34593505 endpoint — typically used by :class:`~durable_workflow.Worker`.
3506+ ``timeout`` controls the server long-poll window; the HTTP request
3507+ uses a small grace margin.
34603508 """
34613509 body : dict [str , Any ] = {
34623510 "worker_id" : worker_id ,
34633511 "task_queue" : task_queue ,
34643512 "poll_request_id" : poll_request_id or f"activity-poll-{ uuid .uuid4 ().hex } " ,
34653513 }
3514+ timeout_seconds = _worker_poll_timeout_seconds (timeout )
3515+ if timeout_seconds is not None :
3516+ body ["timeout_seconds" ] = timeout_seconds
34663517 if build_id :
34673518 body ["build_id" ] = build_id
3519+ http_timeout = _worker_poll_http_timeout (timeout )
34683520
34693521 for _ in range (2 ):
34703522 try :
34713523 data = await self ._request (
3472- "POST" , "/worker/activity-tasks/poll" , worker = True , json = body , timeout = timeout
3524+ "POST" ,
3525+ "/worker/activity-tasks/poll" ,
3526+ worker = True ,
3527+ json = body ,
3528+ timeout = http_timeout ,
34733529 )
34743530 except httpx .TimeoutException :
34753531 continue
0 commit comments