Skip to content

Commit 8a1b163

Browse files
authored
Merge pull request #304 from binaryfire/add-exponential-backoff
Feature | Add exponential backoff for retry interval
2 parents d84cda4 + 08248be commit 8a1b163

File tree

3 files changed

+36
-2
lines changed

3 files changed

+36
-2
lines changed

src/Traits/Connector/SendsRequests.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public function send(Request $request, MockClient $mockClient = null, callable $
3737
$maxTries = $request->tries ?? $this->tries ?? 1;
3838
$retryInterval = $request->retryInterval ?? $this->retryInterval ?? 0;
3939
$throwOnMaxTries = $request->throwOnMaxTries ?? $this->throwOnMaxTries ?? true;
40+
$useExponentialBackoff = $request->useExponentialBackoff ?? $this->useExponentialBackoff ?? false;
4041

4142
if ($maxTries <= 0) {
4243
$maxTries = 1;
@@ -53,7 +54,11 @@ public function send(Request $request, MockClient $mockClient = null, callable $
5354
// the interval (if it has been provided)
5455

5556
if ($attempts > 1) {
56-
usleep($retryInterval * 1000);
57+
$sleepTime = $useExponentialBackoff
58+
? $retryInterval * (2 ** ($attempts - 2)) * 1000
59+
: $retryInterval * 1000;
60+
61+
usleep($sleepTime);
5762
}
5863

5964
try {
@@ -151,11 +156,12 @@ public function sendAsync(Request $request, MockClient $mockClient = null): Prom
151156
*
152157
* @param callable(\Throwable, \Saloon\Http\Request): (bool)|null $handleRetry
153158
*/
154-
public function sendAndRetry(Request $request, int $tries, int $interval = 0, callable $handleRetry = null, bool $throw = true, MockClient $mockClient = null): Response
159+
public function sendAndRetry(Request $request, int $tries, int $interval = 0, callable $handleRetry = null, bool $throw = true, MockClient $mockClient = null, bool $useExponentialBackoff = false): Response
155160
{
156161
$request->tries = $tries;
157162
$request->retryInterval = $interval;
158163
$request->throwOnMaxTries = $throw;
164+
$request->useExponentialBackoff = $useExponentialBackoff;
159165

160166
return $this->send($request, $mockClient, $handleRetry);
161167
}

src/Traits/RequestProperties/HasTries.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ trait HasTries
2626
*/
2727
public ?int $retryInterval = null;
2828

29+
/**
30+
* Should Saloon use exponential backoff during retries?
31+
*
32+
* When true, Saloon will double the retry interval after each attempt.
33+
*/
34+
public bool $useExponentialBackoff = false;
35+
2936
/**
3037
* Should Saloon throw an exception after exhausting the maximum number of retries?
3138
*

tests/Feature/SendAndRetryTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,27 @@
108108
expect(round(microtime(true) - $start))->toBeGreaterThanOrEqual(2);
109109
});
110110

111+
test('a failed request can have an interval with exponential backoff between each attempt', function () {
112+
$mockClient = new MockClient([
113+
MockResponse::make(['name' => 'Sam'], 500), // 1,000
114+
MockResponse::make(['name' => 'Gareth'], 500), // 2,000
115+
MockResponse::make(['name' => 'Michael'], 500), // 4,000
116+
MockResponse::make(['name' => 'Teodor'], 200),
117+
]);
118+
119+
$connector = new TestConnector;
120+
$connector->withMockClient($mockClient);
121+
122+
$start = microtime(true);
123+
124+
$connector->sendAndRetry(new UserRequest, 4, 1000, useExponentialBackoff: true);
125+
126+
// It should be a duration of > 7000ms (7 seconds) because the there are four requests
127+
// after the first.
128+
129+
expect(round(microtime(true) - $start))->toBeGreaterThanOrEqual(7);
130+
});
131+
111132
test('an exception other than a request exception will not be retried', function () {
112133
$mockClient = new MockClient([
113134
MockResponse::make(['name' => 'Sam'], 500),

0 commit comments

Comments
 (0)