Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
68.75% |
33 / 48 |
|
44.44% |
4 / 9 |
CRAP | |
0.00% |
0 / 1 |
Lock | |
68.75% |
33 / 48 |
|
44.44% |
4 / 9 |
44.07 | |
0.00% |
0 / 1 |
__construct | |
73.68% |
14 / 19 |
|
0.00% |
0 / 1 |
6.66 | |||
filepath | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
acquired | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
haveYouKilledFrozenProcess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
release | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
isToKill | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isKilledFrozenProcess | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
mkPath | |
62.50% |
5 / 8 |
|
0.00% |
0 / 1 |
15.27 | |||
mkLock | |
71.43% |
5 / 7 |
|
0.00% |
0 / 1 |
2.09 |
1 | <?php |
2 | namespace DiegoSorrentino\Library; |
3 | |
4 | /** |
5 | * Lock class for PHP scripts |
6 | * Create a lock file in "<basePath>/processName.lock", containing the script PID, |
7 | * and the maxLife (in minutes) for the process. When script is called it search for the lock file and: |
8 | * - if not exists, it is created |
9 | * - if exists but is expired (creationTime filectime + $this->maxLifeInMin*60 > NOW), kill the previuos PID, destroy and recreate lock |
10 | * - if exists and is not expired, simply dont acquire the lock |
11 | * |
12 | * @param $processName string set filename for lock file. Default value 'file.lock'. It will be <basePath>/processName.lock |
13 | * @param $basePath string (nullable) set basePath to store lock file. Default value './' (if not exists it will be created) |
14 | * @param $maxLifeInMin int (nullable) set maxLife (in minutes) for the process. Default value is 30 min |
15 | */ |
16 | |
17 | class Lock{ |
18 | private int $maxLifeInMin = 30; |
19 | |
20 | private string $path; |
21 | private string $filepath; |
22 | |
23 | private bool $acquired = false; |
24 | private bool $killedFrozenProcess = false; |
25 | |
26 | public function __construct( string $processName, ?string $basePath = './', ?int $maxLifeInMin = 30){ |
27 | $this->path = $basePath; |
28 | $this->maxLifeInMin = $maxLifeInMin; |
29 | |
30 | if( ! $this->mkPath($this->path) ) |
31 | throw new \RuntimeException(sprintf("[%s] Unable to create or write in %s directory", __METHOD__, $this->path) ); |
32 | |
33 | $processName = |
34 | $this->filepath = sprintf('%s/%s%s', |
35 | $this->path, |
36 | $processName, |
37 | preg_match('/.lock$/', $processName) ? '' : '.lock'); |
38 | |
39 | if( ! file_exists($this->filepath)){ |
40 | $this->acquired = $this->mkLock(); |
41 | return; |
42 | } |
43 | //else lock already exists... but i must check if is started before than $maxLifeInMin |
44 | if ( ! $this->isToKill() ){ |
45 | error_log("[INF] {$this->filepath} <= maxLifeInMin: i wait"); |
46 | return; |
47 | } |
48 | |
49 | error_log("[ERR] {$this->filepath} > maxLifeInMin: process must be killed"); |
50 | if ( $this->isKilledFrozenProcess() ){ |
51 | $this->acquired = $this->mkLock(); |
52 | $this->killFrozenProcess = true; |
53 | } |
54 | |
55 | } |
56 | |
57 | /** |
58 | * Get filepath url |
59 | * |
60 | * @return string lock file path |
61 | */ |
62 | public function filepath():string{ return $this->filepath; } |
63 | |
64 | /** |
65 | * Get lock acquire status |
66 | * |
67 | * @return bool True if is acquired, false otherwise |
68 | */ |
69 | public function acquired():bool { return $this->acquired; } |
70 | |
71 | /** |
72 | * Get status about previuos process killed |
73 | * |
74 | * @return bool True if is killed, false otherwise |
75 | */ |
76 | public function haveYouKilledFrozenProcess():bool { return $this->killedFrozenProcess; } |
77 | |
78 | /** |
79 | * Get status lock release |
80 | * |
81 | * @return bool True if is released, false otherwise |
82 | */ |
83 | public function release():bool{ |
84 | $released = unlink($this->filepath); |
85 | |
86 | error_log($released |
87 | ? "[INF] Lock released" |
88 | : "[ERR] Impossible release lock" |
89 | ); |
90 | return $released; |
91 | } |
92 | |
93 | /** |
94 | * Check if previous executed process is to kill |
95 | * |
96 | * @return bool True if is to kill, false otherwise |
97 | */ |
98 | private function isToKill():bool{ return ( filectime($this->filepath) + $this->maxLifeInMin*60 < time() ); } |
99 | |
100 | /** |
101 | * Kill frozen process |
102 | * |
103 | * @return bool True when process is killed, false otherwise |
104 | */ |
105 | private function isKilledFrozenProcess():bool{ |
106 | $pid = file_get_contents($this->filepath); |
107 | unlink($this->filepath); |
108 | posix_kill($pid, SIGKILL); |
109 | return true; |
110 | } |
111 | |
112 | /** |
113 | * Create path dir for lock files |
114 | * Check if file exists AND is directory AND is writable, or try to create/set it permission |
115 | * |
116 | * @param string $path Path to try to create |
117 | * |
118 | * @return bool True if file exists AND is directory AND is writable, false otherwise |
119 | */ |
120 | private function mkPath(string $path):bool{ |
121 | if( file_exists($path) && is_dir($path) && is_writable($path) ) |
122 | return true; |
123 | |
124 | if( file_exists($path) && is_dir($path) && ! is_writable($path) ) |
125 | return chmod($path, 0755); |
126 | |
127 | if( file_exists($path) && ! is_dir($path) ){ |
128 | if ( ! unlink($path) ) |
129 | return false; //and below i try to mkdir |
130 | } |
131 | |
132 | //file not exists |
133 | return mkdir($path, 0755, true); |
134 | } |
135 | |
136 | /** |
137 | * Create lock file containing process PID |
138 | * |
139 | * @return bool True if file exists AND is directory AND is writable, false otherwise |
140 | */ |
141 | private function mkLock():bool{ |
142 | $handler = fopen($this->filepath, 'w'); |
143 | |
144 | if( false == $handler ){ |
145 | error_log(sprintf("[%s] Unable to create lock file %s", __METHOD__, $this->filepath)); |
146 | return false; |
147 | } |
148 | |
149 | fwrite( $handler, getmypid() ); |
150 | fclose($handler); |
151 | return true; |
152 | } |
153 | |
154 | |
155 | |
156 | } |
157 | |
158 | ?> |