PluginProbe
DecaLog / 4.4.0
DecaLog v4.4.0
3.0.2 3.1.0 3.10.0 3.2.0 3.3.0 3.4.0 3.4.1 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.6.3 3.7.0 3.7.1 3.8.0 3.9.0 3.9.1 4.0.0 4.1.0 4.2.0 4.3.0 4.3.1 4.4.0 4.5.0 All 75 releases
decalog / includes / libraries / lock / util / Loop.php

Loop.php in DecaLog 4.4.0, at includes/libraries/lock/util/Loop.php

130 lines 3.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace malkusch\lock\util;
6
7 use LengthException;
8 use malkusch\lock\exception\TimeoutException;
9
10 /**
11 * Repeats executing a code until it was successful.
12 *
13 * @author Markus Malkusch <markus@malkusch.de>
14 * @link bitcoin:1P5FAZ4QhXCuwYPnLZdk3PJsqePbu1UDDA Donations
15 * @license WTFPL
16 * @internal
17 */
18 class Loop
19 {
20 /**
21 * Minimum time that we want to wait, between lock checks. In micro seconds.
22 *
23 * @var double
24 */
25 private const MINIMUM_WAIT_US = 1e4; // 0.01 seconds
26
27 /**
28 * Maximum time that we want to wait, between lock checks. In micro seconds.
29 *
30 * @var double
31 */
32 private const MAXIMUM_WAIT_US = 5e5; // 0.50 seconds
33
34 /**
35 * @var int The timeout in seconds.
36 */
37 private $timeout;
38
39 /**
40 * @var bool True while code execution is repeating.
41 */
42 private $looping;
43
44 /**
45 * Sets the timeout. The default is 3 seconds.
46 *
47 * @param int $timeout The timeout in seconds. The default is 3 seconds.
48 * @throws \LengthException The timeout must be greater than 0.
49 */
50 public function __construct(int $timeout = 3)
51 {
52 if ($timeout <= 0) {
53 throw new LengthException(\sprintf(
54 'The timeout must be greater than 0. %d was given.',
55 $timeout
56 ));
57 }
58
59 $this->timeout = $timeout;
60 $this->looping = false;
61 }
62
63 /**
64 * Notifies that this was the last iteration.
65 *
66 * @return void
67 */
68 public function end(): void
69 {
70 $this->looping = false;
71 }
72
73 /**
74 * Repeats executing a code until it was successful.
75 *
76 * The code has to be designed in a way that it can be repeated without any
77 * side effects. When execution was successful it should notify that event
78 * by calling {@link \malkusch\lock\util\Loop::end()}. I.e. the only side
79 * effects of the code may happen after a successful execution.
80 *
81 * If the code throws an exception it will stop repeating the execution.
82 *
83 * @param callable $code The to be executed code callback.
84 * @throws \Exception The execution callback threw an exception.
85 * @throws \malkusch\lock\exception\TimeoutException The timeout has been
86 * reached.
87 * @return mixed The return value of the executed code callback.
88 *
89 */
90 public function execute(callable $code)
91 {
92 $this->looping = true;
93
94 // At this time, the lock will time out.
95 $deadline = microtime(true) + $this->timeout;
96
97 $result = null;
98 for ($i = 0; $this->looping && microtime(true) < $deadline; ++$i) {
99 $result = $code();
100 if (!$this->looping) { // @phpstan-ignore-line
101 break;
102 }
103
104 // Calculate max time remaining, don't sleep any longer than that.
105 $usecRemaining = intval(($deadline - microtime(true)) * 1e6);
106
107 // We've ran out of time.
108 if ($usecRemaining <= 0) {
109 throw TimeoutException::create($this->timeout);
110 }
111
112 $min = min(
113 (int) self::MINIMUM_WAIT_US * 1.25 ** $i,
114 self::MAXIMUM_WAIT_US
115 );
116 $max = min($min * 2, self::MAXIMUM_WAIT_US);
117
118 $usecToSleep = min($usecRemaining, random_int((int)$min, (int)$max));
119
120 usleep($usecToSleep);
121 }
122
123 if (microtime(true) >= $deadline) {
124 throw TimeoutException::create($this->timeout);
125 }
126
127 return $result;
128 }
129 }
130