Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.75% covered (warning)
68.75%
33 / 48
44.44% covered (danger)
44.44%
4 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Lock
68.75% covered (warning)
68.75%
33 / 48
44.44% covered (danger)
44.44%
4 / 9
44.07
0.00% covered (danger)
0.00%
0 / 1
 __construct
73.68% covered (warning)
73.68%
14 / 19
0.00% covered (danger)
0.00%
0 / 1
6.66
 filepath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 acquired
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 haveYouKilledFrozenProcess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 release
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 isToKill
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isKilledFrozenProcess
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 mkPath
62.50% covered (warning)
62.50%
5 / 8
0.00% covered (danger)
0.00%
0 / 1
15.27
 mkLock
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
2.09
1<?php
2namespace 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
17class 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?>