PluginProbe
WANotifier for Forms and Actions / 1.0.0
WANotifier for Forms and Actions v1.0.0
3.1.1 3.1.0 3.0.4 2.7.10 2.7.11 2.7.12 2.7.13 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 3.0.0 3.0.1 3.0.2 3.0.3 trunk 0.1.0 0.1.1 1.0.0 1.0.1 1.0.2 All 67 releases
notifier / libraries / action-scheduler / classes / abstracts / ActionScheduler_Abstract_QueueRunner.php

ActionScheduler_Abstract_QueueRunner.php in WANotifier for Forms and Actions 1.0.0, at libraries/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php

299 lines 10.5 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
31 * @param ActionScheduler_FatalErrorMonitor $monitor
32 * @param ActionScheduler_QueueCleaner $cleaner
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 identifer 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 */
50 public function process_action( $action_id, $context = '' ) {
51 try {
52 $valid_action = false;
53 do_action( 'action_scheduler_before_execute', $action_id, $context );
54
55 if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) {
56 do_action( 'action_scheduler_execution_ignored', $action_id, $context );
57 return;
58 }
59
60 $valid_action = true;
61 do_action( 'action_scheduler_begin_execute', $action_id, $context );
62
63 $action = $this->store->fetch_action( $action_id );
64 $this->store->log_execution( $action_id );
65 $action->execute();
66 do_action( 'action_scheduler_after_execute', $action_id, $action, $context );
67 $this->store->mark_complete( $action_id );
68 } catch ( Exception $e ) {
69 if ( $valid_action ) {
70 $this->store->mark_failure( $action_id );
71 do_action( 'action_scheduler_failed_execution', $action_id, $e, $context );
72 } else {
73 do_action( 'action_scheduler_failed_validation', $action_id, $e, $context );
74 }
75 }
76
77 if ( isset( $action ) && is_a( $action, 'ActionScheduler_Action' ) && $action->get_schedule()->is_recurring() ) {
78 $this->schedule_next_instance( $action, $action_id );
79 }
80 }
81
82 /**
83 * Schedule the next instance of the action if necessary.
84 *
85 * @param ActionScheduler_Action $action
86 * @param int $action_id
87 */
88 protected function schedule_next_instance( ActionScheduler_Action $action, $action_id ) {
89 // If a recurring action has been consistently failing, we may wish to stop rescheduling it.
90 if (
91 ActionScheduler_Store::STATUS_FAILED === $this->store->get_status( $action_id )
92 && $this->recurring_action_is_consistently_failing( $action, $action_id )
93 ) {
94 ActionScheduler_Logger::instance()->log(
95 $action_id,
96 __( 'This action appears to be consistently failing. A new instance will not be scheduled.', 'action-scheduler' )
97 );
98
99 return;
100 }
101
102 try {
103 ActionScheduler::factory()->repeat( $action );
104 } catch ( Exception $e ) {
105 do_action( 'action_scheduler_failed_to_schedule_next_instance', $action_id, $e, $action );
106 }
107 }
108
109 /**
110 * Determine if the specified recurring action has been consistently failing.
111 *
112 * @param ActionScheduler_Action $action The recurring action to be rescheduled.
113 * @param int $action_id The ID of the recurring action.
114 *
115 * @return bool
116 */
117 private function recurring_action_is_consistently_failing( ActionScheduler_Action $action, $action_id ) {
118 /**
119 * Controls the failure threshold for recurring actions.
120 *
121 * Before rescheduling a recurring action, we look at its status. If it failed, we then check if all of the most
122 * recent actions (upto the threshold set by this filter) sharing the same hook have also failed: if they have,
123 * that is considered consistent failure and a new instance of the action will not be scheduled.
124 *
125 * @param int $failure_threshold Number of actions of the same hook to examine for failure. Defaults to 5.
126 */
127 $consistent_failure_threshold = (int) apply_filters( 'action_scheduler_recurring_action_failure_threshold', 5 );
128
129 // This query should find the earliest *failing* action (for the hook we are interested in) within our threshold.
130 $query_args = array(
131 'hook' => $action->get_hook(),
132 'status' => ActionScheduler_Store::STATUS_FAILED,
133 'date' => date_create( 'now', timezone_open( 'UTC' ) )->format( 'Y-m-d H:i:s' ),
134 'date_compare' => '<',
135 'per_page' => 1,
136 'offset' => $consistent_failure_threshold - 1
137 );
138
139 $first_failing_action_id = $this->store->query_actions( $query_args );
140
141 // If we didn't retrieve an action ID, then there haven't been enough failures for us to worry about.
142 if ( empty( $first_failing_action_id ) ) {
143 return false;
144 }
145
146 // Now let's fetch the first action (having the same hook) of *any status*ithin the same window.
147 unset( $query_args['status'] );
148 $first_action_id_with_the_same_hook = $this->store->query_actions( $query_args );
149
150 // If the IDs match, then actions for this hook must be consistently failing.
151 return $first_action_id_with_the_same_hook === $first_failing_action_id;
152 }
153
154 /**
155 * Run the queue cleaner.
156 *
157 * @author Jeremy Pry
158 */
159 protected function run_cleanup() {
160 $this->cleaner->clean( 10 * $this->get_time_limit() );
161 }
162
163 /**
164 * Get the number of concurrent batches a runner allows.
165 *
166 * @return int
167 */
168 public function get_allowed_concurrent_batches() {
169 return apply_filters( 'action_scheduler_queue_runner_concurrent_batches', 1 );
170 }
171
172 /**
173 * Check if the number of allowed concurrent batches is met or exceeded.
174 *
175 * @return bool
176 */
177 public function has_maximum_concurrent_batches() {
178 return $this->store->get_claim_count() >= $this->get_allowed_concurrent_batches();
179 }
180
181 /**
182 * Get the maximum number of seconds a batch can run for.
183 *
184 * @return int The number of seconds.
185 */
186 protected function get_time_limit() {
187
188 $time_limit = 30;
189
190 // Apply deprecated filter from deprecated get_maximum_execution_time() method
191 if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) {
192 _deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' );
193 $time_limit = apply_filters( 'action_scheduler_maximum_execution_time', $time_limit );
194 }
195
196 return absint( apply_filters( 'action_scheduler_queue_runner_time_limit', $time_limit ) );
197 }
198
199 /**
200 * Get the number of seconds the process has been running.
201 *
202 * @return int The number of seconds.
203 */
204 protected function get_execution_time() {
205 $execution_time = microtime( true ) - $this->created_time;
206
207 // Get the CPU time if the hosting environment uses it rather than wall-clock time to calculate a process's execution time.
208 if ( function_exists( 'getrusage' ) && apply_filters( 'action_scheduler_use_cpu_execution_time', defined( 'PANTHEON_ENVIRONMENT' ) ) ) {
209 $resource_usages = getrusage();
210
211 if ( isset( $resource_usages['ru_stime.tv_usec'], $resource_usages['ru_stime.tv_usec'] ) ) {
212 $execution_time = $resource_usages['ru_stime.tv_sec'] + ( $resource_usages['ru_stime.tv_usec'] / 1000000 );
213 }
214 }
215
216 return $execution_time;
217 }
218
219 /**
220 * Check if the host's max execution time is (likely) to be exceeded if processing more actions.
221 *
222 * @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
223 * @return bool
224 */
225 protected function time_likely_to_be_exceeded( $processed_actions ) {
226
227 $execution_time = $this->get_execution_time();
228 $max_execution_time = $this->get_time_limit();
229 $time_per_action = $execution_time / $processed_actions;
230 $estimated_time = $execution_time + ( $time_per_action * 3 );
231 $likely_to_be_exceeded = $estimated_time > $max_execution_time;
232
233 return apply_filters( 'action_scheduler_maximum_execution_time_likely_to_be_exceeded', $likely_to_be_exceeded, $this, $processed_actions, $execution_time, $max_execution_time );
234 }
235
236 /**
237 * Get memory limit
238 *
239 * Based on WP_Background_Process::get_memory_limit()
240 *
241 * @return int
242 */
243 protected function get_memory_limit() {
244 if ( function_exists( 'ini_get' ) ) {
245 $memory_limit = ini_get( 'memory_limit' );
246 } else {
247 $memory_limit = '128M'; // Sensible default, and minimum required by WooCommerce
248 }
249
250 if ( ! $memory_limit || -1 === $memory_limit || '-1' === $memory_limit ) {
251 // Unlimited, set to 32GB.
252 $memory_limit = '32G';
253 }
254
255 return ActionScheduler_Compatibility::convert_hr_to_bytes( $memory_limit );
256 }
257
258 /**
259 * Memory exceeded
260 *
261 * Ensures the batch process never exceeds 90% of the maximum WordPress memory.
262 *
263 * Based on WP_Background_Process::memory_exceeded()
264 *
265 * @return bool
266 */
267 protected function memory_exceeded() {
268
269 $memory_limit = $this->get_memory_limit() * 0.90;
270 $current_memory = memory_get_usage( true );
271 $memory_exceeded = $current_memory >= $memory_limit;
272
273 return apply_filters( 'action_scheduler_memory_exceeded', $memory_exceeded, $this );
274 }
275
276 /**
277 * See if the batch limits have been exceeded, which is when memory usage is almost at
278 * the maximum limit, or the time to process more actions will exceed the max time limit.
279 *
280 * Based on WC_Background_Process::batch_limits_exceeded()
281 *
282 * @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
283 * @return bool
284 */
285 protected function batch_limits_exceeded( $processed_actions ) {
286 return $this->memory_exceeded() || $this->time_likely_to_be_exceeded( $processed_actions );
287 }
288
289 /**
290 * Process actions in the queue.
291 *
292 * @author Jeremy Pry
293 * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
294 * Generally, this should be capitalised and not localised as it's a proper noun.
295 * @return int The number of actions processed.
296 */
297 abstract public function run( $context = '' );
298 }
299