Skip to content

Commit 62766cc

Browse files
Add safety check to prevent jittered times from going before start
When using large jitter values, the random offset could potentially push scheduled times before the start time. Added a safety check to clamp jittered times to never be earlier than the start time. Added test case to verify this edge case is handled correctly. Co-authored-by: Chris Guidry <[email protected]>
1 parent 9e42c4d commit 62766cc

File tree

2 files changed

+35
-1
lines changed

2 files changed

+35
-1
lines changed

src/docket/agenda.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,11 @@ async def scatter(
154154
-jitter.total_seconds(), jitter.total_seconds()
155155
)
156156
)
157-
jittered_times.append(schedule_time + offset)
157+
# Ensure the jittered time doesn't go before start
158+
jittered_time = schedule_time + offset
159+
if jittered_time < start:
160+
jittered_time = start
161+
jittered_times.append(jittered_time)
158162
schedule_times = jittered_times
159163

160164
# Build all Execution objects first, validating as we go

tests/test_agenda.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,36 @@ async def test_agenda_scatter_with_jitter(
124124
assert diff <= 30
125125

126126

127+
async def test_agenda_scatter_with_large_jitter(
128+
docket: Docket, agenda: Agenda, the_task: AsyncMock
129+
):
130+
"""Should ensure jittered times never go before start even with large jitter."""
131+
docket.register(the_task)
132+
133+
# Add tasks that will be scheduled close to start
134+
for i in range(3):
135+
agenda.add(the_task)(f"task{i}")
136+
137+
start_time = datetime.now(timezone.utc)
138+
139+
# Use a very large jitter (5 minutes) on a short window (1 minute)
140+
# This could potentially push times before start without our safety check
141+
executions = await agenda.scatter(
142+
docket,
143+
start=start_time,
144+
over=timedelta(minutes=1),
145+
jitter=timedelta(minutes=5)
146+
)
147+
148+
assert len(executions) == 3
149+
150+
# All scheduled times should be at or after start_time
151+
for execution in executions:
152+
assert execution.when >= start_time, (
153+
f"Task scheduled at {execution.when} is before start {start_time}"
154+
)
155+
156+
127157
async def test_agenda_scatter_single_task(
128158
docket: Docket, agenda: Agenda, the_task: AsyncMock
129159
):

0 commit comments

Comments
 (0)