PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.2
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.2
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / app / Services / Libraries / action-scheduler / classes / abstracts / ActionScheduler_Abstract_QueueRunner.php

ActionScheduler_Abstract_QueueRunner.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.2, at app/Services/Libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php

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