Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
if (!class_exists('Google_Client')) {
19
  require_once dirname(__FILE__) . '/../autoload.php';
20
}
21
 
22
/**
23
 * A task runner with exponential backoff support.
24
 *
25
 * @see https://developers.google.com/drive/web/handle-errors#implementing_exponential_backoff
26
 */
27
#[AllowDynamicProperties]
28
class Google_Task_Runner
29
{
30
  /**
31
   * @var integer $maxDelay The max time (in seconds) to wait before a retry.
32
   */
33
  private $maxDelay = 60;
34
  /**
35
   * @var integer $delay The previous delay from which the next is calculated.
36
   */
37
  private $delay = 1;
38
 
39
  /**
40
   * @var integer $factor The base number for the exponential back off.
41
   */
42
  private $factor = 2;
43
  /**
44
   * @var float $jitter A random number between -$jitter and $jitter will be
45
   * added to $factor on each iteration to allow for a better distribution of
46
   * retries.
47
   */
48
  private $jitter = 0.5;
49
 
50
  /**
51
   * @var integer $attempts The number of attempts that have been tried so far.
52
   */
53
  private $attempts = 0;
54
  /**
55
   * @var integer $maxAttempts The max number of attempts allowed.
56
   */
57
  private $maxAttempts = 1;
58
 
59
  /**
60
   * @var Google_Client $client The current API client.
61
   */
62
  private $client;
63
 
64
  /**
65
   * @var string $name The name of the current task (used for logging).
66
   */
67
  private $name;
68
  /**
69
   * @var callable $action The task to run and possibly retry.
70
   */
71
  private $action;
72
  /**
73
   * @var array $arguments The task arguments.
74
   */
75
  private $arguments;
76
 
77
  /**
78
   * Creates a new task runner with exponential backoff support.
79
   *
80
   * @param Google_Client $client The current API client
81
   * @param string $name The name of the current task (used for logging)
82
   * @param callable $action The task to run and possibly retry
83
   * @param array $arguments The task arguments
84
   * @throws Google_Task_Exception when misconfigured
85
   */
86
  public function __construct(
87
      Google_Client $client,
88
      $name,
89
      $action,
90
      array $arguments = array()
91
  ) {
92
    $config = (array) $client->getClassConfig('Google_Task_Runner');
93
 
94
    if (isset($config['initial_delay'])) {
95
      if ($config['initial_delay'] < 0) {
96
        throw new Google_Task_Exception(
97
            'Task configuration `initial_delay` must not be negative.'
98
        );
99
      }
100
 
101
      $this->delay = $config['initial_delay'];
102
    }
103
 
104
    if (isset($config['max_delay'])) {
105
      if ($config['max_delay'] <= 0) {
106
        throw new Google_Task_Exception(
107
            'Task configuration `max_delay` must be greater than 0.'
108
        );
109
      }
110
 
111
      $this->maxDelay = $config['max_delay'];
112
    }
113
 
114
    if (isset($config['factor'])) {
115
      if ($config['factor'] <= 0) {
116
        throw new Google_Task_Exception(
117
            'Task configuration `factor` must be greater than 0.'
118
        );
119
      }
120
 
121
      $this->factor = $config['factor'];
122
    }
123
 
124
    if (isset($config['jitter'])) {
125
      if ($config['jitter'] <= 0) {
126
        throw new Google_Task_Exception(
127
            'Task configuration `jitter` must be greater than 0.'
128
        );
129
      }
130
 
131
      $this->jitter = $config['jitter'];
132
    }
133
 
134
    if (isset($config['retries'])) {
135
      if ($config['retries'] < 0) {
136
        throw new Google_Task_Exception(
137
            'Task configuration `retries` must not be negative.'
138
        );
139
      }
140
      $this->maxAttempts += $config['retries'];
141
    }
142
 
143
    if (!is_callable($action)) {
144
        throw new Google_Task_Exception(
145
            'Task argument `$action` must be a valid callable.'
146
        );
147
    }
148
 
149
    $this->name = $name;
150
    $this->client = $client;
151
    $this->action = $action;
152
    $this->arguments = $arguments;
153
  }
154
 
155
  /**
156
   * Checks if a retry can be attempted.
157
   *
158
   * @return boolean
159
   */
160
  public function canAttmpt()
161
  {
162
    return $this->attempts < $this->maxAttempts;
163
  }
164
 
165
  /**
166
   * Runs the task and (if applicable) automatically retries when errors occur.
167
   *
168
   * @return mixed
169
   * @throws Google_Task_Retryable on failure when no retries are available.
170
   */
171
  public function run()
172
  {
173
    while ($this->attempt()) {
174
      try {
175
        return call_user_func_array($this->action, $this->arguments);
176
      } catch (Google_Task_Retryable $exception) {
177
        $allowedRetries = $exception->allowedRetries();
178
 
179
        if (!$this->canAttmpt() || !$allowedRetries) {
180
          throw $exception;
181
        }
182
 
183
        if ($allowedRetries > 0) {
184
          $this->maxAttempts = min(
185
              $this->maxAttempts,
186
              $this->attempts + $allowedRetries
187
          );
188
        }
189
      }
190
    }
191
  }
192
 
193
  /**
194
   * Runs a task once, if possible. This is useful for bypassing the `run()`
195
   * loop.
196
   *
197
   * NOTE: If this is not the first attempt, this function will sleep in
198
   * accordance to the backoff configurations before running the task.
199
   *
200
   * @return boolean
201
   */
202
  public function attempt()
203
  {
204
    if (!$this->canAttmpt()) {
205
      return false;
206
    }
207
 
208
    if ($this->attempts > 0) {
209
      $this->backOff();
210
    }
211
 
212
    $this->attempts++;
213
    return true;
214
  }
215
 
216
  /**
217
   * Sleeps in accordance to the backoff configurations.
218
   */
219
  private function backOff()
220
  {
221
    $delay = $this->getDelay();
222
 
223
    $this->client->getLogger()->debug(
224
        'Retrying task with backoff',
225
        array(
226
            'request' => $this->name,
227
            'retry' => $this->attempts,
228
            'backoff_seconds' => $delay
229
        )
230
    );
231
 
232
    usleep($delay * 1000000);
233
  }
234
 
235
  /**
236
   * Gets the delay (in seconds) for the current backoff period.
237
   *
238
   * @return float
239
   */
240
  private function getDelay()
241
  {
242
    $jitter = $this->getJitter();
243
    $factor = $this->attempts > 1 ? $this->factor + $jitter : 1 + abs($jitter);
244
 
245
    return $this->delay = min($this->maxDelay, $this->delay * $factor);
246
  }
247
 
248
  /**
249
   * Gets the current jitter (random number between -$this->jitter and
250
   * $this->jitter).
251
   *
252
   * @return float
253
   */
254
  private function getJitter()
255
  {
256
    return $this->jitter * 2 * mt_rand() / mt_getrandmax() - $this->jitter;
257
  }
258
}