@@ -50,48 +50,17 @@ function Push-ExecScheduledCommand {
5050 # Task should be 'Pending' (queued by orchestrator) or 'Running' (retry/recovery)
5151 # We accept both to handle edge cases
5252
53- # Check for rerun protection - prevent duplicate executions within the recurrence interval
54- # Do this BEFORE updating state to 'Running' to avoid getting stuck
55- if ($task.Recurrence -and $task.Recurrence -ne ' 0' -and ! $IsMultiTenantExecution ) {
56- # Calculate interval in seconds from recurrence string
57- $IntervalSeconds = switch - Regex ($task.Recurrence ) {
58- ' ^(\d+)$' { [int64 ]$matches [1 ] * 86400 } # Plain number = days
59- ' (\d+)m$' { [int64 ]$matches [1 ] * 60 }
60- ' (\d+)h$' { [int64 ]$matches [1 ] * 3600 }
61- ' (\d+)d$' { [int64 ]$matches [1 ] * 86400 }
62- default { 0 }
63- }
64-
65- if ($IntervalSeconds -gt 0 ) {
66- # Round down to nearest 15-minute interval (900 seconds) since that's when orchestrator runs
67- # This prevents rerun blocking issues due to slight timing variations
68- $FifteenMinutes = 900
69- $AdjustedInterval = [Math ]::Floor($IntervalSeconds / $FifteenMinutes ) * $FifteenMinutes
70-
71- # Ensure we have at least one 15-minute interval
72- if ($AdjustedInterval -lt $FifteenMinutes ) {
73- $AdjustedInterval = $FifteenMinutes
74- }
75- # Use task RowKey as API identifier for rerun cache
76- $RerunParams = @ {
77- TenantFilter = $Tenant
78- Type = ' ScheduledTask'
79- API = $task.RowKey
80- Interval = $AdjustedInterval
81- BaseTime = [int64 ]$task.ScheduledTime
82- Headers = $Headers
83- }
84-
85- $IsRerun = Test-CIPPRerun @RerunParams
86- if ($IsRerun ) {
87- Write-Information " Scheduled task $ ( $task.Name ) for tenant $Tenant was recently executed. Skipping to prevent duplicate execution."
88- Remove-Variable - Name ScheduledTaskId - Scope Script - ErrorAction SilentlyContinue
89- return
90- }
91- }
92- }
93-
94- # Also check for one-time task rerun protection based on ExecutedTime
53+ # NOTE: Recurring scheduled tasks intentionally have NO rerun-cache protection here.
54+ # Duplicate execution is already prevented by the orchestrator's own state machine:
55+ # - the ETag claim flips the task to 'Pending' atomically (no concurrent dispatch),
56+ # - 'Pending'/'Running' tasks are not re-picked until they go stale (1h/4h),
57+ # - ScheduledTime is advanced on completion so the task isn't eligible again until due.
58+ # A separate rerun cache (Test-CIPPRerun) was a second, independent clock that drifted out
59+ # of sync with ScheduledTime whenever a run didn't finish advancing the schedule, which both
60+ # deadlocked tasks and blocked the orchestrator's stuck-task recovery. ScheduledTime is the
61+ # single source of truth.
62+
63+ # One-time task rerun protection based on ExecutedTime (the task's own field, not a cache)
9564 if ((! $task.Recurrence -or $task.Recurrence -eq ' 0' ) -and $task.ExecutedTime -and ! $IsMultiTenantExecution ) {
9665 $currentUnixTime = [int64 ](([datetime ]::UtcNow) - (Get-Date ' 1/1/1970' )).TotalSeconds
9766 $timeSinceExecution = $currentUnixTime - [int64 ]$task.ExecutedTime
0 commit comments