-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #425: Add RpcRetryOption and use longer retry inte…
…rval on RESOURCE_EXHAUSTED
- Loading branch information
Showing
13 changed files
with
415 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Temporal\Client\Common; | ||
|
||
/** | ||
* Used to throttle code execution in presence of failures using exponential backoff logic. | ||
* | ||
* The formula used to calculate the next sleep interval is: | ||
* | ||
* ``` | ||
* jitter = rand(-maxJitterCoefficient, +maxJitterCoefficient) | ||
* wait = min(pow(backoffCoefficient, failureCount - 1) * initialInterval * (1 + jitter), maxInterval) | ||
* ``` | ||
* | ||
* Note | ||
* `initialInterval` may be changed in runtime depending on the failure type. | ||
* That it means that attempt X can possibly get a shorter throttle than attempt X-1. | ||
* | ||
* Example: | ||
* | ||
* ```php | ||
* $throttler = new BackoffThrottler(maxInterval: 60_000, 0.1, 2.0); | ||
* | ||
* // First retry | ||
* // There 1000 is initial interval for the RESOURCE_EXHAUSTED exception | ||
* $throttler->calculateSleepTime(failureCount: 1, baseInterval: 1000); | ||
* | ||
* // Second retry | ||
* // There 500 is a common initial interval for all other exceptions | ||
* $throttler->calculateSleepTime(failureCount: 2, baseInterval: 500); | ||
* ``` | ||
* | ||
* @internal | ||
*/ | ||
final class BackoffThrottler | ||
{ | ||
/** | ||
* @param int $maxInterval Maximum sleep interval in milliseconds. Must be greater than 0. | ||
* @param float $maxJitterCoefficient Maximum jitter to apply. Must be in the range [0.0, 1.0). | ||
* 0.1 means that actual retry time can be +/- 10% of the calculated time. | ||
* @param float $backoffCoefficient Coefficient used to calculate the next retry backoff interval. | ||
* The next retry interval is previous interval multiplied by this coefficient. | ||
* Must be greater than 1.0. | ||
*/ | ||
public function __construct( | ||
private readonly int $maxInterval, | ||
private readonly float $maxJitterCoefficient, | ||
private readonly float $backoffCoefficient, | ||
) { | ||
$maxJitterCoefficient >= 0 && $maxJitterCoefficient < 1 or throw new \InvalidArgumentException( | ||
'$jitterCoefficient must be in the range [0.0, 1.0).', | ||
); | ||
$this->maxInterval > 0 or throw new \InvalidArgumentException('$maxInterval must be greater than 0.'); | ||
$this->backoffCoefficient >= 1.0 or throw new \InvalidArgumentException( | ||
'$backoffCoefficient must be greater than 1.', | ||
); | ||
} | ||
|
||
/** | ||
* Calculates the next sleep interval in milliseconds. | ||
* | ||
* @param int $failureCount number of failures | ||
* @param int $initialInterval in milliseconds | ||
* | ||
* @return int<0, max> | ||
* | ||
* @psalm-assert int<1, max> $failureCount | ||
* @psalm-assert int<1, max> $initialInterval | ||
* | ||
* @psalm-suppress InvalidOperand | ||
*/ | ||
public function calculateSleepTime(int $failureCount, int $initialInterval): int | ||
{ | ||
$failureCount > 0 or throw new \InvalidArgumentException('$failureCount must be greater than 0.'); | ||
$initialInterval > 0 or throw new \InvalidArgumentException('$initialInterval must be greater than 0.'); | ||
|
||
// Choose a random number in the range -maxJitterCoefficient ... +maxJitterCoefficient | ||
$jitter = \random_int(-1000, 1000) * $this->maxJitterCoefficient / 1000; | ||
$sleepTime = \min( | ||
\pow($this->backoffCoefficient, $failureCount - 1) * $initialInterval * (1.0 + $jitter), | ||
$this->maxInterval, | ||
); | ||
|
||
return \abs((int)$sleepTime); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Temporal\Client\Common; | ||
|
||
use JetBrains\PhpStorm\Pure; | ||
use Temporal\Common\RetryOptions; | ||
use Temporal\Internal\Support\DateInterval; | ||
|
||
/** | ||
* @psalm-import-type DateIntervalValue from DateInterval | ||
* @psalm-immutable | ||
*/ | ||
final class RpcRetryOptions extends RetryOptions | ||
{ | ||
/** | ||
* Interval of the first retry, on congestion related failures (i.e. RESOURCE_EXHAUSTED errors). | ||
* | ||
* If coefficient is 1.0 then it is used for all retries. Defaults to 1000ms. | ||
*/ | ||
public ?\DateInterval $congestionInitialInterval = null; | ||
|
||
/** | ||
* Maximum amount of jitter to apply. | ||
* Must be lower than 1. | ||
* | ||
* 0.1 means that actual retry time can be +/- 10% of the calculated time. | ||
*/ | ||
public float $maximumJitterCoefficient = 0.1; | ||
|
||
/** | ||
* Converts {@see RetryOptions} to {@see RpcRetryOptions}. | ||
* | ||
* @internal | ||
*/ | ||
public static function fromRetryOptions(RetryOptions $options): self | ||
{ | ||
return $options instanceof self ? $options : (new self()) | ||
->withInitialInterval($options->initialInterval) | ||
->withBackoffCoefficient($options->backoffCoefficient) | ||
->withMaximumInterval($options->maximumInterval) | ||
->withMaximumAttempts($options->maximumAttempts) | ||
->withNonRetryableExceptions($options->nonRetryableExceptions); | ||
} | ||
|
||
/** | ||
* Interval of the first retry, on congestion related failures (i.e. RESOURCE_EXHAUSTED errors). | ||
* If coefficient is 1.0 then it is used for all retries. Defaults to 1000ms. | ||
* | ||
* @param DateIntervalValue|null $interval Interval to wait on first retry, on congestion failures. | ||
* Defaults to 1000ms, which is used if set to {@see null}. | ||
* | ||
* @return self | ||
* | ||
* @psalm-suppress ImpureMethodCall | ||
*/ | ||
#[Pure] | ||
public function withCongestionInitialInterval($interval): self | ||
{ | ||
$interval === null || DateInterval::assert($interval) or throw new \InvalidArgumentException( | ||
'Invalid interval value.' | ||
); | ||
|
||
$self = clone $this; | ||
$self->congestionInitialInterval = DateInterval::parseOrNull($interval, DateInterval::FORMAT_SECONDS); | ||
return $self; | ||
} | ||
|
||
/** | ||
* Maximum amount of jitter to apply. | ||
* | ||
* 0.2 means that actual retry time can be +/- 20% of the calculated time. | ||
* Set to 0 to disable jitter. Must be lower than 1. | ||
* | ||
* @param null|float $coefficient Maximum amount of jitter. | ||
* Default will be used if set to {@see null}. | ||
* | ||
* @return self | ||
*/ | ||
#[Pure] | ||
public function withMaximumJitterCoefficient(?float $coefficient): self | ||
{ | ||
$coefficient === null || ($coefficient >= 0.0 && $coefficient < 1.0) or throw new \InvalidArgumentException( | ||
'Maximum jitter coefficient must be in range [0, 1).' | ||
); | ||
|
||
$self = clone $this; | ||
$self->maximumJitterCoefficient = $coefficient ?? 0.1; | ||
return $self; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.