[Bug] async def update validator is accepted but never runs, so every update is accepted
メンテナーはふだん 1 日以内に返信
関連するプルリクエストがすでにマージされています。
- #2005 @avanish-garg による — マージ済み
評価
調査の方向性
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時間
- マージ済み PR(30日)
- 68
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- 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 日前に担当しましたが、オープン中のプルリクエストはありません。 オープン
temporalio/sdk-python#1704 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
temporalio/sdk-python の issue をすべて見る
似ている issue
-
難易度 1/5 1〜3時間 初心者へのやさしさ 85/100
WLAN-Pi/wlanpi-profiler#341 ·
メンテナーはふだん 1 日以内に返信
-
Memory leak: the shared `validation_aborted_error` keeps every traceback it is raised through対応中かも @Fire162 が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
graphql-python/graphql-core#287 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 67/100
roman-corgi/ctc-dip#160 ·
メンテナーはふだん 1 日以内に返信
-
type-bug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
render_findings.py reads and writes in the host's default encoding, so findings.html breaks on a cp1252 Windows host対応中かも @Aaqibhafeezkhan が今日担当しました。 オープンbug good first issue mentored python size: XS up-for-grabs
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
dheerajjha/mcp-migrate#366 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信