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 / mutex / FlockMutex.php

FlockMutex.php in DecaLog 4.4.0, at includes/libraries/lock/mutex/FlockMutex.php

182 lines 4.0 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\mutex;
6
7 use malkusch\lock\exception\DeadlineException;
8 use malkusch\lock\exception\LockAcquireException;
9 use malkusch\lock\exception\LockReleaseException;
10 use malkusch\lock\exception\TimeoutException;
11 use malkusch\lock\util\Loop;
12 use malkusch\lock\util\PcntlTimeout;
13
14 /**
15 * Flock() based mutex implementation.
16 *
17 * @author Markus Malkusch <[email protected]>
18 * @link bitcoin:1P5FAZ4QhXCuwYPnLZdk3PJsqePbu1UDDA Donations
19 * @license WTFPL
20 * @see flock()
21 */
22 class FlockMutex extends LockMutex
23 {
24 public const INFINITE_TIMEOUT = -1;
25
26 /**
27 * @internal
28 */
29 const STRATEGY_BLOCK = 1;
30
31 /**
32 * @internal
33 */
34 const STRATEGY_PCNTL = 2;
35
36 /**
37 * @internal
38 */
39 const STRATEGY_BUSY = 3;
40
41 /**
42 * @var resource $fileHandle The file handle.
43 */
44 private $fileHandle;
45
46 /**
47 * @var int
48 */
49 private $timeout;
50
51 /**
52 * @var int
53 */
54 private $strategy;
55
56 /**
57 * Sets the file handle.
58 *
59 * @param resource $fileHandle The file handle.
60 * @param int $timeout
61 */
62 public function __construct($fileHandle, int $timeout = self::INFINITE_TIMEOUT)
63 {
64 if (!is_resource($fileHandle)) {
65 throw new \InvalidArgumentException('The file handle is not a valid resource.');
66 }
67
68 $this->fileHandle = $fileHandle;
69 $this->timeout = $timeout;
70 $this->strategy = $this->determineLockingStrategy();
71 }
72
73 private function determineLockingStrategy()
74 {
75 if ($this->timeout == self::INFINITE_TIMEOUT) {
76 return self::STRATEGY_BLOCK;
77 }
78
79 if (PcntlTimeout::isSupported()) {
80 return self::STRATEGY_PCNTL;
81 }
82
83 return self::STRATEGY_BUSY;
84 }
85
86 /**
87 * @throws LockAcquireException
88 */
89 private function lockBlocking(): void
90 {
91 if (!flock($this->fileHandle, LOCK_EX)) {
92 throw new LockAcquireException('Failed to lock the file.');
93 }
94 }
95
96 /**
97 * @throws LockAcquireException
98 * @throws TimeoutException
99 */
100 private function lockPcntl(): void
101 {
102 $timebox = new PcntlTimeout($this->timeout);
103
104 try {
105 $timebox->timeBoxed(
106 function (): void {
107 $this->lockBlocking();
108 }
109 );
110 } catch (DeadlineException $e) {
111 throw TimeoutException::create($this->timeout);
112 }
113 }
114
115 /**
116 * @throws TimeoutException
117 * @throws LockAcquireException
118 */
119 private function lockBusy()
120 {
121 $loop = new Loop($this->timeout);
122 $loop->execute(function () use ($loop): void {
123 if ($this->acquireNonBlockingLock()) {
124 $loop->end();
125 }
126 });
127 }
128
129 /**
130 * @throws LockAcquireException
131 * @return bool
132 */
133 private function acquireNonBlockingLock(): bool
134 {
135 if (!flock($this->fileHandle, LOCK_EX|LOCK_NB, $wouldBlock)) {
136 if ($wouldBlock) {
137 /*
138 * Another process holds the lock.
139 */
140 return false;
141 }
142 throw new LockAcquireException('Failed to lock the file.');
143 }
144
145 return true;
146 }
147
148 /**
149 * @throws LockAcquireException
150 * @throws TimeoutException
151 */
152 protected function lock(): void
153 {
154 switch ($this->strategy) {
155 case self::STRATEGY_BLOCK:
156 $this->lockBlocking();
157
158 return;
159 case self::STRATEGY_PCNTL:
160 $this->lockPcntl();
161
162 return;
163 case self::STRATEGY_BUSY:
164 $this->lockBusy();
165
166 return;
167 }
168
169 throw new \RuntimeException("Unknown strategy '{$this->strategy}'.'");
170 }
171
172 /**
173 * @throws LockReleaseException
174 */
175 protected function unlock(): void
176 {
177 if (!flock($this->fileHandle, LOCK_UN)) {
178 throw new LockReleaseException('Failed to unlock the file.');
179 }
180 }
181 }
182