fix: publish schedules after creation watermark
This commit is contained in:
+13
-4
@@ -21,7 +21,7 @@ from wf_scheduling.history import FileScheduleHistoryRecorder, HistoryEntry
|
|||||||
from wf_scheduling.models import OccurrenceRecord, Schedule
|
from wf_scheduling.models import OccurrenceRecord, Schedule
|
||||||
from wf_scheduling.occurrences import occurrence_id
|
from wf_scheduling.occurrences import occurrence_id
|
||||||
from wf_scheduling.prepare import PreparationRejected, SchedulePreparer
|
from wf_scheduling.prepare import PreparationRejected, SchedulePreparer
|
||||||
from wf_scheduling.store import StaleScheduleRevisionError
|
from wf_scheduling.store import ScheduleExistsError, StaleScheduleRevisionError
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
JsonProjector,
|
JsonProjector,
|
||||||
@@ -207,11 +207,20 @@ class WorkflowScheduleApi:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self._validate_definition(schedule)
|
self._validate_definition(schedule)
|
||||||
stored = store.create_schedule(schedule)
|
|
||||||
# Creation never backfills time before the revision: the consumed
|
# Creation never backfills time before the revision: the consumed
|
||||||
# watermark starts at creation so catch-up only ever covers
|
# watermark starts at creation so catch-up only ever covers downtime
|
||||||
# downtime after this revision, never pre-creation instants.
|
# after this revision, never pre-creation instants. Check the id
|
||||||
|
# before this write so a duplicate create cannot overwrite the
|
||||||
|
# existing schedule's watermark; publishing the schedule last keeps
|
||||||
|
# a failed watermark write invisible to the poller.
|
||||||
|
try:
|
||||||
|
store.get_schedule(schedule_id)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ScheduleExistsError(f"schedule id already exists: {schedule_id!r}")
|
||||||
store.save_consumed(schedule_id, now)
|
store.save_consumed(schedule_id, now)
|
||||||
|
stored = store.create_schedule(schedule)
|
||||||
return _PROJECT_SCHEDULE(stored.model_dump(mode="json"))
|
return _PROJECT_SCHEDULE(stored.model_dump(mode="json"))
|
||||||
|
|
||||||
async def get_schedule(self, *, schedule_id: str) -> ScheduleResult:
|
async def get_schedule(self, *, schedule_id: str) -> ScheduleResult:
|
||||||
|
|||||||
@@ -528,6 +528,26 @@ async def test_create_initializes_consumed_no_backfill(tmp_path: Path) -> None:
|
|||||||
assert consumed is not None and consumed >= created_at
|
assert consumed is not None and consumed >= created_at
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_watermark_failure_does_not_publish_schedule(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""A failed creation watermark cannot expose a backfillable schedule."""
|
||||||
|
api, sched_store, _ = _fault_harness(tmp_path / "torn_create")
|
||||||
|
sched_store.armed = True
|
||||||
|
|
||||||
|
with pytest.raises(OSError, match="injected consumed failure"):
|
||||||
|
await api.create_schedule(
|
||||||
|
schedule_id="s",
|
||||||
|
deployment_id="dep.personal",
|
||||||
|
trigger=_cron(),
|
||||||
|
misfire="latest",
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
sched_store.get_schedule("s")
|
||||||
|
assert sched_store.get_consumed("s") is None
|
||||||
|
|
||||||
|
|
||||||
async def test_update_watermark_failure_leaves_revision(tmp_path: Path) -> None:
|
async def test_update_watermark_failure_leaves_revision(tmp_path: Path) -> None:
|
||||||
api, sched_store, _ = _fault_harness(tmp_path / "torn_update")
|
api, sched_store, _ = _fault_harness(tmp_path / "torn_update")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user