-
Notifications
You must be signed in to change notification settings - Fork 21
/
AbstractBatchedActionSchedulerJob.php
188 lines (167 loc) · 5.52 KB
/
AbstractBatchedActionSchedulerJob.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
<?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\Jobs;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* AbstractBatchedActionSchedulerJob class.
*
* Enables a job to be processed in recurring scheduled batches with queued events.
*
* Notes:
* - Uses ActionScheduler's very scalable async actions feature which will run async batches in loop back requests until all batches are done
* - Items may be processed concurrently by AS, but batches will be created one after the other, not concurrently
* - The job will not start if it is already running
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Jobs
*/
abstract class AbstractBatchedActionSchedulerJob extends AbstractActionSchedulerJob implements BatchedActionSchedulerJobInterface {
/**
* Init the batch schedule for the job.
*
* The job name is used to generate the schedule event name.
*/
public function init(): void {
add_action( $this->get_create_batch_hook(), [ $this, 'handle_create_batch_action' ] );
parent::init();
}
/**
* Get the hook name for the "create batch" action.
*
* @return string
*/
protected function get_create_batch_hook(): string {
return "{$this->get_hook_base_name()}create_batch";
}
/**
* Enqueue the "create_batch" action provided it doesn't already exist.
*
* To minimize the resource use of starting the job the batch creation is handled async.
*
* @param array $args
*/
public function schedule( array $args = [] ) {
$this->schedule_create_batch_action( 1 );
}
/**
* Handles batch creation action hook.
*
* @hooked gla/jobs/{$job_name}/create_batch
*
* Schedules an action to run immediately for the items in the batch.
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @throws Exception If an error occurs.
* @throws JobException If the job failure rate is too high.
*/
public function handle_create_batch_action( int $batch_number ) {
$create_batch_hook = $this->get_create_batch_hook();
$create_batch_args = [ $batch_number ];
$this->monitor->validate_failure_rate( $this, $create_batch_hook, $create_batch_args );
if ( $this->retry_on_timeout ) {
$this->monitor->attach_timeout_monitor( $create_batch_hook, $create_batch_args );
}
$items = $this->get_batch( $batch_number );
if ( empty( $items ) ) {
// if no more items the job is complete
$this->handle_complete( $batch_number );
} else {
// if items, schedule the process action
$this->schedule_process_action( $items );
// Add another "create_batch" action to handle unfiltered items.
// The last batch created here will be an empty batch, it
// will call "handle_complete" to finish the job.
$this->schedule_create_batch_action( $batch_number + 1 );
}
$this->monitor->detach_timeout_monitor( $create_batch_hook, $create_batch_args );
}
/**
* Get job batch size.
*
* @return int
*/
protected function get_batch_size(): int {
/**
* Filters the batch size for the job.
*
* @param string Job's name
*/
return apply_filters( 'woocommerce_gla_batched_job_size', 100, $this->get_name() );
}
/**
* Get the query offset based on a given batch number and the specified batch size.
*
* @param int $batch_number
*
* @return int
*/
protected function get_query_offset( int $batch_number ): int {
return $this->get_batch_size() * ( $batch_number - 1 );
}
/**
* Schedule a new "create batch" action to run immediately.
*
* @param int $batch_number The batch number for the new batch.
*/
protected function schedule_create_batch_action( int $batch_number ) {
if ( $this->can_schedule( [ $batch_number ] ) ) {
$this->action_scheduler->schedule_immediate( $this->get_create_batch_hook(), [ $batch_number ] );
}
}
/**
* Schedule a new "process" action to run immediately.
*
* @param int[] $items Array of item ids.
*/
protected function schedule_process_action( array $items ) {
if ( ! $this->is_processing( $items ) ) {
$this->action_scheduler->schedule_immediate( $this->get_process_item_hook(), [ $items ] );
}
}
/**
* Check if this job is running.
*
* The job is considered to be running if a "create_batch" action is currently pending or in-progress.
*
* @param array|null $args
*
* @return bool
*/
protected function is_running( ?array $args = [] ): bool {
return $this->action_scheduler->has_scheduled_action( $this->get_create_batch_hook(), $args );
}
/**
* Check if this job is processing the given items.
*
* The job is considered to be processing if a "process_item" action is currently pending or in-progress.
*
* @param array $items
*
* @return bool
*/
protected function is_processing( array $items = [] ): bool {
return $this->action_scheduler->has_scheduled_action( $this->get_process_item_hook(), [ $items ] );
}
/**
* Called when the job is completed.
*
* @param int $final_batch_number The final batch number when the job was completed.
* If equal to 1 then no items were processed by the job.
*/
protected function handle_complete( int $final_batch_number ) {
// Optionally over-ride this method in child class.
}
/**
* Get a single batch of items.
*
* If no items are returned the job will stop.
*
* @param int $batch_number The batch number increments for each new batch in the job cycle.
*
* @return array
*
* @throws Exception If an error occurs. The exception will be logged by ActionScheduler.
*/
abstract protected function get_batch( int $batch_number ): array;
}