PluginProbe
DecaLog / 4.4.0
DecaLog v4.4.0
3.0.2 3.1.0 3.10.0 3.2.0 3.3.0 3.4.0 3.4.1 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.6.3 3.7.0 3.7.1 3.8.0 3.9.0 3.9.1 4.0.0 4.1.0 4.2.0 4.3.0 4.3.1 4.4.0 4.5.0 All 75 releases
decalog / includes / libraries / lock / util / PcntlTimeout.php

PcntlTimeout.php in DecaLog 4.4.0, at includes/libraries/lock/util/PcntlTimeout.php

114 lines 3.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace malkusch\lock\util;
6
7 use InvalidArgumentException;
8 use malkusch\lock\exception\DeadlineException;
9 use malkusch\lock\exception\LockAcquireException;
10 use RuntimeException;
11
12 /**
13 * Timeout based on a scheduled alarm.
14 *
15 * This class requires the pcntl module.
16 *
17 * @author Markus Malkusch <[email protected]>
18 * @link bitcoin:1335STSwu9hST4vcMRppEPgENMHD2r1REK Donations
19 * @license WTFPL
20 * @internal
21 */
22 final class PcntlTimeout
23 {
24 /**
25 * @var int Timeout in seconds
26 */
27 private $timeout;
28
29 /**
30 * Builds the timeout.
31 *
32 * @param int $timeout Timeout in seconds.
33 * @throws \RuntimeException When the PCNTL module is not enabled.
34 * @throws \InvalidArgumentException When the timeout is zero or negative.
35 */
36 public function __construct(int $timeout)
37 {
38 if (!self::isSupported()) {
39 throw new RuntimeException('PCNTL module not enabled');
40 }
41
42 if ($timeout <= 0) {
43 throw new InvalidArgumentException(
44 'Timeout must be positive and non zero'
45 );
46 }
47
48 $this->timeout = $timeout;
49 }
50
51 /**
52 * Runs the code and would eventually time out.
53 *
54 * This method has the side effect, that any signal handler for SIGALRM will
55 * be reset to the default hanlder (SIG_DFL). It also expects that there is
56 * no previously scheduled alarm. If your application uses alarms
57 * ({@link pcntl_alarm()}) or a signal handler for SIGALRM, don't use this
58 * method. It will interfer with your application and lead to unexpected
59 * behaviour.
60 *
61 * @param callable $code Executed code block
62 * @throws \malkusch\lock\exception\DeadlineException Running the code hit
63 * the deadline.
64 * @throws \malkusch\lock\exception\LockAcquireException Installing the
65 * timeout failed.
66 * @return mixed Return value of the executed block
67 */
68 public function timeBoxed(callable $code)
69 {
70 $existingHandler = pcntl_signal_get_handler(SIGALRM);
71
72 $signal = pcntl_signal(SIGALRM, function (): void {
73 throw new DeadlineException(sprintf(
74 'Timebox hit deadline of %d seconds',
75 $this->timeout
76 ));
77 });
78 if (!$signal) {
79 throw new LockAcquireException('Could not install signal');
80 }
81
82 $oldAlarm = pcntl_alarm($this->timeout);
83 if ($oldAlarm != 0) {
84 throw new LockAcquireException('Existing alarm was not expected');
85 }
86
87 try {
88 return $code();
89 } finally {
90 pcntl_alarm(0);
91 pcntl_signal_dispatch();
92 pcntl_signal(SIGALRM, $existingHandler);
93 }
94 }
95
96 /**
97 * Returns if this class is supported by the PHP runtime.
98 *
99 * This class requires the pcntl module. This method checks if
100 * it is available.
101 *
102 * @return bool TRUE if this class is supported by the PHP runtime.
103 */
104 public static function isSupported(): bool
105 {
106 return
107 PHP_SAPI === 'cli' &&
108 extension_loaded('pcntl') &&
109 function_exists('pcntl_alarm') &&
110 function_exists('pcntl_signal') &&
111 function_exists('pcntl_signal_dispatch');
112 }
113 }
114