PluginProbe
WP-Stateless – Google Cloud Storage / 2.2.7
WP-Stateless – Google Cloud Storage v2.2.7
4.4.3 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.3.0 2.3.1 2.3.2 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 All 62 releases
wp-stateless / lib / Google / src / Google / Task / Runner.php

Runner.php in WP-Stateless – Google Cloud Storage 2.2.7, at lib/Google/src/Google/Task/Runner.php

284 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * Copyright 2014 Google Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 /**
19 * A task runner with exponential backoff support.
20 *
21 * @see https://developers.google.com/drive/web/handle-errors#implementing_exponential_backoff
22 */
23 namespace wpCloud\StatelessMedia\Google_Client;
24
25 class Google_Task_Runner
26 {
27 const TASK_RETRY_NEVER = 0;
28 const TASK_RETRY_ONCE = 1;
29 const TASK_RETRY_ALWAYS = -1;
30
31 /**
32 * @var integer $maxDelay The max time (in seconds) to wait before a retry.
33 */
34 private $maxDelay = 60;
35 /**
36 * @var integer $delay The previous delay from which the next is calculated.
37 */
38 private $delay = 1;
39
40 /**
41 * @var integer $factor The base number for the exponential back off.
42 */
43 private $factor = 2;
44 /**
45 * @var float $jitter A random number between -$jitter and $jitter will be
46 * added to $factor on each iteration to allow for a better distribution of
47 * retries.
48 */
49 private $jitter = 0.5;
50
51 /**
52 * @var integer $attempts The number of attempts that have been tried so far.
53 */
54 private $attempts = 0;
55 /**
56 * @var integer $maxAttempts The max number of attempts allowed.
57 */
58 private $maxAttempts = 1;
59
60 /**
61 * @var callable $action The task to run and possibly retry.
62 */
63 private $action;
64 /**
65 * @var array $arguments The task arguments.
66 */
67 private $arguments;
68
69 /**
70 * @var array $retryMap Map of errors with retry counts.
71 */
72 protected $retryMap = [
73 '500' => self::TASK_RETRY_ALWAYS,
74 '503' => self::TASK_RETRY_ALWAYS,
75 'rateLimitExceeded' => self::TASK_RETRY_ALWAYS,
76 'userRateLimitExceeded' => self::TASK_RETRY_ALWAYS,
77 6 => self::TASK_RETRY_ALWAYS, // CURLE_COULDNT_RESOLVE_HOST
78 7 => self::TASK_RETRY_ALWAYS, // CURLE_COULDNT_CONNECT
79 28 => self::TASK_RETRY_ALWAYS, // CURLE_OPERATION_TIMEOUTED
80 35 => self::TASK_RETRY_ALWAYS, // CURLE_SSL_CONNECT_ERROR
81 52 => self::TASK_RETRY_ALWAYS // CURLE_GOT_NOTHING
82 ];
83
84 /**
85 * Creates a new task runner with exponential backoff support.
86 *
87 * @param array $config The task runner config
88 * @param string $name The name of the current task (used for logging)
89 * @param callable $action The task to run and possibly retry
90 * @param array $arguments The task arguments
91 * @throws Google_Task_Exception when misconfigured
92 */
93 public function __construct(
94 $config,
95 $name,
96 $action,
97 array $arguments = array()
98 ) {
99 if (isset($config['initial_delay'])) {
100 if ($config['initial_delay'] < 0) {
101 throw new Google_Task_Exception(
102 'Task configuration `initial_delay` must not be negative.'
103 );
104 }
105
106 $this->delay = $config['initial_delay'];
107 }
108
109 if (isset($config['max_delay'])) {
110 if ($config['max_delay'] <= 0) {
111 throw new Google_Task_Exception(
112 'Task configuration `max_delay` must be greater than 0.'
113 );
114 }
115
116 $this->maxDelay = $config['max_delay'];
117 }
118
119 if (isset($config['factor'])) {
120 if ($config['factor'] <= 0) {
121 throw new Google_Task_Exception(
122 'Task configuration `factor` must be greater than 0.'
123 );
124 }
125
126 $this->factor = $config['factor'];
127 }
128
129 if (isset($config['jitter'])) {
130 if ($config['jitter'] <= 0) {
131 throw new Google_Task_Exception(
132 'Task configuration `jitter` must be greater than 0.'
133 );
134 }
135
136 $this->jitter = $config['jitter'];
137 }
138
139 if (isset($config['retries'])) {
140 if ($config['retries'] < 0) {
141 throw new Google_Task_Exception(
142 'Task configuration `retries` must not be negative.'
143 );
144 }
145 $this->maxAttempts += $config['retries'];
146 }
147
148 if (!is_callable($action)) {
149 throw new Google_Task_Exception(
150 'Task argument `$action` must be a valid callable.'
151 );
152 }
153
154 $this->action = $action;
155 $this->arguments = $arguments;
156 }
157
158 /**
159 * Checks if a retry can be attempted.
160 *
161 * @return boolean
162 */
163 public function canAttempt()
164 {
165 return $this->attempts < $this->maxAttempts;
166 }
167
168 /**
169 * Runs the task and (if applicable) automatically retries when errors occur.
170 *
171 * @return mixed
172 * @throws Google_Task_Retryable on failure when no retries are available.
173 */
174 public function run()
175 {
176 while ($this->attempt()) {
177 try {
178 return call_user_func_array($this->action, $this->arguments);
179 } catch (Google_Service_Exception $exception) {
180 $allowedRetries = $this->allowedRetries(
181 $exception->getCode(),
182 $exception->getErrors()
183 );
184
185 if (!$this->canAttempt() || !$allowedRetries) {
186 throw $exception;
187 }
188
189 if ($allowedRetries > 0) {
190 $this->maxAttempts = min(
191 $this->maxAttempts,
192 $this->attempts + $allowedRetries
193 );
194 }
195 }
196 }
197 }
198
199 /**
200 * Runs a task once, if possible. This is useful for bypassing the `run()`
201 * loop.
202 *
203 * NOTE: If this is not the first attempt, this function will sleep in
204 * accordance to the backoff configurations before running the task.
205 *
206 * @return boolean
207 */
208 public function attempt()
209 {
210 if (!$this->canAttempt()) {
211 return false;
212 }
213
214 if ($this->attempts > 0) {
215 $this->backOff();
216 }
217
218 $this->attempts++;
219 return true;
220 }
221
222 /**
223 * Sleeps in accordance to the backoff configurations.
224 */
225 private function backOff()
226 {
227 $delay = $this->getDelay();
228
229 usleep($delay * 1000000);
230 }
231
232 /**
233 * Gets the delay (in seconds) for the current backoff period.
234 *
235 * @return float
236 */
237 private function getDelay()
238 {
239 $jitter = $this->getJitter();
240 $factor = $this->attempts > 1 ? $this->factor + $jitter : 1 + abs($jitter);
241
242 return $this->delay = min($this->maxDelay, $this->delay * $factor);
243 }
244
245 /**
246 * Gets the current jitter (random number between -$this->jitter and
247 * $this->jitter).
248 *
249 * @return float
250 */
251 private function getJitter()
252 {
253 return $this->jitter * 2 * mt_rand() / mt_getrandmax() - $this->jitter;
254 }
255
256 /**
257 * Gets the number of times the associated task can be retried.
258 *
259 * NOTE: -1 is returned if the task can be retried indefinitely
260 *
261 * @return integer
262 */
263 public function allowedRetries($code, $errors = array())
264 {
265 if (isset($this->retryMap[$code])) {
266 return $this->retryMap[$code];
267 }
268
269 if (
270 !empty($errors) &&
271 isset($errors[0]['reason'], $this->retryMap[$errors[0]['reason']])
272 ) {
273 return $this->retryMap[$errors[0]['reason']];
274 }
275
276 return 0;
277 }
278
279 public function setRetryMap($retryMap)
280 {
281 $this->retryMap = $retryMap;
282 }
283 }
284