schedulable_ids(), $trigger ); } /** * The scheduled callback: run every schedulable task. */ public static function run_scheduled(): void { self::run( TaskRegistry::get_instance()->schedulable_ids(), Context::TRIGGER_SCHEDULE ); } /** * Run the given tasks under a claim, journalling the outcome. * * @param string[] $task_ids * @param string $trigger * @param bool $dry_run Caller's request; an unactivated site forces true. * @return array{results: array, dry_run: bool, entry: ?array, skipped: bool} */ public static function run( array $task_ids, string $trigger, bool $dry_run = false ): array { if ( Settings::is_disabled() ) { return [ 'results' => [], 'dry_run' => true, 'entry' => null, 'skipped' => true ]; } if ( ! self::claim( $trigger ) ) { // Another sweep holds an unexpired claim. Skip rather than queue — // the next scheduled fire will pick the work up. return [ 'results' => [], 'dry_run' => $dry_run, 'entry' => null, 'skipped' => true ]; } // The gate: nothing is removed on a site nobody has activated, whatever // the caller asked for. $dry_run = $dry_run || Activation::forces_dry_run(); $context = Context::create( $trigger, RetentionPolicy::from_settings(), $dry_run, self::RUN_BUDGET ); try { $results = TaskRegistry::get_instance()->run( $task_ids, $context ); $entry = RunJournal::record( $trigger, $results, $dry_run, $context->elapsed_ms() ); } finally { self::release(); } return [ 'results' => $results, 'dry_run' => $dry_run, 'entry' => $entry, 'skipped' => false ]; } /** * Take the run claim, unless a live one is held. */ public static function claim( string $trigger ): bool { $claim = get_option( self::CLAIM_OPTION, null ); if ( is_array( $claim ) && isset( $claim['expires_at'] ) && (int) $claim['expires_at'] > time() ) { return false; } if ( is_array( $claim ) ) { // A claim that outlived its TTL means the run holding it died — // a fatal, a host restart, an execution timeout. Log it, because a // crashed sweep is worth knowing about even though it self-heals. Helper::log( 'Reclaiming an expired cleanup claim from a run that did not finish.', 'cleanup' ); } update_option( self::CLAIM_OPTION, [ 'started_at' => time(), 'expires_at' => time() + self::CLAIM_TTL, 'trigger' => $trigger, ], false ); return true; } public static function release(): void { delete_option( self::CLAIM_OPTION ); } public static function is_running(): bool { $claim = get_option( self::CLAIM_OPTION, null ); return is_array( $claim ) && isset( $claim['expires_at'] ) && (int) $claim['expires_at'] > time(); } }