Skip to content

Commit

Permalink
* Revert static default values (deprecated!)
Browse files Browse the repository at this point in the history
 * Added method `setJitterPercent()`
  • Loading branch information
SmetDenis committed Mar 19, 2021
1 parent cca9a00 commit 4c2b43f
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 17 deletions.
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,27 @@ $result = (new Retry())
});
```

You might want to do this somewhere in your application bootstrap for example. These defaults will be used anytime you create an instance of the Retry class or use the `retry()` helper function.
## Changing defaults

**Important Note:** It's a fork. So I left it here just for backward compatibility. Static variables are deprecated and don't work at all!

This is terrible practice! Explicit is better than implicit. ;)

* Example #1. Different parts of your project can have completely different settings.
* Example #2. Imagine what would happen if some third3-party library (in `./vendor`) uses its own default settings. Let's fight!
* Example #3. It's just an attempt to store variables in a global namespace. Do you see it?


So the next variables are deprecated, and they don't influence anything.
```php
use JBZoo\Retry\Retry;

Retry::$defaultMaxAttempts;
Retry::$defaultStrategy;
Retry::$defaultJitterEnabled;
```

Just use dependencies injection or so and don't warm your head.

## Strategies

Expand Down Expand Up @@ -205,11 +225,13 @@ If you have a lot of clients starting a job at the same time and encountering fa

The solution for this is to add randomness. See here for a good explanation:

https://www.awsarchitectureblog.com/2015/03/retry.html
https://aws.amazon.com/ru/blogs/architecture/exponential-backoff-and-jitter

You can enable jitter by passing `true` in as the fifth argument to the `retry` helper function, or by using the `enableJitter()` method on the Retry class.

We use the "FullJitter" approach outlined in the above article, where a random number between 0 and the sleep time provided by your selected strategy is used.
By default, we use the "FullJitter" approach outlined in the above article, where a random number between 0 and the sleep time provided by your selected strategy is used.

But you can change the maximum time for Jitter with method `setJitterPercent(). By default it's 100.

## Custom retry decider

Expand Down
97 changes: 87 additions & 10 deletions src/Retry.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,38 @@
*/
class Retry
{
public const DEFAULT_MAX_ATTEMPTS = 5;
public const DEFAULT_STRATEGY = self::STRATEGY_POLYNOMIAL;
public const DEFAULT_JITTER_STATE = false;
// Fallback values and global defaults
public const DEFAULT_MAX_ATTEMPTS = 5;
public const DEFAULT_STRATEGY = self::STRATEGY_POLYNOMIAL;
public const DEFAULT_JITTER_STATE = false;
public const DEFAULT_JITTER_PERCENT = 100;

// Pre-defined strategies
public const STRATEGY_CONSTANT = 'constant';
public const STRATEGY_LINEAR = 'linear';
public const STRATEGY_POLYNOMIAL = 'polynomial';
public const STRATEGY_EXPONENTIAL = 'exponential';

/**
* @var int
* @deprecated See README.md "Changing defaults"
*/
public static $defaultMaxAttempts = self::DEFAULT_MAX_ATTEMPTS;

/**
* @var string
* @deprecated See README.md "Changing defaults"
*/
public static $defaultStrategy = self::DEFAULT_STRATEGY;

/**
* @var bool
* @deprecated See README.md "Changing defaults"
*/
public static $defaultJitterEnabled = self::DEFAULT_JITTER_STATE;

/**
* This callable should take an 'attempt' integer, and return a wait time in milliseconds
*
* @var callable
*/
protected $strategy;
Expand Down Expand Up @@ -73,6 +92,16 @@ class Retry
*/
protected $useJitter = false;

/**
* @var int
*/
protected $jitterPercent = self::DEFAULT_JITTER_PERCENT;

/**
* @var int
*/
protected $jitterMinTime = 0;

/**
* @var array|non-empty-array<int,\Exception>|non-empty-array<int,\Throwable>
*/
Expand Down Expand Up @@ -104,20 +133,20 @@ public function __construct(
?bool $useJitter = self::DEFAULT_JITTER_STATE,
?callable $decider = null
) {
$this->setMaxAttempts($maxAttempts ?: self::DEFAULT_MAX_ATTEMPTS);
$this->setStrategy($strategy ?? self::DEFAULT_STRATEGY);
$this->setMaxAttempts($maxAttempts);
$this->setStrategy($strategy ?: self::DEFAULT_STRATEGY);
$this->setJitter($useJitter ?? self::DEFAULT_JITTER_STATE);
$this->setWaitCap($waitCap);
$this->setDecider($decider ?? self::getDefaultDecider());
}

/**
* @param int $attempts
* @param int $maxAttempts
* @return $this
*/
public function setMaxAttempts(int $attempts): self
public function setMaxAttempts(int $maxAttempts): self
{
$this->maxAttempts = $attempts;
$this->maxAttempts = $maxAttempts > 0 ? $maxAttempts : self::DEFAULT_MAX_ATTEMPTS;
return $this;
}

Expand Down Expand Up @@ -159,6 +188,43 @@ public function setJitter(bool $useJitter): self
return $this;
}

/**
* @param int $jitterPercent
* @return $this
*/
public function setJitterPercent(int $jitterPercent): self
{
$this->jitterPercent = $jitterPercent;
return $this;
}

/**
* @return int
*/
public function getJitterPercent(): int
{
return $this->jitterPercent;
}


/**
* @param int $jitterMinTime
* @return $this
*/
public function setJitterMinCap(int $jitterMinTime): self
{
$this->jitterMinTime = $jitterMinTime < 0 ? 0 : $jitterMinTime;
return $this;
}

/**
* @return int
*/
public function getJitterMinCap(): int
{
return $this->jitterMinTime;
}

/**
* @return $this
*/
Expand Down Expand Up @@ -369,6 +435,17 @@ protected function cap(int $waitTime): int
*/
protected function jitter(int $waitTime): int
{
return $this->jitterEnabled() ? \random_int(0, $waitTime) : $waitTime;
if ($this->jitterEnabled()) {
$minValue = $this->jitterMinTime;
$maxValue = (int)($waitTime * $this->jitterPercent / 100);

if ($minValue > $maxValue) {
$minValue = $maxValue;
}

return \random_int($minValue, $maxValue);
}

return $waitTime;
}
}
13 changes: 12 additions & 1 deletion tests/RetryComposerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

namespace JBZoo\PHPUnit;

use function JBZoo\Data\json;

/**
* Class RetryComposerTest
* @package JBZoo\PHPUnit
Expand All @@ -25,6 +27,15 @@ class RetryComposerTest extends AbstractComposerTest
{
public function testAuthor(): void
{
skip("It's fork");
$composerPath = PROJECT_ROOT . '/composer.json';
$composerJson = json($composerPath);

if ($this->authorName) {
isSame($this->authorName, $composerJson->find('authors.1.name'), "See file: {$composerPath}");
}

if ($this->authorEmail) {
isSame($this->authorEmail, $composerJson->find('authors.1.email'), "See file: {$composerPath}");
}
}
}
75 changes: 72 additions & 3 deletions tests/RetryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public function testDefaults()
isSame(5, $retry->getMaxAttempts());
self::assertInstanceOf(PolynomialStrategy::class, $retry->getStrategy());
isFalse($retry->jitterEnabled());

$retry->setMaxAttempts(0);
isSame(5, $retry->getMaxAttempts());
}

public function testFluidApi()
Expand All @@ -54,28 +57,56 @@ public function testFluidApi()
self::assertInstanceOf(ConstantStrategy::class, $retry->getStrategy());
}

public function testNotChangingStaticDefaults()
{
Retry::$defaultMaxAttempts = 15;
Retry::$defaultStrategy = "constant";
Retry::$defaultJitterEnabled = true;

$retry = new Retry();

isSame(Retry::DEFAULT_MAX_ATTEMPTS, $retry->getMaxAttempts());
self::assertInstanceOf(PolynomialStrategy::class, $retry->getStrategy());
isSame(Retry::DEFAULT_JITTER_STATE, $retry->jitterEnabled());

Retry::$defaultStrategy = new LinearStrategy(250);

$retry = new Retry();

self::assertInstanceOf(PolynomialStrategy::class, $retry->getStrategy());

// I don't care about put them back. They dont' work at all and deprecated.
// Retry::$defaultMaxAttempts = 5;
// Retry::$defaultStrategy = "polynomial";
// Retry::$defaultJitterEnabled = false;
}

public function testConstructorParams()
{
$b = new Retry(10, "linear");
$retry = new Retry(10, "linear");

isSame(10, $b->getMaxAttempts());
self::assertInstanceOf(LinearStrategy::class, $b->getStrategy());
isSame(10, $retry->getMaxAttempts());
self::assertInstanceOf(LinearStrategy::class, $retry->getStrategy());
}

public function testStrategyKeys()
{
$retry = new Retry();

$retry->setStrategy("constant");
$retry->setStrategy(Retry::STRATEGY_CONSTANT);
self::assertInstanceOf(ConstantStrategy::class, $retry->getStrategy());

$retry->setStrategy("linear");
$retry->setStrategy(Retry::STRATEGY_LINEAR);
self::assertInstanceOf(LinearStrategy::class, $retry->getStrategy());

$retry->setStrategy("polynomial");
$retry->setStrategy(Retry::STRATEGY_POLYNOMIAL);
self::assertInstanceOf(PolynomialStrategy::class, $retry->getStrategy());

$retry->setStrategy("exponential");
$retry->setStrategy(Retry::STRATEGY_EXPONENTIAL);
self::assertInstanceOf(ExponentialStrategy::class, $retry->getStrategy());
}

Expand Down Expand Up @@ -393,4 +424,42 @@ public function testWaitingTimeWithJitter()
$retry->getWaitTime(5)
]);
}

public function testJitterPercent()
{
$originalPeriod = 100;
$retry = (new Retry(1, $originalPeriod))->enableJitter();

// Check default
isSame(100, $retry->getJitterPercent());
isSame(0, $retry->getJitterMinCap());


// Check min jitter period
$retry->setJitterMinCap(-1);
isSame(0, $retry->getJitterMinCap());
$retry->setJitterMinCap(1);
isSame(1, $retry->getJitterMinCap());
$retry->setJitterMinCap(1000000);
isSame(100, $retry->getWaitTime(1));


// If jitter = 1%
$retry->setJitterPercent(1);
$retry->setJitterMinCap(1);
isSame(1, $retry->getJitterPercent());
isSame(1, $retry->getWaitTime(1));


// If jitter = 1,000,000%
$retry->setJitterPercent(1000000);
isSame(1000000, $retry->getJitterPercent());
isTrue($retry->getWaitTime(1) > $originalPeriod);


// Revert to default
$retry->setJitterPercent(Retry::DEFAULT_JITTER_PERCENT);
isSame(100, $retry->getJitterPercent());
isTrue($retry->getWaitTime(1) <= $originalPeriod);
}
}

0 comments on commit 4c2b43f

Please sign in to comment.