Skip to content

Commit 48b77de

Browse files
committed
Change logic in determineDelayTimeout and shouldRetryHttpResponse to fix issue #36
1 parent 22cac8a commit 48b77de

File tree

2 files changed

+49
-15
lines changed

2 files changed

+49
-15
lines changed

src/GuzzleRetryMiddleware.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,16 @@ protected function shouldRetryHttpResponse(
280280
$statuses = array_map('\intval', (array) $options['retry_on_status']);
281281
$hasRetryAfterHeader = $response && $response->hasHeader('Retry-After');
282282

283+
// Abort if next request time would be greater than the time
284+
if ((int) $options['give_up_after_secs'] > 0) {
285+
$delayTimeout = ($this->determineDelayTimeout($options, $response));
286+
$nextRequestTime = $options['first_request_timestamp'] + $delayTimeout;
287+
$giveUpAfterTime = $options['first_request_timestamp'] + $options['give_up_after_secs'];
288+
if ($nextRequestTime > $giveUpAfterTime) {
289+
return false;
290+
}
291+
}
292+
283293
switch (true) {
284294
case $options['retry_enabled'] === false:
285295
case $this->hasTimeAvailable($options) === false:
@@ -385,6 +395,8 @@ protected function returnResponse(array $options, ResponseInterface $response):
385395
*/
386396
protected function determineDelayTimeout(array $options, ?ResponseInterface $response = null): float
387397
{
398+
$timeoutDerivedFromServer = false;
399+
388400
// If 'default_retry_multiplier' option is a callable, call it to determine the default timeout...
389401
if (is_callable($options['default_retry_multiplier'])) {
390402
$defaultDelayTimeout = (float) call_user_func(
@@ -402,7 +414,12 @@ protected function determineDelayTimeout(array $options, ?ResponseInterface $res
402414
$timeout = $this->deriveTimeoutFromHeader(
403415
$response->getHeader($options['retry_after_header'])[0],
404416
$options['retry_after_date_format']
405-
) ?? $defaultDelayTimeout;
417+
);
418+
if ($timeout !== null) {
419+
$timeoutDerivedFromServer = true;
420+
} else {
421+
$timeout = $defaultDelayTimeout;
422+
}
406423
} else {
407424
$timeout = abs($defaultDelayTimeout);
408425
}
@@ -415,7 +432,7 @@ protected function determineDelayTimeout(array $options, ?ResponseInterface $res
415432
}
416433

417434
// If 'give_up_after_secs' is set, account for it in determining the timeout
418-
if ($options['give_up_after_secs']) {
435+
if ($options['give_up_after_secs'] && ! $timeoutDerivedFromServer) {
419436
$giveUpAfterSecs = abs((float) $options['give_up_after_secs']);
420437
$timeSinceFirstReq = $options['request_timestamp'] - $options['first_request_timestamp'];
421438
$timeout = min($timeout, ($giveUpAfterSecs - $timeSinceFirstReq));
@@ -450,8 +467,7 @@ protected function hasTimeAvailable(array $options): bool
450467
*/
451468
protected function deriveTimeoutFromHeader(string $headerValue, string $dateFormat = self::DATE_FORMAT): ?float
452469
{
453-
// The timeout will either be a number or a HTTP-formatted date,
454-
// or seconds (integer)
470+
// The timeout will either be a number of seconds (int) or an HTTP-formatted date
455471
if (is_numeric($headerValue)) {
456472
return (float) trim($headerValue);
457473
} elseif ($date = DateTime::createFromFormat($dateFormat ?: self::DATE_FORMAT, trim($headerValue))) {

tests/GuzzleRetryMiddlewareTest.php

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use GuzzleHttp\Exception\BadResponseException;
2626
use GuzzleHttp\Exception\ClientException;
2727
use GuzzleHttp\Exception\ConnectException;
28+
use GuzzleHttp\Exception\GuzzleException;
2829
use GuzzleHttp\Exception\ServerException;
2930
use GuzzleHttp\Exception\TransferException;
3031
use GuzzleHttp\Handler\MockHandler;
@@ -58,6 +59,7 @@ public function testInstantiation(): void
5859
* @dataProvider providerForRetryOccursWhenStatusCodeMatches
5960
* @param Response $response
6061
* @param bool $retryShouldOccur
62+
* @throws GuzzleException
6163
*/
6264
public function testRetryOccursWhenStatusCodeMatches(Response $response, bool $retryShouldOccur): void
6365
{
@@ -101,6 +103,7 @@ public function providerForRetryOccursWhenStatusCodeMatches(): array
101103
*
102104
* @dataProvider retriesFailAfterSpecifiedLimitProvider
103105
* @param array<int,Response> $responses
106+
* @throws GuzzleException
104107
*/
105108
public function testRetriesFailAfterSpecifiedLimit(array $responses): void
106109
{
@@ -529,7 +532,7 @@ public function testDelayTakesIntoAccountGiveUpAfterTime(): void
529532
$client->request('GET', '/');
530533
$this->fail('Should have timed out');
531534
} catch (ClientException $e) {
532-
$this->assertEquals([3.0, 7.0], $delayTimes);
535+
$this->assertEquals([3.0, 10.0], $delayTimes);
533536
}
534537
}
535538

@@ -762,24 +765,39 @@ public function testGiveUpAfterSecsFailsWhenTimeLimitExceeded(): void
762765
$client->request('GET', '/');
763766
}
764767

765-
public function testGiveUpAfterSecsWithLongerTimes(): void
768+
public function testRespectGiveUpAfterSecs(): void
766769
{
767770
$responses = [
768-
new Response(429, [], 'Wait 1'),
769-
new Response(429, [], 'Wait 2'),
770-
new Response(503, [], 'Wait 3'),
771-
new Response(429, [], 'Wait 4'),
771+
new Response(503, ['Retry-After' => '1'], 'Resp 1'),
772+
new Response(503, ['Retry-After' => '1'], 'Resp 2'),
773+
new Response(503, ['Retry-After' => '1'], 'Resp 3'),
774+
new Response(503, ['Retry-After' => '360'], 'Resp 4'),
775+
new Response(503, ['Retry-After' => '10'], 'Resp 5'),
772776
new Response(200, [], 'Good')
773777
];
774778

779+
$numRetries = 0;
780+
775781
$stack = HandlerStack::create(new MockHandler($responses));
776782
$stack->push(GuzzleRetryMiddleware::factory([
777-
'default_retry_multiplier' => 1.5,
778-
'give_up_after_secs' => 1
783+
'default_retry_multiplier' => 2,
784+
'give_up_after_secs' => 5,
785+
'on_retry_callback' => function ($retryCount) use (&$numRetries) {
786+
$numRetries = $retryCount;
787+
}
779788
]));
780789

781-
$client = new Client(['handler' => $stack]);
782-
$client->request('GET', '/');
790+
$respBody = null;
791+
792+
try {
793+
$client = new Client(['handler' => $stack]);
794+
$client->request('GET', '/');
795+
} catch (ServerException $e) {
796+
$respBody = $e->getResponse()->getBody()->getContents();
797+
}
798+
799+
$this->assertEquals(3, $numRetries);
800+
$this->assertEquals('Resp 4', $respBody);
783801
}
784802

785803
public function testGiveUpAfterSecsSucceedsWhenTimeLimitNotExceeded(): void
@@ -896,7 +914,7 @@ public function testRetryCallbackReferenceModification(): void
896914
$client->request('GET', '/');
897915

898916
$this->assertEquals('GoodHeader', $testRequest->getHeaderLine('TestHeader'));
899-
$this->assertArrayHasKey('TestOption', $testOptions);
917+
$this->assertArrayHasKey('TestOption', $testOptions ?? []);
900918
$this->assertEquals('GoodOption', $testOptions['TestOption']);
901919
}
902920

0 commit comments

Comments
 (0)