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
15declare(strict_types=1);
16
17namespace Workerman;
18
19use RuntimeException;
20use Throwable;
21use Workerman\Events\EventInterface;
22use Workerman\Events\Fiber;
23use Workerman\Events\Swoole;
24use Revolt\EventLoop;
25use Swoole\Coroutine\System;
26use function function_exists;
27use function pcntl_alarm;
28use function pcntl_signal;
29use function time;
30use const PHP_INT_MAX;
31use const SIGALRM;
32
33/**
34 * Timer.
35 */
36class 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}