Timer.php 6.6 KB

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