|null) $heartbeat */ public function __construct( private Settings $settings, private $flush, private $heartbeat ) {} /** * One pass of the daemon's own upload duties, called between stream opens. Each part runs only when its interval * has elapsed, so a stream that reopens every second does not flush every second. * * @return array{flushed:bool,heartbeat:bool,batches:int,error:string|null} */ public function tick( ?int $now = null ): array { $now = $now ?? time(); $out = array( 'flushed' => false, 'heartbeat' => false, 'batches' => 0, 'error' => null ); if ( $now - (int) $this->settings->get( self::KEY_FLUSH_AT, 0 ) >= self::FLUSH_INTERVAL_S ) { $res = ( $this->flush )(); $out['flushed'] = true; $out['batches'] = (int) ( $res['batches'] ?? 0 ); $out['error'] = isset( $res['error'] ) && is_string( $res['error'] ) ? $res['error'] : null; // Recorded whatever the outcome: a daemon that is trying and failing is still the one on duty, and the // Flusher's own backoff (shared with cron) is what holds it back. $this->settings->set( self::KEY_FLUSH_AT, $now ); } if ( $now - (int) $this->settings->get( self::KEY_HEARTBEAT_AT, 0 ) >= self::HEARTBEAT_INTERVAL_S ) { ( $this->heartbeat )(); $out['heartbeat'] = true; $this->settings->set( self::KEY_HEARTBEAT_AT, $now ); } return $out; } /** * Should WP-Cron do the flushing? Yes unless a daemon is demonstrably on duty right now. * * Two independent conditions, both required for cron to stand down: the site is still in `persistent_stream` * mode, and the daemon's record is fresher than `CRON_DEFER_S`. A mode change hands the work back immediately * rather than after the freshness expires, which is the "switch modes with a batch in flight" case. */ public static function cron_should_flush( Settings $settings, ?int $now = null ): bool { return self::cron_should( $settings, self::KEY_FLUSH_AT, $now ?? time() ); } /** Same rule for the heartbeat; the rest of the collect pass is local work and always runs. */ public static function cron_should_heartbeat( Settings $settings, ?int $now = null ): bool { return self::cron_should( $settings, self::KEY_HEARTBEAT_AT, $now ?? time() ); } private static function cron_should( Settings $settings, string $key, int $now ): bool { if ( $settings->connection_mode() !== Settings::MODE_PERSISTENT_STREAM ) { return true; } return $now - (int) $settings->get( $key, 0 ) >= self::CRON_DEFER_S; } /** The transport label a batch carries, from the site's own state. Used for the `via` field and the admin page. */ public static function active_transport( Settings $settings, ?int $now = null ): string { return self::cron_should_flush( $settings, $now ) ? 'cron' : 'stream'; } }