source: vendor/google/apiclient/src/Task/Runner.php@ e3d4e0a

Last change on this file since e3d4e0a was e3d4e0a, checked in by Vlado 222039 <vlado.popovski@…>, 7 days ago

Upload project files

  • Property mode set to 100644
File size: 8.0 KB
Line 
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
18namespace Google\Task;
19
20use Google\Service\Exception as GoogleServiceException;
21use Google\Task\Exception as GoogleTaskException;
22
23/**
24 * A task runner with exponential backoff support.
25 *
26 * @see https://developers.google.com/drive/web/handle-errors#implementing_exponential_backoff
27 */
28class Runner
29{
30 const TASK_RETRY_NEVER = 0;
31 const TASK_RETRY_ONCE = 1;
32 const TASK_RETRY_ALWAYS = -1;
33
34 /**
35 * @var integer $maxDelay The max time (in seconds) to wait before a retry.
36 */
37 private $maxDelay = 60;
38
39 /**
40 * @var integer $delay The previous delay from which the next is calculated.
41 */
42 private $delay = 1;
43
44 /**
45 * @var integer $factor The base number for the exponential back off.
46 */
47 private $factor = 2;
48
49 /**
50 * @var float $jitter A random number between -$jitter and $jitter will be
51 * added to $factor on each iteration to allow for a better distribution of
52 * retries.
53 */
54 private $jitter = 0.5;
55
56 /**
57 * @var integer $attempts The number of attempts that have been tried so far.
58 */
59 private $attempts = 0;
60
61 /**
62 * @var integer $maxAttempts The max number of attempts allowed.
63 */
64 private $maxAttempts = 1;
65
66 /**
67 * @var callable $action The task to run and possibly retry.
68 */
69 private $action;
70
71 /**
72 * @var array $arguments The task arguments.
73 */
74 private $arguments;
75
76 /**
77 * @var array $retryMap Map of errors with retry counts.
78 */
79 protected $retryMap = [
80 '500' => self::TASK_RETRY_ALWAYS,
81 '503' => self::TASK_RETRY_ALWAYS,
82 'rateLimitExceeded' => self::TASK_RETRY_ALWAYS,
83 'userRateLimitExceeded' => self::TASK_RETRY_ALWAYS,
84 6 => self::TASK_RETRY_ALWAYS, // CURLE_COULDNT_RESOLVE_HOST
85 7 => self::TASK_RETRY_ALWAYS, // CURLE_COULDNT_CONNECT
86 28 => self::TASK_RETRY_ALWAYS, // CURLE_OPERATION_TIMEOUTED
87 35 => self::TASK_RETRY_ALWAYS, // CURLE_SSL_CONNECT_ERROR
88 52 => self::TASK_RETRY_ALWAYS, // CURLE_GOT_NOTHING
89 'lighthouseError' => self::TASK_RETRY_NEVER
90 ];
91
92 /**
93 * Creates a new task runner with exponential backoff support.
94 *
95 * @param array $config The task runner config
96 * @param string $name The name of the current task (used for logging)
97 * @param callable $action The task to run and possibly retry
98 * @param array $arguments The task arguments
99 * @throws \Google\Task\Exception when misconfigured
100 */
101 // @phpstan-ignore-next-line
102 public function __construct(
103 $config,
104 $name,
105 $action,
106 array $arguments = []
107 ) {
108 if (isset($config['initial_delay'])) {
109 if ($config['initial_delay'] < 0) {
110 throw new GoogleTaskException(
111 'Task configuration `initial_delay` must not be negative.'
112 );
113 }
114
115 $this->delay = $config['initial_delay'];
116 }
117
118 if (isset($config['max_delay'])) {
119 if ($config['max_delay'] <= 0) {
120 throw new GoogleTaskException(
121 'Task configuration `max_delay` must be greater than 0.'
122 );
123 }
124
125 $this->maxDelay = $config['max_delay'];
126 }
127
128 if (isset($config['factor'])) {
129 if ($config['factor'] <= 0) {
130 throw new GoogleTaskException(
131 'Task configuration `factor` must be greater than 0.'
132 );
133 }
134
135 $this->factor = $config['factor'];
136 }
137
138 if (isset($config['jitter'])) {
139 if ($config['jitter'] <= 0) {
140 throw new GoogleTaskException(
141 'Task configuration `jitter` must be greater than 0.'
142 );
143 }
144
145 $this->jitter = $config['jitter'];
146 }
147
148 if (isset($config['retries'])) {
149 if ($config['retries'] < 0) {
150 throw new GoogleTaskException(
151 'Task configuration `retries` must not be negative.'
152 );
153 }
154 $this->maxAttempts += $config['retries'];
155 }
156
157 if (!is_callable($action)) {
158 throw new GoogleTaskException(
159 'Task argument `$action` must be a valid callable.'
160 );
161 }
162
163 $this->action = $action;
164 $this->arguments = $arguments;
165 }
166
167 /**
168 * Checks if a retry can be attempted.
169 *
170 * @return boolean
171 */
172 public function canAttempt()
173 {
174 return $this->attempts < $this->maxAttempts;
175 }
176
177 /**
178 * Runs the task and (if applicable) automatically retries when errors occur.
179 *
180 * @return mixed
181 * @throws \Google\Service\Exception on failure when no retries are available.
182 */
183 public function run()
184 {
185 while ($this->attempt()) {
186 try {
187 return call_user_func_array($this->action, $this->arguments);
188 } catch (GoogleServiceException $exception) {
189 $allowedRetries = $this->allowedRetries(
190 $exception->getCode(),
191 $exception->getErrors()
192 );
193
194 if (!$this->canAttempt() || !$allowedRetries) {
195 throw $exception;
196 }
197
198 if ($allowedRetries > 0) {
199 $this->maxAttempts = min(
200 $this->maxAttempts,
201 $this->attempts + $allowedRetries
202 );
203 }
204 }
205 }
206 }
207
208 /**
209 * Runs a task once, if possible. This is useful for bypassing the `run()`
210 * loop.
211 *
212 * NOTE: If this is not the first attempt, this function will sleep in
213 * accordance to the backoff configurations before running the task.
214 *
215 * @return boolean
216 */
217 public function attempt()
218 {
219 if (!$this->canAttempt()) {
220 return false;
221 }
222
223 if ($this->attempts > 0) {
224 $this->backOff();
225 }
226
227 $this->attempts++;
228
229 return true;
230 }
231
232 /**
233 * Sleeps in accordance to the backoff configurations.
234 */
235 private function backOff()
236 {
237 $delay = $this->getDelay();
238
239 usleep((int) ($delay * 1000000));
240 }
241
242 /**
243 * Gets the delay (in seconds) for the current backoff period.
244 *
245 * @return int
246 */
247 private function getDelay()
248 {
249 $jitter = $this->getJitter();
250 $factor = $this->attempts > 1 ? $this->factor + $jitter : 1 + abs($jitter);
251
252 return $this->delay = min($this->maxDelay, $this->delay * $factor);
253 }
254
255 /**
256 * Gets the current jitter (random number between -$this->jitter and
257 * $this->jitter).
258 *
259 * @return float
260 */
261 private function getJitter()
262 {
263 return $this->jitter * 2 * mt_rand() / mt_getrandmax() - $this->jitter;
264 }
265
266 /**
267 * Gets the number of times the associated task can be retried.
268 *
269 * NOTE: -1 is returned if the task can be retried indefinitely
270 *
271 * @return integer
272 */
273 public function allowedRetries($code, $errors = [])
274 {
275 if (isset($this->retryMap[$code])) {
276 return $this->retryMap[$code];
277 }
278
279 if (
280 !empty($errors) &&
281 isset($errors[0]['reason'], $this->retryMap[$errors[0]['reason']])
282 ) {
283 return $this->retryMap[$errors[0]['reason']];
284 }
285
286 return 0;
287 }
288
289 public function setRetryMap($retryMap)
290 {
291 $this->retryMap = $retryMap;
292 }
293}
Note: See TracBrowser for help on using the repository browser.