Timer.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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. declare(strict_types=1);
  15. namespace Workerman;
  16. use Exception;
  17. use Revolt\EventLoop;
  18. use RuntimeException;
  19. use Swoole\Coroutine\System;
  20. use Throwable;
  21. use Workerman\Events\EventInterface;
  22. use Workerman\Events\Revolt;
  23. use Workerman\Events\Swoole;
  24. use Workerman\Events\Swow;
  25. use function function_exists;
  26. use function is_callable;
  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. * Timer.
  34. */
  35. class Timer
  36. {
  37. /**
  38. * Tasks that based on ALARM signal.
  39. * [
  40. * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
  41. * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
  42. * ..
  43. * ]
  44. *
  45. * @var array
  46. */
  47. protected static array $tasks = [];
  48. /**
  49. * Event
  50. *
  51. * @var ?EventInterface
  52. */
  53. protected static ?EventInterface $event = null;
  54. /**
  55. * Timer id
  56. *
  57. * @var int
  58. */
  59. protected static int $timerId = 0;
  60. /**
  61. * Timer status
  62. * [
  63. * timer_id1 => bool,
  64. * timer_id2 => bool,
  65. * ....................,
  66. * ]
  67. *
  68. * @var array
  69. */
  70. protected static array $status = [];
  71. /**
  72. * Init.
  73. *
  74. * @param EventInterface|null $event
  75. * @return void
  76. */
  77. public static function init(EventInterface $event = null)
  78. {
  79. if ($event) {
  80. self::$event = $event;
  81. return;
  82. }
  83. if (function_exists('pcntl_signal')) {
  84. pcntl_signal(SIGALRM, ['\Workerman\Timer', 'signalHandle'], false);
  85. }
  86. }
  87. /**
  88. * ALARM signal handler.
  89. *
  90. * @return void
  91. */
  92. public static function signalHandle()
  93. {
  94. if (!self::$event) {
  95. pcntl_alarm(1);
  96. self::tick();
  97. }
  98. }
  99. /**
  100. * Add a timer.
  101. *
  102. * @param float $timeInterval
  103. * @param callable $func
  104. * @param mixed|array $args
  105. * @param bool $persistent
  106. * @return int
  107. */
  108. public static function add(float $timeInterval, callable $func, null|array $args = [], bool $persistent = true): int
  109. {
  110. if ($timeInterval < 0) {
  111. throw new RuntimeException('$timeInterval can not less than 0');
  112. }
  113. if ($args === null) {
  114. $args = [];
  115. }
  116. if (self::$event) {
  117. return $persistent ? self::$event->repeat($timeInterval, $func, $args) : self::$event->delay($timeInterval, $func, $args);
  118. }
  119. // If not workerman runtime just return.
  120. if (!Worker::getAllWorkers()) {
  121. throw new RuntimeException('Timer can only be used in workerman running environment');
  122. }
  123. if (empty(self::$tasks)) {
  124. pcntl_alarm(1);
  125. }
  126. $runTime = time() + $timeInterval;
  127. if (!isset(self::$tasks[$runTime])) {
  128. self::$tasks[$runTime] = [];
  129. }
  130. self::$timerId = self::$timerId == PHP_INT_MAX ? 1 : ++self::$timerId;
  131. self::$status[self::$timerId] = true;
  132. self::$tasks[$runTime][self::$timerId] = [$func, (array)$args, $persistent, $timeInterval];
  133. return self::$timerId;
  134. }
  135. /**
  136. * Coroutine sleep.
  137. *
  138. * @param float $delay
  139. * @return void
  140. */
  141. public static function sleep(float $delay)
  142. {
  143. switch (Worker::$eventLoopClass) {
  144. // Fiber
  145. case Revolt::class:
  146. $suspension = EventLoop::getSuspension();
  147. static::add($delay, function () use ($suspension) {
  148. $suspension->resume();
  149. }, null, false);
  150. $suspension->suspend();
  151. return;
  152. // Swoole
  153. case Swoole::class:
  154. System::sleep($delay);
  155. return;
  156. // Swow
  157. case Swow::class:
  158. usleep($delay * 1000 * 1000);
  159. return;
  160. }
  161. throw new RuntimeException('Timer::sleep() require revolt/event-loop. Please run command "composer require revolt/event-loop" and restart workerman');
  162. }
  163. /**
  164. * Tick.
  165. *
  166. * @return void
  167. */
  168. public static function tick()
  169. {
  170. if (empty(self::$tasks)) {
  171. pcntl_alarm(0);
  172. return;
  173. }
  174. $timeNow = time();
  175. foreach (self::$tasks as $runTime => $taskData) {
  176. if ($timeNow >= $runTime) {
  177. foreach ($taskData as $index => $oneTask) {
  178. $taskFunc = $oneTask[0];
  179. $taskArgs = $oneTask[1];
  180. $persistent = $oneTask[2];
  181. $timeInterval = $oneTask[3];
  182. try {
  183. $taskFunc(...$taskArgs);
  184. } catch (Throwable $e) {
  185. Worker::safeEcho((string)$e);
  186. }
  187. if ($persistent && !empty(self::$status[$index])) {
  188. $newRunTime = time() + $timeInterval;
  189. if (!isset(self::$tasks[$newRunTime])) self::$tasks[$newRunTime] = [];
  190. self::$tasks[$newRunTime][$index] = [$taskFunc, (array)$taskArgs, $persistent, $timeInterval];
  191. }
  192. }
  193. unset(self::$tasks[$runTime]);
  194. }
  195. }
  196. }
  197. /**
  198. * Remove a timer.
  199. *
  200. * @param int $timerId
  201. * @return bool
  202. */
  203. public static function del(int $timerId): bool
  204. {
  205. if (self::$event) {
  206. return self::$event->offDelay($timerId);
  207. }
  208. foreach (self::$tasks as $runTime => $taskData) {
  209. if (array_key_exists($timerId, $taskData)) {
  210. unset(self::$tasks[$runTime][$timerId]);
  211. }
  212. }
  213. if (array_key_exists($timerId, self::$status)) {
  214. unset(self::$status[$timerId]);
  215. }
  216. return true;
  217. }
  218. /**
  219. * Remove all timers.
  220. *
  221. * @return void
  222. */
  223. public static function delAll()
  224. {
  225. self::$tasks = self::$status = [];
  226. if (function_exists('pcntl_alarm')) {
  227. pcntl_alarm(0);
  228. }
  229. if (self::$event) {
  230. self::$event->deleteAllTimer();
  231. }
  232. }
  233. }