Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
13 / 13 |
CRAP | |
100.00% |
47 / 47 |
ProcessStatus | |
100.00% |
1 / 1 |
|
100.00% |
13 / 13 |
25 | |
100.00% |
47 / 47 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
setTtl | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
setMaxTry | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
setMaxProcess | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
setAutoKill | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
verify | |
100.00% |
1 / 1 |
4 | |
100.00% |
12 / 12 |
|||
each | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
count | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
kill | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
getProcess | |
100.00% |
1 / 1 |
5 | |
100.00% |
8 / 8 |
|||
ttlFilter | |
100.00% |
1 / 1 |
3 | |
100.00% |
8 / 8 |
|||
isZombie | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
runningFilter | |
100.00% |
1 / 1 |
2 | |
100.00% |
2 / 2 |
1 | <?php |
2 | |
3 | namespace Qmp\Laravel\AsyncProcessFactory; |
4 | |
5 | use DateTime; |
6 | use Illuminate\Database\Eloquent\Collection as EloquentCollection; |
7 | use Illuminate\Support\Facades\Log; |
8 | use Qmp\Laravel\AsyncProcessFactory\Model\ProcessStatus as ProcessStatusModel; |
9 | |
10 | class ProcessStatus |
11 | { |
12 | /** |
13 | * The max process lifetime |
14 | * |
15 | * @var string |
16 | */ |
17 | protected $ttl = '15 minutes'; |
18 | |
19 | /** |
20 | * The max retry times of the process |
21 | * |
22 | * @var int |
23 | */ |
24 | protected $maxTry = 2; |
25 | |
26 | /** |
27 | * The max process |
28 | * |
29 | * @var int |
30 | */ |
31 | protected $maxProcess = 10; |
32 | |
33 | /** |
34 | * The model that's holds process information |
35 | * |
36 | * @var string |
37 | */ |
38 | protected $model = ProcessStatusModel::class; |
39 | |
40 | /** |
41 | * If process is hanging, kill it |
42 | * |
43 | * @var boolean |
44 | */ |
45 | protected $autoKill = true; |
46 | |
47 | /** |
48 | * Undocumented variable |
49 | * |
50 | * @var [type] |
51 | */ |
52 | protected $command; |
53 | |
54 | /** |
55 | * The authorized filters in proccessVerify method |
56 | * |
57 | * @var array |
58 | */ |
59 | protected $authorizedFilters = ['ttl', 'running']; |
60 | |
61 | /** |
62 | * Class constructor |
63 | * |
64 | * @param string $command |
65 | */ |
66 | public function __construct(string $command) |
67 | { |
68 | $this->command = $command; |
69 | } |
70 | |
71 | /** |
72 | * Set ttl |
73 | * |
74 | * @param string $ttl |
75 | * @return self |
76 | */ |
77 | public function setTtl(string $ttl): self |
78 | { |
79 | $this->ttl = $ttl; |
80 | return $this; |
81 | } |
82 | |
83 | /** |
84 | * Set maxTry |
85 | * |
86 | * @param int $maxTry |
87 | * @return self |
88 | */ |
89 | public function setMaxTry(int $maxTry): self |
90 | { |
91 | $this->maxTry = $maxTry; |
92 | return $this; |
93 | } |
94 | |
95 | /** |
96 | * Set max process |
97 | * |
98 | * @param int $maxProcess |
99 | * @return self |
100 | */ |
101 | public function setMaxProcess(int $maxProcess): self |
102 | { |
103 | $this->maxProcess = $maxProcess; |
104 | return $this; |
105 | } |
106 | |
107 | /** |
108 | * Set autokill |
109 | * |
110 | * @param bool $autoKill |
111 | * @return self |
112 | */ |
113 | public function setAutoKill(bool $autoKill): self |
114 | { |
115 | $this->autoKill = $autoKill; |
116 | return $this; |
117 | } |
118 | |
119 | /** |
120 | * Verify all process and reset those who not pass filters |
121 | * |
122 | * @param array $filters |
123 | * @return self |
124 | */ |
125 | public function verify(array $filters): self |
126 | { |
127 | $this->getProcess()->filter(function ($process) use ($filters) { |
128 | foreach ($filters as $filter) { |
129 | if (in_array($filter, $this->authorizedFilters)) { |
130 | $filter .= 'Filter'; |
131 | if (!$this->{$filter}($process)) { |
132 | return true; |
133 | } |
134 | } |
135 | } |
136 | })->each(function ($process) { |
137 | $process->pid = null; |
138 | $process->try += 1; |
139 | $process->save(); |
140 | }); |
141 | |
142 | return $this; |
143 | } |
144 | |
145 | /** |
146 | * Execute callback on each valid process |
147 | * |
148 | * @param Callable $callback |
149 | * @return void |
150 | */ |
151 | public function each(callable $callback): void |
152 | { |
153 | $this->getProcess(false)->each($callback); |
154 | } |
155 | |
156 | /** |
157 | * Count valid process |
158 | * |
159 | * @return int |
160 | */ |
161 | public function count() |
162 | { |
163 | return $this->getProcess(false)->count(); |
164 | } |
165 | |
166 | /** |
167 | * Kill the given process |
168 | * |
169 | * @param $pid |
170 | * @return boolean |
171 | */ |
172 | public function kill($pid): bool |
173 | { |
174 | if ($this->autoKill) { |
175 | return posix_kill($pid, SIGKILL); |
176 | } |
177 | return false; |
178 | } |
179 | |
180 | /** |
181 | * Get processes base on condition |
182 | * |
183 | * @param boolean $null |
184 | * @return EloquentCollection |
185 | */ |
186 | protected function getProcess(bool $null = true): EloquentCollection |
187 | { |
188 | $models = $this->model::where('command', $this->command)->where('active', 1); |
189 | $models = $this->maxTry ? $models->where('try', '<=', $this->maxTry) : $models; |
190 | |
191 | $collection = $null ? $models->whereNotNull('pid')->get() : $models->whereNull('pid')->get(); |
192 | |
193 | if (!$null) { |
194 | $diff = $this->maxProcess - $this->getProcess()->count(); |
195 | $diff = $diff > -1 ? $diff : 0; |
196 | return $collection->take($diff); |
197 | } |
198 | |
199 | return $collection; |
200 | } |
201 | |
202 | /** |
203 | * Ttl filter |
204 | * @param ProcessStatusModel $model |
205 | * @return boolean |
206 | * @throws \Exception |
207 | */ |
208 | protected function ttlFilter(ProcessStatusModel $model): bool |
209 | { |
210 | $datetime = new datetime($model->updated_at); |
211 | $datetime->modify('+' . $this->ttl); |
212 | |
213 | if (new dateTime() >= $datetime) { |
214 | if (!$this->isZombie($model->pid)) { |
215 | Log::debug(var_export("KILL $model->pid $model->command $model->id", true)); |
216 | $this->kill($model->pid); |
217 | return false; |
218 | } |
219 | } |
220 | |
221 | return true; |
222 | } |
223 | |
224 | /** |
225 | * Check if the process is in zombie state |
226 | * |
227 | * @param integer $pid |
228 | * @return boolean |
229 | */ |
230 | protected function isZombie(int $pid): bool |
231 | { |
232 | return exec('ps axo stat,ppid,pid,comm | grep -w defunct | grep -w ' . $pid) ? true : false; |
233 | } |
234 | |
235 | /** |
236 | * Running filter |
237 | * @param ProcessStatusModel $model |
238 | * @return boolean |
239 | */ |
240 | protected function runningFilter(ProcessStatusModel $model): bool |
241 | { |
242 | $running = posix_getpgid($model->pid) !== false; |
243 | return $running && !$this->isZombie($model->pid); |
244 | } |
245 | } |