PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.5.4
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.5.4
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.5.4, at vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php

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