Update sent to an uncached Worker after a command-less first Workflow Task runs before the run method, then fails replay
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 18/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- python
- Ambito
- backend, distributed-systems
Direzione di ricerca
Start with _WorkflowInstanceImpl.activate in temporalio/worker/_workflow_instance.py (around lines 484-500 in 1.34.0), which applies update and signal jobs before initialize_workflow, and compare it with the replay path. Run the repro script with max_cached_workflows=0 and then with a large cache to confirm the ordering difference. Done means the run method always executes before the update handler on an uncached worker, and the repro completes without a nondeterminism error.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
An update that reaches a Worker without the Workflow in its cache, right after a first Workflow Task that issued no commands, runs its handler before the run method, both live. A later replay runs the run method first. So values from workflow.uuid4() swap between the two, and every later Workflow Task fails with a nondeterminism error.
temporalio 1.34.0, Python 3.13.16, Temporal CLI 1.10.0 (Server 1.32.2), macOS.
import asyncio
import uuid
from datetime import timedelta
from temporalio import activity, workflow
from temporalio.api.enums.v1 import EventType
from temporalio.client import WorkflowUpdateStage
from temporalio.testing import WorkflowEnvironment
from temporalio.worker import Worker
@activity.defn
async def noop() -> None:
pass
@workflow.defn
class UpdateWorkflow:
def __init__(self) -> None:
self.done = False
@workflow.run
async def run(self) -> None:
mode = "replay" if workflow.unsafe.is_replaying() else "live"
print(f"run method ({mode}):", workflow.uuid4(), flush=True)
await workflow.wait_condition(lambda: self.done)
@workflow.update
async def update(self) -> None:
mode = "replay" if workflow.unsafe.is_replaying() else "live"
activity_id = f"act-{workflow.uuid4()}"
print(f"update ({mode}):", activity_id, flush=True)
await workflow.execute_activity(
noop, activity_id=activity_id, start_to_close_timeout=timedelta(seconds=10)
)
self.done = True
async def main() -> None:
async with await WorkflowEnvironment.start_local(dev_server_log_level="error") as env:
async with Worker(
env.client,
task_queue="q",
workflows=[UpdateWorkflow],
activities=[noop],
max_cached_workflows=0,
):
handle = await env.client.start_workflow(
UpdateWorkflow.run, id=str(uuid.uuid4()), task_queue="q"
)
# Wait until the first Workflow Task, which has no commands, completes.
while not any(
e.event_type == EventType.EVENT_TYPE_WORKFLOW_TASK_COMPLETED
for e in (await handle.fetch_history()).events
):
await asyncio.sleep(0.1)
await handle.start_update(
UpdateWorkflow.update, wait_for_stage=WorkflowUpdateStage.ACCEPTED
)
try:
await asyncio.wait_for(handle.result(), 10)
print("completed")
except asyncio.TimeoutError:
for e in (await handle.fetch_history()).events:
if e.event_type == EventType.EVENT_TYPE_WORKFLOW_TASK_FAILED:
print("task failed:", e.workflow_task_failed_event_attributes.failure.message)
break
await handle.terminate()
if __name__ == "__main__":
asyncio.run(main())
Output of python repro.py 2>/dev/null | grep -E '^(run method|update|task failed|completed)':
run method (live): d2101c6b-5154-42d0-9d93-90790dce80d8
update (live): act-d2101c6b-5154-42d0-9d93-90790dce80d8
run method (live): fe143ce3-789d-406b-97bb-ee4e6ddb64ab
run method (replay): d2101c6b-5154-42d0-9d93-90790dce80d8
update (replay): act-fe143ce3-789d-406b-97bb-ee4e6ddb64ab
run method (replay): d2101c6b-5154-42d0-9d93-90790dce80d8
update (replay): act-fe143ce3-789d-406b-97bb-ee4e6ddb64ab
task failed: [TMPRL1100] Nondeterminism error: Activity id of scheduled event 'act-d2101c6b-5154-42d0-9d93-90790dce80d8' does not match activity id of activity command 'act-fe143ce3-789d-406b-97bb-ee4e6ddb64ab'
- Line 1: the first Workflow Task runs the run method, which draws
d2101c6b-…and waits. It issues no commands. - Lines 2 and 3: the update's task, on a cache of 0, runs the update handler first, live, so it draws
d2101c6b-…, then runs the run method again, also live. - From line 4: each later task replays the run method first, so the handler draws
fe143ce3-…and its Activity ID no longer matches history.
With max_cached_workflows=1000, the same script prints:
run method (live): 12225546-2e7a-4fbc-8a12-dea82961db18
update (live): act-70f81baf-de7d-47d4-ba6c-8ffa093f1a15
completed
_WorkflowInstanceImpl.activate (temporalio/worker/_workflow_instance.py, lines 484-500 in 1.34.0) applies signal and update jobs before initialize_workflow. That suits signal-with-start, but here the run method already ran in an earlier task. It may be that the first task should be replayed on its own instead.
Found while reviewing temporalio/ai-integrations#133, where the LangSmith plugin's run IDs depend on the same order.
- Lingua principale
- Python
- Stelle
- 1.2k
- Fork
- 285
- Merge medio
- 1g 23h
- PR unite (30g)
- 89
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di temporalio/sdk-python
-
[Bug] Sandboxed datetime hints can fail decoding and drop signalsForse già presa @VegetarianOrc l’ha presa oggi. Aperta
temporalio/sdk-python#2072 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
[Bug] TaskGroup cancellation of mixed Temporal operations can break replayForse già presa @VegetarianOrc l’ha presa oggi. Aperta
temporalio/sdk-python#2069 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
sdk-python
Difficoltà 4/5 3-5 giorni Idoneità per principianti 22/100
temporalio/sdk-python#2067 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
[Bug] lambda_worker otel.apply_defaults leaks a span exporter thread and gRPC channel on every warm invocationForse già presa @avanish-garg l’ha presa 5 giorni fa. Apertabug sdk-python
Difficoltà 4/5 1-2 giorni Idoneità per principianti 30/100
temporalio/sdk-python#2040 ·
I maintainer di solito rispondono entro 1 giorno
-
Cloud CI Skips Nexus TestsForse di nuovo libera @tconley1428 l’ha presa 84 giorni fa e non c’è nessuna pull request aperta. Aperta
temporalio/sdk-python#1704 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di temporalio/sdk-python
Issue simili
-
bug ready-for-dev
Difficoltà 1/5 1-3 ore Idoneità per principianti 92/100
OpenHands/extensions#897 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
tensorflow/datasets#11253 ·
I maintainer di solito rispondono entro 1 giorno
-
fix(core): a config section with all keys commented out crashes startupForse già presa @Aryanbhargava18 l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
kubeflow/mcp-server#512 ·
I maintainer di solito rispondono entro 1 giorno
-
Stop trusting the compromised eMudhra CA certificatesForse già presa @anth-volk l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
PolicyEngine/uk-triple-lock#54 ·
I maintainer di solito rispondono entro 1 giorno
-
DBFS append mode silently overwrites existing filesForse già presa @IlyaKhalafi l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
fsspec/filesystem_spec#2429 ·
I maintainer di solito rispondono entro 1 giorno