Address comments

This commit is contained in:
Tao Chen
2026-06-11 15:27:36 -07:00
Unverified
parent ed27241543
commit b0d0224ed4
5 changed files with 110 additions and 6 deletions
@@ -403,8 +403,9 @@ class InProcRunnerContext:
def reset_for_new_run(self) -> None:
"""Reset the context for a new workflow run.
This clears messages, events, and resets streaming flag.
Runtime checkpoint storage is NOT cleared here as it's managed at the workflow level.
Clears messages, the pending event queue, the pending request_info
correlation map, and the streaming flag. Runtime checkpoint storage is
NOT cleared here as it's managed at the workflow level.
"""
self._messages.clear()
# Clear any pending events (best-effort) by recreating the queue
@@ -503,11 +503,18 @@ class Workflow(DictConvertible):
if checkpoint_storage is not None:
self._runner.context.set_runtime_checkpoint_storage(checkpoint_storage)
# Capture the runner's checkpoint id before attempting to save. The runner
# log-and-swallows storage save errors and only updates
# ``previous_checkpoint_id`` on success, so a failed save would otherwise
# leave the prior id in place and we'd return it as if a fresh checkpoint
# had been created.
previous_id_before = self._runner.previous_checkpoint_id
try:
await self._runner.create_checkpoint_if_enabled()
if self._runner.previous_checkpoint_id is None:
new_id = self._runner.previous_checkpoint_id
if new_id is None or new_id == previous_id_before:
raise WorkflowCheckpointException("Failed to create checkpoint.")
return self._runner.previous_checkpoint_id
return new_id
finally:
self._runner.context.clear_runtime_checkpoint_storage()
@@ -1461,5 +1461,36 @@ class TestWorkflowCreateCheckpoint:
assert second is not None
assert second.previous_checkpoint_id == first_id
async def test_raises_when_save_fails_after_prior_success(self, simple_executor: Executor) -> None:
"""A failed save after an earlier successful checkpoint must not return the stale id.
The runner log-and-swallows storage save errors and only updates
``previous_checkpoint_id`` on success. Without an explicit transition check,
``create_checkpoint`` would silently return the previously stored id as if a
new checkpoint had been created.
"""
from unittest.mock import AsyncMock
storage = InMemoryCheckpointStorage()
workflow = WorkflowBuilder(start_executor=simple_executor).add_edge(simple_executor, simple_executor).build()
# First call succeeds and seeds ``previous_checkpoint_id``.
first_id = await workflow.create_checkpoint(storage)
assert first_id
# Second call fails to save, so the runner leaves ``previous_checkpoint_id``
# pointing at ``first_id``. The method must detect that the id did not
# transition and raise instead of returning the stale value.
original_save = storage.save
storage.save = AsyncMock(side_effect=RuntimeError("boom")) # type: ignore[method-assign]
try:
with pytest.raises(WorkflowCheckpointException, match="Failed to create checkpoint"):
await workflow.create_checkpoint(storage)
finally:
storage.save = original_save # type: ignore[method-assign]
# The runner's bookkeeping is unchanged after the failed call.
assert workflow._runner.previous_checkpoint_id == first_id # type: ignore[attr-defined]
# endregion