Completed
Push — master ( 0183a2...e56bcb )
by Eugene
09:09
created

RetryMiddleware::custom()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
rs 10
1
<?php
2
3
/**
4
 * This file is part of the tarantool/client package.
5
 *
6
 * (c) Eugene Leonovich <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Tarantool\Client\Middleware;
15
16
use Tarantool\Client\Exception\ClientException;
17
use Tarantool\Client\Exception\CommunicationFailed;
18
use Tarantool\Client\Exception\ConnectionFailed;
19
use Tarantool\Client\Exception\UnexpectedResponse;
20
use Tarantool\Client\Handler\Handler;
21
use Tarantool\Client\Request\Request;
22
use Tarantool\Client\Response;
23
24
final class RetryMiddleware implements Middleware
25
{
26
    private const DEFAULT_MAX_RETRIES = 2;
27
    private const MAX_RETRIES_LIMIT = 10;
28
    private const MAX_DELAY_MS = 60000;
29
30
    /** @var \Closure */
31
    private $getDelayMs;
32
33
    /**
34
     * @param \Closure $getDelayMs
35
     */
36 18
    private function __construct($getDelayMs)
37
    {
38 18
        $this->getDelayMs = $getDelayMs;
39 18
    }
40
41
    public static function constant(int $maxRetries = self::DEFAULT_MAX_RETRIES, int $intervalMs = 100) : self
42
    {
43
        return new self(static function (int $retries) use ($maxRetries, $intervalMs) {
44
            return $retries > $maxRetries ? null : $intervalMs;
45
        });
46
    }
47
48
    public static function exponential(int $maxRetries = self::DEFAULT_MAX_RETRIES, int $baseMs = 10) : self
49
    {
50
        return new self(static function (int $retries) use ($maxRetries, $baseMs) {
51
            return $retries > $maxRetries ? null : $baseMs ** $retries;
52
        });
53
    }
54
55 9
    public static function linear(int $maxRetries = self::DEFAULT_MAX_RETRIES, int $differenceMs = 100) : self
56
    {
57 9
        return new self(static function (int $retries) use ($maxRetries, $differenceMs) {
58 6
            return $retries > $maxRetries ? null : $differenceMs * $retries;
59 9
        });
60
    }
61
62 9
    public static function custom(\Closure $getDelayMs) : self
63
    {
64 9
        return new self(static function (int $retries, \Throwable $e) use ($getDelayMs) : ?int {
65 9
            return $getDelayMs($retries, $e);
66 9
        });
67
    }
68
69 18
    public function process(Request $request, Handler $handler) : Response
70
    {
71 18
        $retries = 0;
72
73
        do {
74
            try {
75 18
                return $handler->handle($request);
76 18
            } catch (UnexpectedResponse $e) {
77 3
                $handler->getConnection()->close();
78 3
                break;
79 15
            } catch (ConnectionFailed | CommunicationFailed $e) {
80 9
                $handler->getConnection()->close();
81 9
                goto retry;
82 6
            } catch (ClientException $e) {
83
                retry:
84 15
                if (self::MAX_RETRIES_LIMIT === $retries) {
85 3
                    break;
86
                }
87 15
                if (null === $delayMs = ($this->getDelayMs)(++$retries, $e)) {
88 6
                    break;
89
                }
90 15
                $delayMs = \min($delayMs, self::MAX_DELAY_MS) / 2;
91 15
                $delayMs += \mt_rand(0, $delayMs);
92 15
                \usleep($delayMs * 1000);
93
            }
94 15
        } while (true);
95
96 12
        throw $e;
97
    }
98
}
99