← All changes
|
libraries/action-scheduler/classes/ActionScheduler_QueueRunner.php
+86
-27
2.7.9
→
3.1.1
View file →
| @@ -7,26 +7,48 @@ | ||
| 7 | 7 | const WP_CRON_HOOK = 'action_scheduler_run_queue'; |
| 8 | 8 | |
| 9 | 9 | const WP_CRON_SCHEDULE = 'every_minute'; |
| 10 | 10 | |
| 11 | - /** @var ActionScheduler_AsyncRequest_QueueRunner */ | |
| 11 | + /** | |
| 12 | + * ActionScheduler_AsyncRequest_QueueRunner instance. | |
| 13 | + * | |
| 14 | + * @var ActionScheduler_AsyncRequest_QueueRunner | |
| 15 | + */ | |
| 12 | 16 | protected $async_request; |
| 13 | 17 | |
| 14 | - /** @var ActionScheduler_QueueRunner */ | |
| 18 | + /** | |
| 19 | + * ActionScheduler_QueueRunner instance. | |
| 20 | + * | |
| 21 | + * @var ActionScheduler_QueueRunner | |
| 22 | + */ | |
| 15 | 23 | private static $runner = null; |
| 16 | 24 | |
| 17 | - /** @var int */ | |
| 25 | + /** | |
| 26 | + * Whether the cleaner instance is a non-default one. | |
| 27 | + * | |
| 28 | + * @var bool | |
| 29 | + */ | |
| 30 | + private $is_custom_cleaner; | |
| 31 | + | |
| 32 | + /** | |
| 33 | + * Number of processed actions. | |
| 34 | + * | |
| 35 | + * @var int | |
| 36 | + */ | |
| 18 | 37 | private $processed_actions_count = 0; |
| 19 | 38 | |
| 20 | 39 | /** |
| 40 | + * Get instance. | |
| 41 | + * | |
| 21 | 42 | * @return ActionScheduler_QueueRunner |
| 22 | 43 | * @codeCoverageIgnore |
| 23 | 44 | */ |
| 24 | 45 | public static function instance() { |
| 25 | - if ( empty(self::$runner) ) { | |
| 26 | - $class = apply_filters('action_scheduler_queue_runner_class', 'ActionScheduler_QueueRunner'); | |
| 46 | + if ( empty( self::$runner ) ) { | |
| 47 | + $class = apply_filters( 'action_scheduler_queue_runner_class', 'ActionScheduler_QueueRunner' ); | |
| 27 | 48 | self::$runner = new $class(); |
| 28 | 49 | } |
| 50 | + | |
| 29 | 51 | return self::$runner; |
| 30 | 52 | } |
| 31 | 53 | |
| 32 | 54 | /** |
| @@ -31,13 +53,14 @@ | ||
| 31 | 53 | |
| 32 | 54 | /** |
| 33 | 55 | * ActionScheduler_QueueRunner constructor. |
| 34 | 56 | * |
| 35 | - * @param ActionScheduler_Store $store | |
| 36 | - * @param ActionScheduler_FatalErrorMonitor $monitor | |
| 37 | - * @param ActionScheduler_QueueCleaner $cleaner | |
| 57 | + * @param ActionScheduler_Store|null $store Store object. | |
| 58 | + * @param ActionScheduler_FatalErrorMonitor|null $monitor Monitor object. | |
| 59 | + * @param ActionScheduler_QueueCleaner|null $cleaner Cleaner object. | |
| 60 | + * @param ActionScheduler_AsyncRequest_QueueRunner|null $async_request Async request runner object. | |
| 38 | 61 | */ |
| 39 | - public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null, ActionScheduler_AsyncRequest_QueueRunner $async_request = null ) { | |
| 62 | + public function __construct( ?ActionScheduler_Store $store = null, ?ActionScheduler_FatalErrorMonitor $monitor = null, ?ActionScheduler_QueueCleaner $cleaner = null, ?ActionScheduler_AsyncRequest_QueueRunner $async_request = null ) { | |
| 40 | 63 | parent::__construct( $store, $monitor, $cleaner ); |
| 41 | 64 | |
| 42 | 65 | if ( is_null( $async_request ) ) { |
| 43 | 66 | $async_request = new ActionScheduler_AsyncRequest_QueueRunner( $this->store ); |
| @@ -42,19 +65,22 @@ | ||
| 42 | 65 | if ( is_null( $async_request ) ) { |
| 43 | 66 | $async_request = new ActionScheduler_AsyncRequest_QueueRunner( $this->store ); |
| 44 | 67 | } |
| 45 | 68 | |
| 46 | - $this->async_request = $async_request; | |
| 69 | + $this->async_request = $async_request; | |
| 70 | + $this->is_custom_cleaner = get_class( $this->cleaner ) !== ActionScheduler_QueueCleaner::class; | |
| 47 | 71 | } |
| 48 | 72 | |
| 49 | 73 | /** |
| 74 | + * Initialize. | |
| 75 | + * | |
| 50 | 76 | * @codeCoverageIgnore |
| 51 | 77 | */ |
| 52 | 78 | public function init() { |
| 53 | 79 | |
| 54 | - add_filter( 'cron_schedules', array( self::instance(), 'add_wp_cron_schedule' ) ); | |
| 80 | + add_filter( 'cron_schedules', array( self::instance(), 'add_wp_cron_schedule' ) ); // phpcs:ignore WordPress.WP.CronInterval.CronSchedulesInterval | |
| 55 | 81 | |
| 56 | - // Check for and remove any WP Cron hook scheduled by Action Scheduler < 3.0.0, which didn't include the $context param | |
| 82 | + // Check for and remove any WP Cron hook scheduled by Action Scheduler < 3.0.0, which didn't include the $context param. | |
| 57 | 83 | $next_timestamp = wp_next_scheduled( self::WP_CRON_HOOK ); |
| 58 | 84 | if ( $next_timestamp ) { |
| 59 | 85 | wp_unschedule_event( $next_timestamp, self::WP_CRON_HOOK ); |
| 60 | 86 | } |
| @@ -67,8 +93,15 @@ | ||
| 67 | 93 | } |
| 68 | 94 | |
| 69 | 95 | add_action( self::WP_CRON_HOOK, array( self::instance(), 'run' ) ); |
| 70 | 96 | $this->hook_dispatch_async_request(); |
| 97 | + | |
| 98 | + // Backward compatibility: If the action cleaner is standard, cleaning will be performed as an action to improve throughput | |
| 99 | + // and enable daily runs. If not, cleaning will occur explicitly before processing actions to ensure backward compatibility. | |
| 100 | + // The cleaner was initially designed as a QueueRunner dependency, which is why the hooks are registered here. | |
| 101 | + if ( ! $this->is_custom_cleaner ) { | |
| 102 | + $this->cleaner->register_cleaner_hooks(); | |
| 103 | + } | |
| 71 | 104 | } |
| 72 | 105 | |
| 73 | 106 | /** |
| 74 | 107 | * Hook check for dispatching an async request. |
| @@ -102,11 +135,14 @@ | ||
| 102 | 135 | * If all of these conditions are met, then we request an async runner check whether it |
| 103 | 136 | * should dispatch a request to process pending actions. |
| 104 | 137 | */ |
| 105 | 138 | public function maybe_dispatch_async_request() { |
| 106 | - if ( is_admin() && ! ActionScheduler::lock()->is_locked( 'async-request-runner' ) ) { | |
| 107 | - // Only start an async queue at most once every 60 seconds | |
| 108 | - ActionScheduler::lock()->set( 'async-request-runner' ); | |
| 139 | + // Only start an async queue at most once every 60 seconds. | |
| 140 | + if ( | |
| 141 | + is_admin() | |
| 142 | + && ! ActionScheduler::lock()->is_locked( 'async-request-runner' ) | |
| 143 | + && ActionScheduler::lock()->set( 'async-request-runner' ) | |
| 144 | + ) { | |
| 109 | 145 | $this->async_request->maybe_dispatch(); |
| 110 | 146 | } |
| 111 | 147 | } |
| 112 | 148 | |
| @@ -116,11 +152,12 @@ | ||
| 116 | 152 | * The $context param of this method defaults to 'WP Cron', because prior to Action Scheduler 3.0.0 |
| 117 | 153 | * that was the only context in which this method was run, and the self::WP_CRON_HOOK hook had no context |
| 118 | 154 | * passed along with it. New code calling this method directly, or by triggering the self::WP_CRON_HOOK, |
| 119 | 155 | * should set a context as the first parameter. For an example of this, refer to the code seen in |
| 156 | + * | |
| 120 | 157 | * @see ActionScheduler_AsyncRequest_QueueRunner::handle() |
| 121 | 158 | * |
| 122 | - * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' | |
| 159 | + * @param string $context Optional identifier for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' | |
| 123 | 160 | * Generally, this should be capitalised and not localised as it's a proper noun. |
| 124 | 161 | * @return int The number of actions processed. |
| 125 | 162 | */ |
| 126 | 163 | public function run( $context = 'WP Cron' ) { |
| @@ -125,18 +162,32 @@ | ||
| 125 | 162 | */ |
| 126 | 163 | public function run( $context = 'WP Cron' ) { |
| 127 | 164 | ActionScheduler_Compatibility::raise_memory_limit(); |
| 128 | 165 | ActionScheduler_Compatibility::raise_time_limit( $this->get_time_limit() ); |
| 166 | + | |
| 129 | 167 | do_action( 'action_scheduler_before_process_queue' ); |
| 130 | - $this->run_cleanup(); | |
| 131 | 168 | |
| 169 | + $cleanup_time_limit = 10 * $this->get_time_limit(); | |
| 170 | + // Backward compatibility: If the action cleaner is standard, cleaning will be performed as an action to improve throughput | |
| 171 | + // and enable daily runs. If not, cleaning will occur explicitly before processing actions to ensure backward compatibility. | |
| 172 | + if ( $this->is_custom_cleaner ) { | |
| 173 | + // Execute complete cleanup cycle, as in this logical branch deletion IS NOT executed via a separate action. | |
| 174 | + $this->cleaner->clean( $cleanup_time_limit ); | |
| 175 | + } else { | |
| 176 | + // Execute partial cleanup cycle, as in this logical branch deletion IS executed via a separate action. | |
| 177 | + $this->cleaner->reset_timeouts( $cleanup_time_limit ); | |
| 178 | + $this->cleaner->mark_failures( $cleanup_time_limit ); | |
| 179 | + } | |
| 180 | + | |
| 132 | 181 | $this->processed_actions_count = 0; |
| 133 | 182 | if ( false === $this->has_maximum_concurrent_batches() ) { |
| 134 | 183 | $batch_size = apply_filters( 'action_scheduler_queue_runner_batch_size', 25 ); |
| 184 | + // Note: gc_collect_cycles() was considered here and in do_batch/clear_caches, but rejected: | |
| 185 | + // upside is speculative, GC sweep cost scales with object count and can cause pauses. | |
| 135 | 186 | do { |
| 136 | 187 | $processed_actions_in_batch = $this->do_batch( $batch_size, $context ); |
| 137 | 188 | $this->processed_actions_count += $processed_actions_in_batch; |
| 138 | - } while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $this->processed_actions_count ) ); // keep going until we run out of actions, time, or memory | |
| 189 | + } while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $this->processed_actions_count ) ); // keep going until we run out of actions, time, or memory. | |
| 139 | 190 | } |
| 140 | 191 | |
| 141 | 192 | do_action( 'action_scheduler_after_process_queue' ); |
| 142 | 193 | return $this->processed_actions_count; |
| @@ -147,23 +198,25 @@ | ||
| 147 | 198 | * |
| 148 | 199 | * Actions are processed by claiming a set of pending actions then processing each one until either the batch |
| 149 | 200 | * size is completed, or memory or time limits are reached, defined by @see $this->batch_limits_exceeded(). |
| 150 | 201 | * |
| 151 | - * @param int $size The maximum number of actions to process in the batch. | |
| 152 | - * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' | |
| 153 | - * Generally, this should be capitalised and not localised as it's a proper noun. | |
| 202 | + * @param int $size The maximum number of actions to process in the batch. | |
| 203 | + * @param string $context Optional identifier for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron' | |
| 204 | + * Generally, this should be capitalised and not localised as it's a proper noun. | |
| 154 | 205 | * @return int The number of actions processed. |
| 155 | 206 | */ |
| 156 | 207 | protected function do_batch( $size = 100, $context = '' ) { |
| 157 | - $claim = $this->store->stake_claim($size); | |
| 158 | - $this->monitor->attach($claim); | |
| 208 | + $claim = $this->store->stake_claim( $size ); | |
| 209 | + $this->monitor->attach( $claim ); | |
| 159 | 210 | $processed_actions = 0; |
| 160 | 211 | |
| 212 | + $claim_id = $claim->get_id(); | |
| 161 | 213 | foreach ( $claim->get_actions() as $action_id ) { |
| 162 | - // bail if we lost the claim | |
| 163 | - if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $claim->get_id() ) ) ) { | |
| 214 | + // Bail if we lost the claim. | |
| 215 | + if ( $claim_id !== $this->store->get_claim_id( $action_id ) ) { | |
| 164 | 216 | break; |
| 165 | 217 | } |
| 218 | + | |
| 166 | 219 | $this->process_action( $action_id, $context ); |
| 167 | 220 | $processed_actions++; |
| 168 | 221 | |
| 169 | 222 | if ( $this->batch_limits_exceeded( $processed_actions + $this->processed_actions_count ) ) { |
| @@ -169,9 +222,9 @@ | ||
| 169 | 222 | if ( $this->batch_limits_exceeded( $processed_actions + $this->processed_actions_count ) ) { |
| 170 | 223 | break; |
| 171 | 224 | } |
| 172 | 225 | } |
| 173 | - $this->store->release_claim($claim); | |
| 226 | + $this->store->release_claim( $claim ); | |
| 174 | 227 | $this->monitor->detach(); |
| 175 | 228 | $this->clear_caches(); |
| 176 | 229 | return $processed_actions; |
| 177 | 230 | } |
| @@ -214,11 +267,17 @@ | ||
| 214 | 267 | wp_cache_flush(); |
| 215 | 268 | } |
| 216 | 269 | } |
| 217 | 270 | |
| 271 | + /** | |
| 272 | + * Add schedule to WP cron. | |
| 273 | + * | |
| 274 | + * @param array<string, array<string, int|string>> $schedules Schedules. | |
| 275 | + * @return array<string, array<string, int|string>> | |
| 276 | + */ | |
| 218 | 277 | public function add_wp_cron_schedule( $schedules ) { |
| 219 | 278 | $schedules['every_minute'] = array( |
| 220 | - 'interval' => 60, // in seconds | |
| 279 | + 'interval' => 60, // in seconds. | |
| 221 | 280 | 'display' => __( 'Every minute', 'action-scheduler' ), |
| 222 | 281 | ); |
| 223 | 282 | |
| 224 | 283 | return $schedules; |