Passed
Pull Request — master (#23)
by Nikolay
09:42 queued 03:08
created

Processes::mwExecBgWithTimeout()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
b 0
f 0
dl 0
loc 12
rs 10
cc 3
nc 2
nop 3
1
<?php
2
/*
3
 * Copyright (C) MIKO LLC - All Rights Reserved
4
 * Unauthorized copying of this file, via any medium is strictly prohibited
5
 * Proprietary and confidential
6
 * Written by Nikolay Beketov, 11 2020
7
 *
8
 */
9
10
namespace MikoPBX\Core\System;
11
12
13
use MikoPBX\Core\Workers\Cron\WorkerSafeScriptsCore;
14
use Phalcon\Di;
15
16
class Processes
17
{
18
19
    /**
20
     * Kills process/daemon by name
21
     *
22
     * @param $procName
23
     *
24
     * @return int|null
25
     */
26
    public static function killByName($procName): ?int
27
    {
28
        $killallPath = Util::which('killall');
29
30
        return self::mwExec($killallPath . ' ' . escapeshellarg($procName));
31
    }
32
33
    /**
34
     * Executes command exec().
35
     *
36
     * @param $command
37
     * @param $outArr
38
     * @param $retVal
39
     *
40
     * @return int
41
     */
42
    public static function mwExec($command, &$outArr = null, &$retVal = null): int
43
    {
44
        $retVal = 0;
45
        $outArr = [];
46
        $di     = Di::getDefault();
47
48
        if ($di !== null && $di->getShared('config')->path('core.debugMode')) {
49
            echo "mwExec(): $command\n";
50
        } else {
51
            exec("$command 2>&1", $outArr, $retVal);
52
        }
53
54
        return $retVal;
55
    }
56
57
    /**
58
     * Executes command exec() as background process with an execution timeout.
59
     *
60
     * @param        $command
61
     * @param int    $timeout
62
     * @param string $logname
63
     */
64
    public static function mwExecBgWithTimeout($command, $timeout = 4, $logname = '/dev/null'): void
65
    {
66
        $di = Di::getDefault();
67
68
        if ($di !== null && $di->getShared('config')->path('core.debugMode')) {
69
            echo "mwExecBg(): $command\n";
70
71
            return;
72
        }
73
        $nohupPath   = Util::which('nohup');
74
        $timeoutPath = Util::which('timeout');
75
        exec("{$nohupPath} {$timeoutPath} -t {$timeout} {$command} > {$logname} 2>&1 &");
76
    }
77
78
    /**
79
     * Executes multiple commands.
80
     *
81
     * @param        $arr_cmds
82
     * @param array  $out
83
     * @param string $logname
84
     */
85
    public static function mwExecCommands($arr_cmds, &$out = [], $logname = ''): void
86
    {
87
        $out = [];
88
        foreach ($arr_cmds as $cmd) {
89
            $out[]   = "$cmd;";
90
            $out_cmd = [];
91
            self::mwExec($cmd, $out_cmd);
92
            $out = array_merge($out, $out_cmd);
93
        }
94
95
        if ($logname !== '') {
96
            $result = implode("\n", $out);
97
            file_put_contents("/tmp/{$logname}_commands.log", $result);
98
        }
99
    }
100
101
    /**
102
     * Restart all workers in separate process,
103
     * we use this method after module install or delete
104
     */
105
    public static function restartAllWorkers(): void
106
    {
107
        $workerSafeScriptsPath = Util::getFilePathByClassName(WorkerSafeScriptsCore::class);
108
        $phpPath               = Util::which('php');
109
        $WorkerSafeScripts     = "{$phpPath} -f {$workerSafeScriptsPath} restart > /dev/null 2> /dev/null";
110
        self::mwExec($WorkerSafeScripts);
111
    }
112
113
    /**
114
     * Process PHP workers
115
     *
116
     * @param string $className
117
     * @param string $paramForPHPWorker
118
     * @param string $action
119
     */
120
    public static function processPHPWorker(
121
        string $className,
122
        string $paramForPHPWorker = 'start',
123
        string $action = 'restart'
124
    ): void {
125
        $workerPath = Util::getFilePathByClassName($className);
126
        if (empty($workerPath)) {
127
            return;
128
        }
129
        $command         = "php -f {$workerPath}";
130
        $path_kill       = Util::which('kill');
131
        $activeProcesses = self::getPidOfProcess($className);
132
        $processes       = explode(' ', $activeProcesses);
133
        if (empty($processes[0])) {
134
            array_shift($processes);
135
        }
136
        $currentProcCount = count($processes);
137
138
        if ( ! class_exists($className)) {
139
            return;
140
        }
141
        $workerObject    = new $className();
142
        $neededProcCount = $workerObject->maxProc;
143
144
        switch ($action) {
145
            case 'restart':
146
                // Stop all old workers
147
                if ($activeProcesses !== '') {
148
                    self::mwExec("{$path_kill} SIGUSR1 {$activeProcesses}  > /dev/null 2>&1 &");
149
                    self::mwExecBgWithTimeout("{$path_kill} SIGTERM {$activeProcesses}", 10);
150
                    $currentProcCount = 0;
151
                }
152
153
                // Start new processes
154
                while ($currentProcCount < $neededProcCount) {
155
                    self::mwExecBg("{$command} {$paramForPHPWorker}");
156
                    $currentProcCount++;
157
                }
158
159
                break;
160
            case 'stop':
161
                if ($activeProcesses !== '') {
162
                    self::mwExec("{$path_kill} SIGUSR1 {$activeProcesses}  > /dev/null 2>&1 &");
163
                    self::mwExecBgWithTimeout("{$path_kill} SIGTERM {$activeProcesses}", 10);
164
                }
165
                break;
166
            case 'start':
167
                if ($currentProcCount === $neededProcCount) {
168
                    return;
169
                } elseif ($neededProcCount > $currentProcCount) {
170
                    // Start additional processes
171
                    while ($currentProcCount < $neededProcCount) {
172
                        self::mwExecBg("{$command} {$paramForPHPWorker}");
173
                        $currentProcCount++;
174
                    }
175
                } elseif ($currentProcCount > $neededProcCount) {
176
                    // Find redundant processes
177
                    $countProc4Kill = $neededProcCount - $currentProcCount;
178
                    // Send SIGUSR1 command to them
179
                    while ($countProc4Kill >= 0) {
180
                        if ( ! isset($processes[$countProc4Kill])) {
181
                            break;
182
                        }
183
                        // Kill old processes with timeout, maybe it is soft restart and worker die without any help
184
                        self::mwExec("{$path_kill} SIGUSR1 {$processes[$countProc4Kill]}  > /dev/null 2>&1 &");
185
                        self::mwExecBgWithTimeout("{$path_kill} SIGTERM {$processes[$countProc4Kill]}", 10);
186
                        $countProc4Kill--;
187
                    }
188
                }
189
                break;
190
            default:
191
        }
192
    }
193
194
    /**
195
     * Возвращает PID процесса по его имени.
196
     *
197
     * @param        $name
198
     * @param string $exclude
199
     *
200
     * @return string
201
     */
202
    public static function getPidOfProcess($name, $exclude = ''): string
203
    {
204
        $path_ps   = Util::which('ps');
205
        $path_grep = Util::which('grep');
206
        $path_awk  = Util::which('awk');
207
208
        $name       = addslashes($name);
209
        $filter_cmd = '';
210
        if ( ! empty($exclude)) {
211
            $filter_cmd = "| $path_grep -v " . escapeshellarg($exclude);
212
        }
213
        $out = [];
214
        self::mwExec(
215
            "{$path_ps} -A -o 'pid,args' {$filter_cmd} | {$path_grep} '{$name}' | {$path_grep} -v grep | {$path_awk} ' {print $1} '",
216
            $out
217
        );
218
219
        return trim(implode(' ', $out));
220
    }
221
222
    /**
223
     * Executes command exec() as background process.
224
     *
225
     * @param $command
226
     * @param $out_file
227
     * @param $sleep_time
228
     */
229
    public static function mwExecBg($command, $out_file = '/dev/null', $sleep_time = 0): void
230
    {
231
        $nohupPath = Util::which('nohup');
232
        $shPath    = Util::which('sh');
233
        $rmPath    = Util::which('rm');
234
        $sleepPath = Util::which('sleep');
235
        if ($sleep_time > 0) {
236
            $filename = '/tmp/' . time() . '_noop.sh';
237
            file_put_contents($filename, "{$sleepPath} {$sleep_time}; {$command}; {$rmPath} -rf {$filename}");
238
            $noop_command = "{$nohupPath} {$shPath} {$filename} > {$out_file} 2>&1 &";
239
        } else {
240
            $noop_command = "{$nohupPath} {$command} > {$out_file} 2>&1 &";
241
        }
242
        exec($noop_command);
243
    }
244
245
    /**
246
     * Manages a daemon/worker process
247
     * Returns process statuses by name of it
248
     *
249
     * @param $cmd
250
     * @param $param
251
     * @param $proc_name
252
     * @param $action
253
     * @param $out_file
254
     *
255
     * @return array | bool
256
     */
257
    public static function processWorker($cmd, $param, $proc_name, $action, $out_file = '/dev/null')
258
    {
259
        $path_kill  = Util::which('kill');
260
        $path_nohup = Util::which('nohup');
261
262
        $WorkerPID = self::getPidOfProcess($proc_name);
263
264
        switch ($action) {
265
            case 'status':
266
                $status = ($WorkerPID !== '') ? 'Started' : 'Stoped';
267
268
                return ['status' => $status, 'app' => $proc_name, 'PID' => $WorkerPID];
269
            case 'restart':
270
                if ($WorkerPID !== '') {
271
                    self::mwExec("{$path_kill} -9 {$WorkerPID}  > /dev/null 2>&1 &");
272
                }
273
                self::mwExec("{$path_nohup} {$cmd} {$param}  > {$out_file} 2>&1 &");
274
                break;
275
            case 'stop':
276
                if ($WorkerPID !== '') {
277
                    self::mwExec("{$path_kill} -9 {$WorkerPID}  > /dev/null 2>&1 &");
278
                }
279
                break;
280
            case 'start':
281
                if ($WorkerPID === '') {
282
                    self::mwExec("{$path_nohup} {$cmd} {$param}  > {$out_file} 2>&1 &");
283
                }
284
                break;
285
            default:
286
        }
287
288
        return true;
289
    }
290
291
}