Skip to content

Commit f4950da

Browse files
committed
Implemented Exponential Backoff for RateExceededError
1 parent b08bc64 commit f4950da

File tree

3 files changed

+106
-16
lines changed

3 files changed

+106
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ All Notable changes to `Laravel AdWords Targeting Idea Service` will be document
55
## [1.3.4] - 2019-02-27
66
- Add compatibility with Laravel 5.8
77
- Using version 40.0 of googleads/googleads-php-lib
8+
- Implemented Exponential Backoff for RateExceededError
89

910
## [1.3.3] - 2018-10-30
1011
- Using version 37.1.0 of googleads/googleads-php-lib

src/AdWordsService.php

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,9 @@ public function performQuery(array $keywords, $requestType, $language = null, $l
5858
$selector->setPaging(new Paging(0, self::PAGE_LIMIT));
5959
$selector->setSearchParameters($this->getSearchParameters($keywords, $language, $location, $included, $excluded));
6060

61-
$currentTry = 0;
62-
63-
while (true) {
64-
try {
65-
$results = $this->targetingIdeaService->get($selector);
66-
67-
return $results;
68-
} catch (ApiException $exception) {
69-
$error = $exception->getErrors()[0];
70-
if ($error instanceof RateExceededError and ++$currentTry < self::MAX_RETRIES) {
71-
sleep($error->getRetryAfterSeconds());
72-
} else {
73-
throw $exception;
74-
}
75-
}
76-
}
61+
return (new ExponentialBackoff(10))->execute(function () use ($selector) {
62+
return $this->targetingIdeaService->get($selector);
63+
});
7764
}
7865

7966
/**

src/ExponentialBackoff.php

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace SchulzeFelix\AdWords;
4+
5+
/**
6+
* Exponential backoff implementation.
7+
*/
8+
class ExponentialBackoff
9+
{
10+
const MAX_DELAY_MICROSECONDS = 120000000;
11+
12+
/**
13+
* @var int
14+
*/
15+
private $retries;
16+
17+
/**
18+
* @var callable
19+
*/
20+
private $retryFunction;
21+
22+
/**
23+
* @var callable
24+
*/
25+
private $delayFunction;
26+
27+
/**
28+
* @param int $retries [optional] Number of retries for a failed request.
29+
* @param callable $retryFunction [optional] returns bool for whether or not to retry
30+
*/
31+
public function __construct($retries = null, callable $retryFunction = null)
32+
{
33+
$this->retries = $retries !== null ? (int) $retries : 3;
34+
$this->retryFunction = $retryFunction;
35+
$this->delayFunction = function ($delay) {
36+
usleep($delay);
37+
};
38+
}
39+
40+
/**
41+
* Executes the retry process.
42+
*
43+
* @param callable $function
44+
* @param array $arguments [optional]
45+
* @return mixed
46+
* @throws \Exception The last exception caught while retrying.
47+
*/
48+
public function execute(callable $function, array $arguments = [])
49+
{
50+
$delayFunction = $this->delayFunction;
51+
$retryAttempt = 0;
52+
$exception = null;
53+
54+
while (true) {
55+
try {
56+
return call_user_func_array($function, $arguments);
57+
} catch (\Exception $exception) {
58+
if ($this->retryFunction) {
59+
if (! call_user_func($this->retryFunction, $exception)) {
60+
throw $exception;
61+
}
62+
}
63+
64+
if ($exception->getCode() == 403) {
65+
break;
66+
}
67+
68+
if ($retryAttempt >= $this->retries) {
69+
break;
70+
}
71+
72+
$delayFunction($this->calculateDelay($retryAttempt));
73+
$retryAttempt++;
74+
}
75+
}
76+
77+
throw $exception;
78+
}
79+
80+
/**
81+
* @param callable $delayFunction
82+
* @return void
83+
*/
84+
public function setDelayFunction(callable $delayFunction)
85+
{
86+
$this->delayFunction = $delayFunction;
87+
}
88+
89+
/**
90+
* Calculates exponential delay.
91+
*
92+
* @param int $attempt
93+
* @return int
94+
*/
95+
private function calculateDelay($attempt)
96+
{
97+
return min(
98+
mt_rand(0, 1000000) + (pow(2, $attempt) * 1000000),
99+
self::MAX_DELAY_MICROSECONDS
100+
);
101+
}
102+
}

0 commit comments

Comments
 (0)