engine = $engine ?? new ExecutionEngine(); $this->event_queue = $event_queue ?? new EventQueueRepository(); $this->workflow_queue = $workflow_queue ?? $this->engine->getQueue(); $this->worker = $worker ?? new EventWorker( $this->event_queue, $this->engine ); } /** * Register cron, Action Scheduler, and request-time listeners. * * @return void */ public function register(): void { add_filter( 'cron_schedules', array( $this, 'addCronSchedule' ) ); add_action( self::CRON_HOOK, array( $this, 'processQueues' ) ); add_action( self::IMMEDIATE_HOOK, array( $this, 'processQueues' ) ); add_action( 'init', array( $this, 'maybeSpawn' ), 20 ); add_action( 'shutdown', array( $this, 'maybeSpawn' ), 1000 ); wp_clear_scheduled_hook( AsyncBootstrap::CRON_HOOK ); if ( ! wp_next_scheduled( self::CRON_HOOK ) ) { wp_schedule_event( time(), self::CRON_INTERVAL, self::CRON_HOOK ); } } /** * Add custom cron interval. * * @param array> $schedules Cron schedules. * @return array> */ public function addCronSchedule( array $schedules ): array { if ( ! isset( $schedules[ self::CRON_INTERVAL ] ) ) { $schedules[ self::CRON_INTERVAL ] = array( 'interval' => 60, 'display' => __( 'Every Minute', 'afroit-messaging-workflows' ), ); } return $schedules; } /** * Drain queued events and resume due waiting executions. * * @return void */ public function processQueues(): void { if ( get_transient( self::LOCK_KEY ) ) { return; } set_transient( self::LOCK_KEY, 1, 30 ); try { $events = $this->worker->process( 20 ); $resumed = $this->engine->resume_due_executions( 20 ); if ( $events > 0 || count( $resumed ) > 0 ) { Log::info( 'ExecutionBootstrap: processed queues', array( 'events' => $events, 'resumed' => count( $resumed ), ) ); } } catch ( \Throwable $e ) { Log::error( 'ExecutionBootstrap: queue processing failed', array( 'error' => $e->getMessage(), ) ); } finally { delete_transient( self::LOCK_KEY ); } } /** * If work is waiting, kick a background run without blocking the current request. * * @return void */ public function maybeSpawn(): void { if ( $this->is_processing_context() ) { return; } if ( ! $this->has_due_work() ) { return; } self::request_tick(); } /** * Schedule an immediate background tick (event enqueue, cron, Action Scheduler). * * @return void */ public static function request_tick(): void { if ( ! wp_next_scheduled( self::IMMEDIATE_HOOK ) ) { wp_schedule_single_event( time(), self::IMMEDIATE_HOOK ); } if ( function_exists( 'as_enqueue_async_action' ) ) { $already = function_exists( 'as_has_scheduled_action' ) && as_has_scheduled_action( self::CRON_HOOK, array(), self::AS_GROUP ); if ( ! $already ) { as_enqueue_async_action( self::CRON_HOOK, array(), self::AS_GROUP ); } } if ( function_exists( 'spawn_cron' ) ) { spawn_cron(); } } /** * @return bool */ private function is_processing_context(): bool { if ( defined( 'WP_INSTALLING' ) && WP_INSTALLING ) { return true; } if ( defined( 'DOING_CRON' ) && DOING_CRON ) { return true; } if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return true; } return false; } /** * @return bool */ private function has_due_work(): bool { if ( $this->event_queue->hasPending() ) { return true; } return $this->workflow_queue->hasDueLocalExecutions(); } }