RetryMiddleware::process()   B
last analyzed

Complexity

Conditions 7
Paths 8

Size

Total Lines 28
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 7
eloc 21
c 4
b 0
f 0
nc 8
nop 2
dl 0
loc 28
rs 8.6506
ccs 19
cts 19
cp 1
crap 7
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 40
    private function __construct($getDelayMs)
37
    {
38 40
        $this->getDelayMs = $getDelayMs;
39
    }
40
41 4
    public static function constant(int $maxRetries = self::DEFAULT_MAX_RETRIES, int $intervalMs = 100) : self
42
    {
43 4
        return new self(static function (int $retries) use ($maxRetries, $intervalMs) {
44 4
            return $retries > $maxRetries ? null : $intervalMs;
45 4
        });
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 24
    public static function linear(int $maxRetries = self::DEFAULT_MAX_RETRIES, int $differenceMs = 100) : self
56
    {
57 24
        return new self(static function (int $retries) use ($maxRetries, $differenceMs) {
58 12
            return $retries > $maxRetries ? null : $differenceMs * $retries;
59 24
        });
60
    }
61
62 12
    public static function custom(\Closure $getDelayMs) : self
63
    {
64 12
        return new self(static function (int $retries, \Throwable $e) use ($getDelayMs) : ?int {
65 12
            return $getDelayMs($retries, $e);
66 12
        });
67
    }
68
69 32
    public function process(Request $request, Handler $handler) : Response
70
    {
71 32
        $retries = 0;
72
73
        do {
74
            try {
75 32
                return $handler->handle($request);
76 32
            } catch (UnexpectedResponse $e) {
77 4
                $handler->getConnection()->close();
78 4
                break;
79 28
            } catch (ConnectionFailed | CommunicationFailed $e) {
80 16
                $handler->getConnection()->close();
81 16
                goto retry;
82 12
            } catch (ClientException $e) {
83
                retry:
84 28
                if (self::MAX_RETRIES_LIMIT === $retries) {
85 4
                    break;
86
                }
87 28
                if (null === $delayMs = ($this->getDelayMs)(++$retries, $e)) {
88 8
                    break;
89
                }
90 28
                $delayMs = \min($delayMs, self::MAX_DELAY_MS) / 2;
91 28
                $delayMs += \mt_rand(0, $delayMs);
92 28
                \usleep($delayMs * 1000);
93
            }
94 28
        } while (true);
95
96 16
        throw $e;
97
    }
98
}
99