PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.7.7
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.7.7
1.7.7 1.7.6 1.7.5 1.7.4 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.2.1 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 All 33 releases
image-optimization / vendor / woocommerce / action-scheduler / classes / abstracts / ActionScheduler_Abstract_QueueRunner.php

ActionScheduler_Abstract_QueueRunner.php in Image Optimizer – Compress Images and Convert to WebP or AVIF 1.7.7, at vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php

384 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Abstract class with common Queue Cleaner functionality.
5 */
6 abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abstract_QueueRunner_Deprecated {
7
8 /**
9 * ActionScheduler_QueueCleaner instance.
10 *
11 * @var ActionScheduler_QueueCleaner
12 */
13 protected $cleaner;
14
15 /**
16 * ActionScheduler_FatalErrorMonitor instance.
17 *
18 * @var ActionScheduler_FatalErrorMonitor
19 */
20 protected $monitor;
21
22 /**
23 * ActionScheduler_Store instance.
24 *
25 * @var ActionScheduler_Store
26 */
27 protected $store;
28
29 /**
30 * The created time.
31 *
32 * Represents when the queue runner was constructed and used when calculating how long a PHP request has been running.
33 * For this reason it should be as close as possible to the PHP request start time.
34 *
35 * @var int
36 */
37 private $created_time;
38
39 /**
40 * ActionScheduler_Abstract_QueueRunner constructor.
41 *
42 * @param ActionScheduler_Store|null $store Store object.
43 * @param ActionScheduler_FatalErrorMonitor|null $monitor Monitor object.
44 * @param ActionScheduler_QueueCleaner|null $cleaner Cleaner object.
45 */
46 public function __construct( ?ActionScheduler_Store $store = null, ?ActionScheduler_FatalErrorMonitor $monitor = null, ?ActionScheduler_QueueCleaner $cleaner = null ) {
47
48 $this->created_time = microtime( true );
49
50 $this->store = $store ? $store : ActionScheduler_Store::instance();
51 $this->monitor = $monitor ? $monitor : new ActionScheduler_FatalErrorMonitor( $this->store );
52 $this->cleaner = $cleaner ? $cleaner : new ActionScheduler_QueueCleaner( $this->store );
53 }
54
55 /**
56 * Process an individual action.
57 *
58 * @param int $action_id The action ID to process.
59 * @param string $context Optional identifier for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
60 * Generally, this should be capitalised and not localised as it's a proper noun.
61 * @throws \Exception When error running action.
62 */
63 public function process_action( $action_id, $context = '' ) {
64 // Temporarily override the error handler while we process the current action.
65 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
66 set_error_handler(
67 /**
68 * Temporary error handler which can catch errors and convert them into exceptions. This facilitates more
69 * robust error handling across all supported PHP versions.
70 *
71 * @throws Exception
72 *
73 * @param int $type Error level expressed as an integer.
74 * @param string $message Error message.
75 */
76 function ( $type, $message ) {
77 throw new Exception( $message );
78 },
79 E_USER_ERROR | E_RECOVERABLE_ERROR
80 );
81
82 /*
83 * The nested try/catch structure is required because we potentially need to convert thrown errors into
84 * exceptions (and an exception thrown from a catch block cannot be caught by a later catch block in the *same*
85 * structure).
86 */
87 try {
88 try {
89 $valid_action = false;
90 do_action( 'action_scheduler_before_execute', $action_id, $context );
91
92 if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) {
93 do_action( 'action_scheduler_execution_ignored', $action_id, $context );
94 return;
95 }
96
97 $valid_action = true;
98 do_action( 'action_scheduler_begin_execute', $action_id, $context );
99
100 $action = $this->store->fetch_action( $action_id );
101 $this->store->log_execution( $action_id );
102 $action->execute();
103 do_action( 'action_scheduler_after_execute', $action_id, $action, $context );
104 $this->store->mark_complete( $action_id );
105 } catch ( Throwable $e ) {
106 // Throwable is defined when executing under PHP 7.0 and up. We convert it to an exception, for
107 // compatibility with ActionScheduler_Logger.
108 throw new Exception( $e->getMessage(), $e->getCode(), $e );
109 }
110 } catch ( Exception $e ) {
111 // This catch block exists for compatibility with PHP 5.6.
112 $this->handle_action_error( $action_id, $e, $context, $valid_action );
113 } finally {
114 restore_error_handler();
115 }
116
117 if ( isset( $action ) && is_a( $action, 'ActionScheduler_Action' ) && $action->get_schedule()->is_recurring() ) {
118 $this->schedule_next_instance( $action, $action_id );
119 }
120 }
121
122 /**
123 * Marks actions as either having failed execution or failed validation, as appropriate.
124 *
125 * @param int $action_id Action ID.
126 * @param Exception $e Exception instance.
127 * @param string $context Execution context.
128 * @param bool $valid_action If the action is valid.
129 *
130 * @return void
131 */
132 private function handle_action_error( $action_id, $e, $context, $valid_action ) {
133 if ( $valid_action ) {
134 $this->store->mark_failure( $action_id );
135 /**
136 * Runs when action execution fails.
137 *
138 * @param int $action_id Action ID.
139 * @param Exception $e Exception instance.
140 * @param string $context Execution context.
141 */
142 do_action( 'action_scheduler_failed_execution', $action_id, $e, $context );
143 } else {
144 /**
145 * Runs when action validation fails.
146 *
147 * @param int $action_id Action ID.
148 * @param Exception $e Exception instance.
149 * @param string $context Execution context.
150 */
151 do_action( 'action_scheduler_failed_validation', $action_id, $e, $context );
152 }
153 }
154
155 /**
156 * Schedule the next instance of the action if necessary.
157 *
158 * @param ActionScheduler_Action $action Action.
159 * @param int $action_id Action ID.
160 */
161 protected function schedule_next_instance( ActionScheduler_Action $action, $action_id ) {
162 // If a recurring action has been consistently failing, we may wish to stop rescheduling it.
163 if (
164 ActionScheduler_Store::STATUS_FAILED === $this->store->get_status( $action_id )
165 && $this->recurring_action_is_consistently_failing( $action, $action_id )
166 ) {
167 ActionScheduler_Logger::instance()->log(
168 $action_id,
169 __( 'This action appears to be consistently failing. A new instance will not be scheduled.', 'action-scheduler' )
170 );
171
172 return;
173 }
174
175 try {
176 ActionScheduler::factory()->repeat( $action );
177 } catch ( Exception $e ) {
178 do_action( 'action_scheduler_failed_to_schedule_next_instance', $action_id, $e, $action );
179 }
180 }
181
182 /**
183 * Determine if the specified recurring action has been consistently failing.
184 *
185 * @param ActionScheduler_Action $action The recurring action to be rescheduled.
186 * @param int $action_id The ID of the recurring action.
187 *
188 * @return bool
189 */
190 private function recurring_action_is_consistently_failing( ActionScheduler_Action $action, $action_id ) {
191 /**
192 * Controls the failure threshold for recurring actions.
193 *
194 * Before rescheduling a recurring action, we look at its status. If it failed, we then check if all of the most
195 * recent actions (upto the threshold set by this filter) sharing the same hook have also failed: if they have,
196 * that is considered consistent failure and a new instance of the action will not be scheduled.
197 *
198 * @param int $failure_threshold Number of actions of the same hook to examine for failure. Defaults to 5.
199 */
200 $consistent_failure_threshold = (int) apply_filters( 'action_scheduler_recurring_action_failure_threshold', 5 );
201
202 // This query should find the earliest *failing* action (for the hook we are interested in) within our threshold.
203 $query_args = array(
204 'hook' => $action->get_hook(),
205 'status' => ActionScheduler_Store::STATUS_FAILED,
206 'date' => date_create( 'now', timezone_open( 'UTC' ) )->format( 'Y-m-d H:i:s' ),
207 'date_compare' => '<',
208 'per_page' => 1,
209 'offset' => $consistent_failure_threshold - 1,
210 );
211
212 $first_failing_action_id = $this->store->query_actions( $query_args );
213
214 // If we didn't retrieve an action ID, then there haven't been enough failures for us to worry about.
215 if ( empty( $first_failing_action_id ) ) {
216 return false;
217 }
218
219 // Now let's fetch the first action (having the same hook) of *any status* within the same window.
220 unset( $query_args['status'] );
221 $first_action_id_with_the_same_hook = $this->store->query_actions( $query_args );
222
223 /**
224 * If a recurring action is assessed as consistently failing, it will not be rescheduled. This hook provides a
225 * way to observe and optionally override that assessment.
226 *
227 * @param bool $is_consistently_failing If the action is considered to be consistently failing.
228 * @param ActionScheduler_Action $action The action being assessed.
229 */
230 return (bool) apply_filters(
231 'action_scheduler_recurring_action_is_consistently_failing',
232 $first_action_id_with_the_same_hook === $first_failing_action_id,
233 $action
234 );
235 }
236
237 /**
238 * Run the queue cleaner.
239 */
240 protected function run_cleanup() {
241 $this->cleaner->clean( 10 * $this->get_time_limit() );
242 }
243
244 /**
245 * Get the number of concurrent batches a runner allows.
246 *
247 * @return int
248 */
249 public function get_allowed_concurrent_batches() {
250 return apply_filters( 'action_scheduler_queue_runner_concurrent_batches', 1 );
251 }
252
253 /**
254 * Check if the number of allowed concurrent batches is met or exceeded.
255 *
256 * @return bool
257 */
258 public function has_maximum_concurrent_batches() {
259 return $this->store->get_claim_count() >= $this->get_allowed_concurrent_batches();
260 }
261
262 /**
263 * Get the maximum number of seconds a batch can run for.
264 *
265 * @return int The number of seconds.
266 */
267 protected function get_time_limit() {
268
269 $time_limit = 30;
270
271 // Apply deprecated filter from deprecated get_maximum_execution_time() method.
272 if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) {
273 _deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' );
274 $time_limit = apply_filters( 'action_scheduler_maximum_execution_time', $time_limit );
275 }
276
277 return absint( apply_filters( 'action_scheduler_queue_runner_time_limit', $time_limit ) );
278 }
279
280 /**
281 * Get the number of seconds the process has been running.
282 *
283 * @return int The number of seconds.
284 */
285 protected function get_execution_time() {
286 $execution_time = microtime( true ) - $this->created_time;
287
288 // Get the CPU time if the hosting environment uses it rather than wall-clock time to calculate a process's execution time.
289 if ( function_exists( 'getrusage' ) && apply_filters( 'action_scheduler_use_cpu_execution_time', defined( 'PANTHEON_ENVIRONMENT' ) ) ) {
290 $resource_usages = getrusage();
291
292 if ( isset( $resource_usages['ru_stime.tv_usec'], $resource_usages['ru_stime.tv_usec'] ) ) {
293 $execution_time = $resource_usages['ru_stime.tv_sec'] + ( $resource_usages['ru_stime.tv_usec'] / 1000000 );
294 }
295 }
296
297 return $execution_time;
298 }
299
300 /**
301 * Check if the host's max execution time is (likely) to be exceeded if processing more actions.
302 *
303 * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action.
304 * @return bool
305 */
306 protected function time_likely_to_be_exceeded( $processed_actions ) {
307 $execution_time = $this->get_execution_time();
308 $max_execution_time = $this->get_time_limit();
309
310 // Safety against division by zero errors.
311 if ( 0 === $processed_actions ) {
312 return $execution_time >= $max_execution_time;
313 }
314
315 $time_per_action = $execution_time / $processed_actions;
316 $estimated_time = $execution_time + ( $time_per_action * 3 );
317 $likely_to_be_exceeded = $estimated_time > $max_execution_time;
318
319 return apply_filters( 'action_scheduler_maximum_execution_time_likely_to_be_exceeded', $likely_to_be_exceeded, $this, $processed_actions, $execution_time, $max_execution_time );
320 }
321
322 /**
323 * Get memory limit
324 *
325 * Based on WP_Background_Process::get_memory_limit()
326 *
327 * @return int
328 */
329 protected function get_memory_limit() {
330 if ( function_exists( 'ini_get' ) ) {
331 $memory_limit = ini_get( 'memory_limit' );
332 } else {
333 $memory_limit = '128M'; // Sensible default, and minimum required by WooCommerce.
334 }
335
336 if ( ! $memory_limit || -1 === $memory_limit || '-1' === $memory_limit ) {
337 // Unlimited, set to 32GB.
338 $memory_limit = '32G';
339 }
340
341 return ActionScheduler_Compatibility::convert_hr_to_bytes( $memory_limit );
342 }
343
344 /**
345 * Memory exceeded
346 *
347 * Ensures the batch process never exceeds 90% of the maximum WordPress memory.
348 *
349 * Based on WP_Background_Process::memory_exceeded()
350 *
351 * @return bool
352 */
353 protected function memory_exceeded() {
354
355 $memory_limit = $this->get_memory_limit() * 0.90;
356 $current_memory = memory_get_usage( true );
357 $memory_exceeded = $current_memory >= $memory_limit;
358
359 return apply_filters( 'action_scheduler_memory_exceeded', $memory_exceeded, $this );
360 }
361
362 /**
363 * See if the batch limits have been exceeded, which is when memory usage is almost at
364 * the maximum limit, or the time to process more actions will exceed the max time limit.
365 *
366 * Based on WC_Background_Process::batch_limits_exceeded()
367 *
368 * @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action.
369 * @return bool
370 */
371 protected function batch_limits_exceeded( $processed_actions ) {
372 return $this->memory_exceeded() || $this->time_likely_to_be_exceeded( $processed_actions );
373 }
374
375 /**
376 * Process actions in the queue.
377 *
378 * @param string $context Optional identifier for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
379 * Generally, this should be capitalised and not localised as it's a proper noun.
380 * @return int The number of actions processed.
381 */
382 abstract public function run( $context = '' );
383 }
384