Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
13.04% |
9 / 69 |
|
22.22% |
2 / 9 |
CRAP | |
0.00% |
0 / 1 |
Timer | |
13.04% |
9 / 69 |
|
22.22% |
2 / 9 |
840.46 | |
0.00% |
0 / 1 |
init | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
repeat | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
delay | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
signalHandle | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
add | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
90 | |||
sleep | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
tick | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
90 | |||
del | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
30 | |||
delAll | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This file is part of workerman. |
4 | * |
5 | * Licensed under The MIT License |
6 | * For full copyright and license information, please see the MIT-LICENSE.txt |
7 | * Redistributions of files must retain the above copyright notice. |
8 | * |
9 | * @author walkor<walkor@workerman.net> |
10 | * @copyright walkor<walkor@workerman.net> |
11 | * @link http://www.workerman.net/ |
12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License |
13 | */ |
14 | |
15 | declare(strict_types=1); |
16 | |
17 | namespace Workerman; |
18 | |
19 | use RuntimeException; |
20 | use Throwable; |
21 | use Workerman\Events\EventInterface; |
22 | use Workerman\Events\Fiber; |
23 | use Workerman\Events\Swoole; |
24 | use Revolt\EventLoop; |
25 | use Swoole\Coroutine\System; |
26 | use function function_exists; |
27 | use function pcntl_alarm; |
28 | use function pcntl_signal; |
29 | use function time; |
30 | use const PHP_INT_MAX; |
31 | use const SIGALRM; |
32 | |
33 | /** |
34 | * Timer. |
35 | */ |
36 | class Timer |
37 | { |
38 | /** |
39 | * Tasks that based on ALARM signal. |
40 | * [ |
41 | * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], |
42 | * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], |
43 | * .. |
44 | * ] |
45 | * |
46 | * @var array |
47 | */ |
48 | protected static array $tasks = []; |
49 | |
50 | /** |
51 | * Event |
52 | * |
53 | * @var ?EventInterface |
54 | */ |
55 | protected static ?EventInterface $event = null; |
56 | |
57 | /** |
58 | * Timer id |
59 | * |
60 | * @var int |
61 | */ |
62 | protected static int $timerId = 0; |
63 | |
64 | /** |
65 | * Timer status |
66 | * [ |
67 | * timer_id1 => bool, |
68 | * timer_id2 => bool, |
69 | * ...................., |
70 | * ] |
71 | * |
72 | * @var array |
73 | */ |
74 | protected static array $status = []; |
75 | |
76 | /** |
77 | * Init. |
78 | * |
79 | * @param EventInterface|null $event |
80 | * @return void |
81 | */ |
82 | public static function init(?EventInterface $event = null): void |
83 | { |
84 | if ($event) { |
85 | self::$event = $event; |
86 | return; |
87 | } |
88 | if (function_exists('pcntl_signal')) { |
89 | pcntl_signal(SIGALRM, self::signalHandle(...), false); |
90 | } |
91 | } |
92 | |
93 | /** |
94 | * Repeat. |
95 | * |
96 | * @param float $timeInterval |
97 | * @param callable $func |
98 | * @param array $args |
99 | * @return int |
100 | */ |
101 | public static function repeat(float $timeInterval, callable $func, array $args = []): int |
102 | { |
103 | return self::$event->repeat($timeInterval, $func, $args); |
104 | } |
105 | |
106 | /** |
107 | * Delay. |
108 | * |
109 | * @param float $timeInterval |
110 | * @param callable $func |
111 | * @param array $args |
112 | * @return int |
113 | */ |
114 | public static function delay(float $timeInterval, callable $func, array $args = []): int |
115 | { |
116 | return self::$event->delay($timeInterval, $func, $args); |
117 | } |
118 | |
119 | /** |
120 | * ALARM signal handler. |
121 | * |
122 | * @return void |
123 | */ |
124 | public static function signalHandle(): void |
125 | { |
126 | if (!self::$event) { |
127 | pcntl_alarm(1); |
128 | self::tick(); |
129 | } |
130 | } |
131 | |
132 | /** |
133 | * Add a timer. |
134 | * |
135 | * @param float $timeInterval |
136 | * @param callable $func |
137 | * @param null|array $args |
138 | * @param bool $persistent |
139 | * @return int |
140 | */ |
141 | public static function add(float $timeInterval, callable $func, ?array $args = [], bool $persistent = true): int |
142 | { |
143 | if ($timeInterval < 0) { |
144 | throw new RuntimeException('$timeInterval can not less than 0'); |
145 | } |
146 | |
147 | if ($args === null) { |
148 | $args = []; |
149 | } |
150 | |
151 | if (self::$event) { |
152 | return $persistent ? self::$event->repeat($timeInterval, $func, $args) : self::$event->delay($timeInterval, $func, $args); |
153 | } |
154 | |
155 | // If not workerman runtime just return. |
156 | if (!Worker::getAllWorkers()) { |
157 | throw new RuntimeException('Timer can only be used in workerman running environment'); |
158 | } |
159 | |
160 | if (empty(self::$tasks)) { |
161 | pcntl_alarm(1); |
162 | } |
163 | |
164 | $runTime = time() + $timeInterval; |
165 | if (!isset(self::$tasks[$runTime])) { |
166 | self::$tasks[$runTime] = []; |
167 | } |
168 | |
169 | self::$timerId = self::$timerId == PHP_INT_MAX ? 1 : ++self::$timerId; |
170 | self::$status[self::$timerId] = true; |
171 | self::$tasks[$runTime][self::$timerId] = [$func, (array)$args, $persistent, $timeInterval]; |
172 | |
173 | return self::$timerId; |
174 | } |
175 | |
176 | /** |
177 | * Coroutine sleep. |
178 | * |
179 | * @param float $delay |
180 | * @return void |
181 | */ |
182 | public static function sleep(float $delay): void |
183 | { |
184 | switch (Worker::$eventLoopClass) { |
185 | // Fiber |
186 | case Fiber::class: |
187 | $suspension = EventLoop::getSuspension(); |
188 | static::add($delay, function () use ($suspension) { |
189 | $suspension->resume(); |
190 | }, null, false); |
191 | $suspension->suspend(); |
192 | return; |
193 | // Swoole |
194 | case Swoole::class: |
195 | System::sleep($delay); |
196 | return; |
197 | } |
198 | usleep((int)($delay * 1000 * 1000)); |
199 | } |
200 | |
201 | /** |
202 | * Tick. |
203 | * |
204 | * @return void |
205 | */ |
206 | protected static function tick(): void |
207 | { |
208 | if (empty(self::$tasks)) { |
209 | pcntl_alarm(0); |
210 | return; |
211 | } |
212 | $timeNow = time(); |
213 | foreach (self::$tasks as $runTime => $taskData) { |
214 | if ($timeNow >= $runTime) { |
215 | foreach ($taskData as $index => $oneTask) { |
216 | $taskFunc = $oneTask[0]; |
217 | $taskArgs = $oneTask[1]; |
218 | $persistent = $oneTask[2]; |
219 | $timeInterval = $oneTask[3]; |
220 | try { |
221 | $taskFunc(...$taskArgs); |
222 | } catch (Throwable $e) { |
223 | Worker::safeEcho((string)$e); |
224 | } |
225 | if ($persistent && !empty(self::$status[$index])) { |
226 | $newRunTime = time() + $timeInterval; |
227 | if (!isset(self::$tasks[$newRunTime])) { |
228 | self::$tasks[$newRunTime] = []; |
229 | } |
230 | self::$tasks[$newRunTime][$index] = [$taskFunc, (array)$taskArgs, $persistent, $timeInterval]; |
231 | } |
232 | } |
233 | unset(self::$tasks[$runTime]); |
234 | } |
235 | } |
236 | } |
237 | |
238 | /** |
239 | * Remove a timer. |
240 | * |
241 | * @param int $timerId |
242 | * @return bool |
243 | */ |
244 | public static function del(int $timerId): bool |
245 | { |
246 | if (self::$event) { |
247 | return self::$event->offDelay($timerId); |
248 | } |
249 | foreach (self::$tasks as $runTime => $taskData) { |
250 | if (array_key_exists($timerId, $taskData)) { |
251 | unset(self::$tasks[$runTime][$timerId]); |
252 | } |
253 | } |
254 | if (array_key_exists($timerId, self::$status)) { |
255 | unset(self::$status[$timerId]); |
256 | } |
257 | return true; |
258 | } |
259 | |
260 | /** |
261 | * Remove all timers. |
262 | * |
263 | * @return void |
264 | */ |
265 | public static function delAll(): void |
266 | { |
267 | self::$tasks = self::$status = []; |
268 | if (function_exists('pcntl_alarm')) { |
269 | pcntl_alarm(0); |
270 | } |
271 | self::$event?->deleteAllTimer(); |
272 | } |
273 | } |