[Bug] async def update validator is accepted but never runs, so every update is accepted
維護者通常 1 天內回覆
評估
研究方向
從 temporalio/worker/_workflow_instance.py 中的 WorkflowInboundImpl.handle_update_validator 開始,接著沿著 _UpdateDefinition.set_validator 以及 decorator、set_update_handler 和 set_dynamic_update_handler 這些路徑追蹤 validator 的註冊。先閱讀相關的 update 和 query 驗證測試。完成標準是:這些路徑在註冊時都會拒絕非同步 validator,同時同步 validator 仍可正常運作。
由索引模型根據 Issue 內容生成。
描述
What are you really trying to do?
Reject an update with invalid arguments before it is written to history, using an update validator on a workflow whose update handler is async def.
Describe the bug
@my_update.validator accepts an async def function without any error, but the validator never executes, so the update is always accepted.
The inbound interceptor calls the validator without awaiting it (WorkflowInboundImpl.handle_update_validator in temporalio/worker/_workflow_instance.py, handler(*input.args)), unlike handle_signal, handle_query and handle_update_handler, which check inspect.iscoroutinefunction and await. For an async def validator the call only creates a coroutine object, which is dropped. The only symptom is a RuntimeWarning: coroutine ... was never awaited on the worker, so a validator that is meant to reject bad input silently lets everything through.
The README says "Validators cannot be async", and the other definition-time mistakes are all reported when the workflow is defined (@workflow.run on a non-async function raises ValueError, duplicate handlers raise ValueError, a validator/handler signature mismatch raises ValueError since #723, async queries emit a DeprecationWarning). The async validator is the one case with no check at all. pyright and mypy do flag it statically, but nothing does at runtime.
The same happens through the dynamic registration paths, workflow.set_update_handler(name, handler, validator=async_fn) and workflow.set_dynamic_update_handler(handler, validator=async_fn).
Expected: a ValueError when the validator is registered (decorator time, as for the signature mismatch). Actual: no error, and the update with n=-5 below is accepted.
Minimal Reproduction
import asyncio, uuid
from temporalio import workflow
from temporalio.client import WorkflowUpdateFailedError
from temporalio.testing import WorkflowEnvironment
from temporalio.worker import UnsandboxedWorkflowRunner, Worker
@workflow.defn
class W:
def __init__(self) -> None:
self.done = False
self.total = 0
@workflow.run
async def run(self) -> int:
await workflow.wait_condition(lambda: self.done)
return self.total
@workflow.update
async def add_sync(self, n: int) -> int:
self.total += n
return self.total
@add_sync.validator
def _v_sync(self, n: int) -> None:
if n < 0:
raise ValueError("negative")
@workflow.update
async def add_async(self, n: int) -> int:
self.total += n
return self.total
@add_async.validator
async def _v_async(self, n: int) -> None: # accepted by the decorator
if n < 0:
raise ValueError("negative")
@workflow.signal
def finish(self) -> None:
self.done = True
async def main():
async with await WorkflowEnvironment.start_local() as env:
async with Worker(
env.client,
task_queue="tq",
workflows=[W],
workflow_runner=UnsandboxedWorkflowRunner(),
):
h = await env.client.start_workflow(W.run, id=str(uuid.uuid4()), task_queue="tq")
try:
await h.execute_update(W.add_sync, -5)
print("sync validator : ACCEPTED -5 (unexpected)")
except WorkflowUpdateFailedError as e:
print("sync validator : rejected -5 ->", repr(e.cause)[:60])
print("async validator: returned", await h.execute_update(W.add_async, -5))
await h.signal(W.finish)
print("final total:", await h.result())
asyncio.run(main())
Output on current main (646d70f5):
.../temporalio/worker/_workflow_instance.py:3264: RuntimeWarning: coroutine '_bind_method.<locals>.with_object' was never awaited
handler(*input.args)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
sync validator : rejected -5 -> ApplicationError('ValueError: negative')
async validator: returned -5 (update with n=-5 ACCEPTED; total is now negative)
final total: -5
(UnsandboxedWorkflowRunner is only used to keep the script short.)
Environment/Versions
- OS and processor: M-series Mac (macOS 26.3.1, arm64)
- Temporal Version: SDK
mainat646d70f5(pure Python from the repo,temporalio1.34.0 wheel bridge), Python 3.12.14, dev server fromWorkflowEnvironment.start_local()(Temporal CLI 1.9.1, server 1.32.0) - Are you using Docker or Kubernetes or building Temporal from source? No
Additional context
Proposed fix, in the PR that will follow: raise ValueError("Update validator must not be an async function") in _UpdateDefinition.set_validator, which the decorator and both set_update_handler / set_dynamic_update_handler go through. It is a (very small) behavior change, since a workflow that has an async def validator today would now fail at class definition instead of silently accepting every update. A DeprecationWarning like the one for async queries would keep that workflow loading, but it would keep the validation switched off, so I went with raising. Happy to switch to a warning if you prefer.
- 主要語言
- Python
- 星號
- 1.2k
- 分支
- 269
- 平均合併
- 1 天 11 小時
- 30 天內合併 PR
- 64
環境準備
- 沒有 Dockerfile 或 Docker Compose 檔案
- 沒有 Pull Request 範本
- 閱讀貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
temporalio/sdk-python 的其他 Issue
-
bug
難度 2/5 1-3 小時 新手友好度 68/100
temporalio/sdk-python#496 ·
維護者通常 1 天內回覆
-
[Bug] Retried `ActivityHandle.terminate()` can fail after the activity is already terminated可能已有人在做 @maciejdudko 於 1 天前認領。 未關閉
temporalio/sdk-python#2008 · 已指派 1 人 ·
維護者通常 1 天內回覆
-
[Bug] Annotated constraints and validators on workflow/activity/handler parameters are ignored by the Pydantic data converter可能已有人在做 @tconley1428 於 1 天前認領。 未關閉bug
temporalio/sdk-python#2006 · 1 則留言 · 已指派 1 人 ·
維護者通常 1 天內回覆
-
Proposal: handle SIGTERM by default to initiate graceful worker shutdown可能已有人在做 @eamsden 於 15 天前認領。 未關閉
temporalio/sdk-python#1948 · 已指派 1 人 ·
維護者通常 1 天內回覆
-
Cloud CI Skips Nexus Tests可能重新可做 @tconley1428 於 73 天前認領,目前沒有進行中的 PR。 未關閉
temporalio/sdk-python#1704 · 已指派 1 人 ·
維護者通常 1 天內回覆
查看 temporalio/sdk-python 的全部 Issue
相似的 Issue
-
[Resource]: Lore未關閉resource-submission validation-passed
難度 1/5 1 小時以內 新手友好度 88/100
hesreallyhim/awesome-claude-code#3234 · 1 則留言 ·
維護者通常 1 天內回覆
-
難度 2/5 1-3 小時 新手友好度 78/100
openminion/openminion#376 ·
維護者通常 1 天內回覆
-
Type: ☹︎ Bug
難度 2/5 1-3 小時 新手友好度 78/100
posit-dev/great-tables#1028 ·
維護者通常 1 天內回覆
-
integration: apple_tv
難度 2/5 1-3 小時 新手友好度 78/100
home-assistant/core#188282 · 1 則留言 ·
維護者通常 1 天內回覆
-
A "since" range is worded as a career: "the past two seasons" says "regular season career (2025 on)"未關閉P4: low
難度 2/5 1-3 小時 新手友好度 65/100
jeffknupp/association#538 ·