refactor(scheduler): extract cron scheduling logic to shared helper

Extract the shouldRunNow() method from ScheduledJobManager and ServerManagerJob into
a reusable shouldRunCronNow() helper function. This centralizes cron scheduling logic
and enables consistent deduplication behavior across all scheduled job types.

- Create shouldRunCronNow() helper in bootstrap/helpers/shared.php with timezone
  and dedup support
- Refactor ScheduledJobManager and ServerManagerJob to use the shared helper
- Add ScheduledJobDiagnostics command for inspecting cache state and scheduling
  decisions across all scheduled jobs
- Simplify shouldRunNow tests to directly test the helper function
- Add DockerCleanupJob test for error handling and execution tracking
- Increase scheduled log retention from 1 to 7 days
This commit is contained in:
Andras Bacsai committed 2026-03-23 10:37:49 +01:00
1 parent 89f2b83104
commit f8f27fff13
8 files changed
+446 -317

No files matched your search

+30
View File
@@ -466,6 +466,36 @@ function validate_cron_expression($expression_to_validate): bool
return $isValid;
}
/**
* Determine if a cron schedule should run now, with deduplication.
*
* Uses getPreviousRunDate() + last-dispatch tracking to be resilient to queue delays.
* Even if the job runs minutes late, it still catches the missed cron window.
* Without a dedupKey, falls back to a simple isDue() check.
*/
function shouldRunCronNow(string $frequency, string $timezone, ?string $dedupKey = null, ?\Illuminate\Support\Carbon $executionTime = null): bool
{
$cron = new \Cron\CronExpression($frequency);
$executionTime = ($executionTime ?? \Illuminate\Support\Carbon::now())->copy()->setTimezone($timezone);
if ($dedupKey === null) {
return $cron->isDue($executionTime);
}
$previousDue = \Illuminate\Support\Carbon::instance($cron->getPreviousRunDate($executionTime, allowCurrentDate: true));
$lastDispatched = Cache::get($dedupKey);
$shouldFire = $lastDispatched === null
? $cron->isDue($executionTime)
: $previousDue->gt(\Illuminate\Support\Carbon::parse($lastDispatched));
// Always write: seeds on first miss, refreshes on dispatch.
// 30-day static TTL covers all intervals; orphan keys self-clean.
Cache::put($dedupKey, ($shouldFire ? $executionTime : $previousDue)->toIso8601String(), 2592000);
return $shouldFire;
}
function validate_timezone(string $timezone): bool
{
return in_array($timezone, timezone_identifiers_list());