| Code Coverage
| Lines | Functions and Methods | Classes and Traits
Total | 13.04% covered (danger) | 13.04% | 9 / 69 | 22.22% covered (danger) | 22.22% | 2 / 9 | CRAP | 0.00% covered (danger) | 0.00% | 0 / 1
Timer | 13.04% covered (danger) | 13.04% | 9 / 69 | 22.22% covered (danger) | 22.22% | 2 / 9 | 840.46 | 0.00% covered (danger) | 0.00% | 0 / 1
init | 100.00% covered (success) | 100.00% | 5 / 5 | 100.00% covered (success) | 100.00% | 1 / 1 | 3 |
repeat | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
delay | 0.00% covered (danger) | 0.00% | 0 / 1 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2 |
signalHandle | 0.00% covered (danger) | 0.00% | 0 / 3 | 0.00% covered (danger) | 0.00% | 0 / 1 | 6 |
add | 0.00% covered (danger) | 0.00% | 0 / 17 | 0.00% covered (danger) | 0.00% | 0 / 1 | 90 |
sleep | 0.00% covered (danger) | 0.00% | 0 / 10 | 0.00% covered (danger) | 0.00% | 0 / 1 | 12 |
tick | 0.00% covered (danger) | 0.00% | 0 / 20 | 0.00% covered (danger) | 0.00% | 0 / 1 | 90 |
del | 0.00% covered (danger) | 0.00% | 0 / 8 | 0.00% covered (danger) | 0.00% | 0 / 1 | 30 |
delAll | 100.00% covered (success) | 100.00% | 4 / 4 | 100.00% covered (success) | 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 | }