Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
68.09% |
32 / 47 |
|
44.44% |
4 / 9 |
CRAP | |
0.00% |
0 / 1 |
| Lock | |
68.09% |
32 / 47 |
|
44.44% |
4 / 9 |
45.32 | |
0.00% |
0 / 1 |
| __construct | |
72.22% |
13 / 18 |
|
0.00% |
0 / 1 |
6.77 | |||
| 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 | $this->filepath = sprintf('%s/%s%s', |
| 34 | $this->path, |
| 35 | $processName, |
| 36 | preg_match('/.lock$/', $processName) ? '' : '.lock'); |
| 37 | |
| 38 | if( ! file_exists($this->filepath)){ |
| 39 | $this->acquired = $this->mkLock(); |
| 40 | return; |
| 41 | } |
| 42 | //else lock already exists... but i must check if is started before than $maxLifeInMin |
| 43 | if ( ! $this->isToKill() ){ |
| 44 | error_log("[INF] {$this->filepath} <= maxLifeInMin: i wait"); |
| 45 | return; |
| 46 | } |
| 47 | |
| 48 | error_log("[ERR] {$this->filepath} > maxLifeInMin: process must be killed"); |
| 49 | if ( $this->isKilledFrozenProcess() ){ |
| 50 | $this->acquired = $this->mkLock(); |
| 51 | $this->killFrozenProcess = true; |
| 52 | } |
| 53 | |
| 54 | } |
| 55 | |
| 56 | /** |
| 57 | * Get filepath url |
| 58 | * |
| 59 | * @return string lock file path |
| 60 | */ |
| 61 | public function filepath():string{ return $this->filepath; } |
| 62 | |
| 63 | /** |
| 64 | * Get lock acquire status |
| 65 | * |
| 66 | * @return bool True if is acquired, false otherwise |
| 67 | */ |
| 68 | public function acquired():bool { return $this->acquired; } |
| 69 | |
| 70 | /** |
| 71 | * Get status about previuos process killed |
| 72 | * |
| 73 | * @return bool True if is killed, false otherwise |
| 74 | */ |
| 75 | public function haveYouKilledFrozenProcess():bool { return $this->killedFrozenProcess; } |
| 76 | |
| 77 | /** |
| 78 | * Get status lock release |
| 79 | * |
| 80 | * @return bool True if is released, false otherwise |
| 81 | */ |
| 82 | public function release():bool{ |
| 83 | $released = unlink($this->filepath); |
| 84 | |
| 85 | error_log($released |
| 86 | ? "[INF] Lock released" |
| 87 | : "[ERR] Impossible release lock" |
| 88 | ); |
| 89 | return $released; |
| 90 | } |
| 91 | |
| 92 | /** |
| 93 | * Check if previous executed process is to kill |
| 94 | * |
| 95 | * @return bool True if is to kill, false otherwise |
| 96 | */ |
| 97 | private function isToKill():bool{ return ( filectime($this->filepath) + $this->maxLifeInMin*60 < time() ); } |
| 98 | |
| 99 | /** |
| 100 | * Kill frozen process |
| 101 | * |
| 102 | * @return bool True when process is killed, false otherwise |
| 103 | */ |
| 104 | private function isKilledFrozenProcess():bool{ |
| 105 | $pid = file_get_contents($this->filepath); |
| 106 | unlink($this->filepath); |
| 107 | posix_kill($pid, SIGKILL); |
| 108 | return true; |
| 109 | } |
| 110 | |
| 111 | /** |
| 112 | * Create path dir for lock files |
| 113 | * Check if file exists AND is directory AND is writable, or try to create/set it permission |
| 114 | * |
| 115 | * @param string $path Path to try to create |
| 116 | * |
| 117 | * @return bool True if file exists AND is directory AND is writable, false otherwise |
| 118 | */ |
| 119 | private function mkPath(string $path):bool{ |
| 120 | if( file_exists($path) && is_dir($path) && is_writable($path) ) |
| 121 | return true; |
| 122 | |
| 123 | if( file_exists($path) && is_dir($path) && ! is_writable($path) ) |
| 124 | return chmod($path, 0755); |
| 125 | |
| 126 | if( file_exists($path) && ! is_dir($path) ){ |
| 127 | if ( ! unlink($path) ) |
| 128 | return false; //and below i try to mkdir |
| 129 | } |
| 130 | |
| 131 | //file not exists |
| 132 | return mkdir($path, 0755, true); |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | * Create lock file containing process PID |
| 137 | * |
| 138 | * @return bool True if file exists AND is directory AND is writable, false otherwise |
| 139 | */ |
| 140 | private function mkLock():bool{ |
| 141 | $handler = fopen($this->filepath, 'w'); |
| 142 | |
| 143 | if( false == $handler ){ |
| 144 | error_log(sprintf("[%s] Unable to create lock file %s", __METHOD__, $this->filepath)); |
| 145 | return false; |
| 146 | } |
| 147 | |
| 148 | fwrite( $handler, getmypid() ); |
| 149 | fclose($handler); |
| 150 | return true; |
| 151 | } |
| 152 | |
| 153 | |
| 154 | |
| 155 | } |
| 156 | |
| 157 | ?> |