ExponentialDelayMiddleware   A
last analyzed

Complexity

Total Complexity 12

Size/Duplication

Total Lines 90
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 90
ccs 38
cts 38
cp 1
rs 10
c 0
b 0
f 0
wmc 12

6 Methods

Rating   Name   Duplication   Size   Complexity  
A suites() 0 3 1
A processFailure() 0 18 2
A __construct() 0 23 5
A createNewMeta() 0 5 1
A getDelay() 0 13 2
A getAttempts() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Queue\Middleware\FailureHandling\Implementation;
6
7
use InvalidArgumentException;
8
use Yiisoft\Queue\Message\MessageInterface;
9
use Yiisoft\Queue\Middleware\FailureHandling\FailureHandlingRequest;
10
use Yiisoft\Queue\Middleware\FailureHandling\MessageFailureHandlerInterface;
11
use Yiisoft\Queue\Middleware\FailureHandling\MiddlewareFailureInterface;
12
use Yiisoft\Queue\Middleware\Push\Implementation\DelayMiddlewareInterface;
13
use Yiisoft\Queue\QueueInterface;
14
use Yiisoft\Queue\Middleware\FailureHandling\FailureEnvelope;
15
16
/**
17
 * Failure strategy which resends the given message to a queue with an exponentially increasing delay.
18
 * The delay mechanism **must** be implemented by the used {@see AdapterInterface} implementation.
19
 */
20
final class ExponentialDelayMiddleware implements MiddlewareFailureInterface
21
{
22
    public const META_KEY_ATTEMPTS = 'failure-strategy-exponential-delay-attempts';
23
    public const META_KEY_DELAY = 'failure-strategy-exponential-delay-delay';
24
25
    /**
26
     * @param string $id A unique id to differentiate two and more instances of this class
27
     * @param int $maxAttempts Maximum attempts count for this strategy with the given $id before it will give up
28
     * @param float $delayInitial The first delay period
29
     * @param float $delayMaximum The maximum delay period
30
     * @param float $exponent Message handling delay will be increased by this multiplication each time it fails
31
     * @param QueueInterface|null $queue
32
     */
33 18
    public function __construct(
34
        private string $id,
35
        private int $maxAttempts,
36
        private float $delayInitial,
37
        private float $delayMaximum,
38
        private float $exponent,
39
        private DelayMiddlewareInterface $delayMiddleware,
40
        private ?QueueInterface $queue = null,
41
    ) {
42 18
        if ($maxAttempts <= 0) {
43 2
            throw new InvalidArgumentException("maxAttempts parameter must be a positive integer, $this->maxAttempts given.");
44
        }
45
46 16
        if ($delayInitial <= 0) {
47 3
            throw new InvalidArgumentException("delayInitial parameter must be a positive float, $this->delayInitial given.");
48
        }
49
50 13
        if ($delayMaximum < $delayInitial) {
51 1
            throw new InvalidArgumentException("delayMaximum parameter must not be less then delayInitial, , $this->delayMaximum given.");
52
        }
53
54 12
        if ($exponent <= 0) {
55 1
            throw new InvalidArgumentException("exponent parameter must not be zero or less, $this->exponent given.");
56
        }
57
    }
58
59 9
    public function processFailure(
60
        FailureHandlingRequest $request,
61
        MessageFailureHandlerInterface $handler
62
    ): FailureHandlingRequest {
63 9
        $message = $request->getMessage();
64 9
        if ($this->suites($message)) {
65 8
            $envelope = new FailureEnvelope($message, $this->createNewMeta($message));
66 8
            $queue = $this->queue ?? $request->getQueue();
67 8
            $middlewareDefinitions = $this->delayMiddleware->withDelay($this->getDelay($envelope));
68 8
            $messageNew = $queue->push(
69 8
                $envelope,
70 8
                $middlewareDefinitions
71 8
            );
72
73 8
            return $request->withMessage($messageNew);
74
        }
75
76 2
        return $handler->handleFailure($request);
77
    }
78
79 9
    private function suites(MessageInterface $message): bool
80
    {
81 9
        return $this->maxAttempts > $this->getAttempts($message);
82
    }
83
84 8
    private function createNewMeta(MessageInterface $message): array
85
    {
86 8
        return [
87 8
            self::META_KEY_DELAY . "-$this->id" => $this->getDelay($message),
88 8
            self::META_KEY_ATTEMPTS . "-$this->id" => $this->getAttempts($message) + 1,
89 8
        ];
90
    }
91
92 9
    private function getAttempts(MessageInterface $message): int
93
    {
94 9
        return $message->getMetadata()[FailureEnvelope::FAILURE_META_KEY][self::META_KEY_ATTEMPTS . "-$this->id"] ?? 0;
95
    }
96
97 8
    private function getDelay(MessageInterface $message): float
98
    {
99 8
        $meta = $message->getMetadata()[FailureEnvelope::FAILURE_META_KEY] ?? [];
100 8
        $key = self::META_KEY_DELAY . "-$this->id";
101
102 8
        $delayOriginal = (float) ($meta[$key] ?? 0);
103 8
        if ($delayOriginal <= 0) {
104 3
            $delayOriginal = $this->delayInitial;
105
        }
106
107 8
        $result = $delayOriginal * $this->exponent;
108
109 8
        return min($result, $this->delayMaximum);
110
    }
111
}
112