Completed
Push — issue/92 ( ebb8c8...de325a )
by Tomas Norre
09:56
created

ProcessCleanUpHook::killProcess()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 10
ccs 0
cts 0
cp 0
crap 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace AOE\Crawler\Hooks;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2016 AOE GmbH <[email protected]>
8
 *
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 3 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use AOE\Crawler\Controller\CrawlerController;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
31
/**
32
 * Class ProcessCleanUpHook
33
 * @package AOE\Crawler\Hooks
34
 */
35
class ProcessCleanUpHook
36
{
37
    /**
38
     * @var CrawlerController
39
     */
40
    private $crawlerController;
41
42
    /**
43
     * @var array
44
     */
45
    private $extensionSettings;
46
47
    /**
48
     * Main function of process CleanUp Hook.
49
     *
50
     * @param CrawlerController $crawlerController Crawler Lib class
51
     *
52
     * @return void
53
     */
54
    public function crawler_init(CrawlerController $crawlerController)
55
    {
56
        $this->crawlerController = $crawlerController;
57
        $this->extensionSettings = $this->crawlerController->extensionSettings;
58
59
        // Clean Up
60
        $this->removeActiveOrphanProcesses();
61
        $this->removeActiveProcessesOlderThanOneHour();
62
    }
63
64
    /**
65
     * Remove active processes older than one hour
66
     *
67
     * @return void
68
     */
69
    private function removeActiveProcessesOlderThanOneHour()
70
    {
71
        $results = $this->getActiveProcessesOlderThanOneOHour();
72
73
        if (!is_array($results)) {
74
            return;
75
        }
76
        foreach ($results as $result) {
77
            $systemProcessId = (int)$result['system_process_id'];
78
            $processId = $result['process_id'];
79
            if ($systemProcessId > 1) {
80
                if ($this->doProcessStillExists($systemProcessId)) {
81
                    $this->killProcess($systemProcessId);
82
                }
83
                $this->removeProcessFromProcesslist($processId);
84
            }
85
        }
86
    }
87
88
    /**
89
     * Removes active orphan processes from process list
90
     *
91
     * @return void
92
     */
93
    private function removeActiveOrphanProcesses()
94
    {
95
        $results = $this->getActiveOrphanProcesses();
96
97
        if (!is_array($results)) {
98
            return;
99
        }
100
        foreach ($results as $result) {
101
            $processExists = false;
102
            $systemProcessId = (int)$result['system_process_id'];
103
            $processId = $result['process_id'];
104
            if ($systemProcessId > 1) {
105
                $dispatcherProcesses = $this->findDispatcherProcesses();
106
                if (!is_array($dispatcherProcesses) || empty($dispatcherProcesses)) {
107
                    $this->removeProcessFromProcesslist($processId);
108
                    return;
109
                }
110
                foreach ($dispatcherProcesses as $process) {
111
                    $responseArray = $this->createResponseArray($process);
112
                    if ($systemProcessId === (int)$responseArray[1]) {
113
                        $processExists = true;
114
                    };
115
                }
116
                if (!$processExists) {
117
                    $this->removeProcessFromProcesslist($processId);
118
                }
119
            }
120
        }
121
    }
122
123
    /**
124
     * Remove a process from processlist
125
     *
126
     * @param string $processId Unique process Id.
127
     *
128
     * @return void
129
     */
130 3
    private function removeProcessFromProcesslist($processId)
131
    {
132 3
        $this->getDatabaseConnection()->exec_DELETEquery(
133 3
            'tx_crawler_process',
134 3
            'process_id = ' . $this->getDatabaseConnection()->fullQuoteStr($processId, 'tx_crawler_process')
135
        );
136
137 3
        $this->getDatabaseConnection()->exec_UPDATEquery(
138 3
            'tx_crawler_queue',
139 3
            'process_id = ' . $this->getDatabaseConnection()->fullQuoteStr($processId, 'tx_crawler_queue'),
140 3
            ['process_id' => '']
141
        );
142 3
    }
143
144
    /**
145
     * Create response array
146
     * Convert string to array with space character as delimiter,
147
     * removes all empty records to have a cleaner array
148
     *
149
     * @param string $string String to create array from
150
     *
151
     * @return array
152
     *
153
     */
154 2
    private function createResponseArray($string)
155
    {
156 2
        $responseArray = GeneralUtility::trimExplode(' ', $string, true);
157 2
        $responseArray = array_values($responseArray);
158 2
        return $responseArray;
159
    }
160
161
    /**
162
     * Check if the process still exists
163
     *
164
     * @param int $pid Process id to be checked.
165
     *
166
     * @return bool
167
     * @codeCoverageIgnore
168
     */
169
    private function doProcessStillExists($pid)
170
    {
171
        $doProcessStillExists = false;
172
        if (!$this->isOsWindows()) {
173
            // Not windows
174
            if (file_exists('/proc/' . $pid)) {
175
                $doProcessStillExists = true;
176
            }
177
        } else {
178
            // Windows
179
            exec('tasklist | find "' . $pid . '"', $returnArray, $returnValue);
180
            if (count($returnArray) > 0 && preg_match('/php/i', $returnValue[0])) {
181
                $doProcessStillExists = true;
182
            }
183
        }
184
        return $doProcessStillExists;
185
    }
186
187
    /**
188
     * Kills a process
189
     *
190
     * @param int $pid Process id to kill
191
     *
192
     * @return void
193
     * @codeCoverageIgnore
194
     */
195
    private function killProcess($pid)
196
    {
197
        if (!$this->isOsWindows()) {
198
            // Not windows
199
            posix_kill($pid, 9);
200
        } else {
201
            // Windows
202
            exec('taskkill /PID ' . $pid);
203
        }
204
    }
205
206
    /**
207
     * Find dispatcher processes
208
     *
209
     * @return array
210
     * @codeCoverageIgnore
211
     */
212
    private function findDispatcherProcesses()
213
    {
214
        $returnArray = [];
215
        if (!$this->isOsWindows()) {
216
            // Not windows
217
            exec('ps aux | grep \'cli_dispatcher\'', $returnArray, $returnValue);
218
        } else {
219
            // Windows
220
            exec('tasklist | find \'cli_dispatcher\'', $returnArray, $returnValue);
221
        }
222
        return $returnArray;
223
    }
224
225
    /**
226
     * Check if OS is Windows
227
     *
228
     * @return bool
229
     * @codeCoverageIgnore
230
     */
231
    private function isOsWindows()
232
    {
233
        if (TYPO3_OS === 'WIN') {
234
            return true;
235
        }
236
        return false;
237
    }
238
239
    /**
240
     * @return \TYPO3\CMS\Core\Database\DatabaseConnection
241
     * @codeCoverageIgnore
242
     */
243
    private function getDatabaseConnection()
244
    {
245
        return $GLOBALS['TYPO3_DB'];
246
    }
247
248
    /**
249
     * @return array|null
250
     */
251 1
    private function getActiveProcessesOlderThanOneOHour()
252
    {
253 1
        $results = $this->getDatabaseConnection()->exec_SELECTgetRows(
254 1
            'process_id, system_process_id',
255 1
            'tx_crawler_process',
256 1
            'ttl <= ' . intval(time() - $this->extensionSettings['processMaxRunTime'] - 3600) . ' AND active = 1'
257
        );
258
259 1
        return $results;
260
    }
261
262
    /**
263
     * @return array|null
264
     */
265 1
    private function getActiveOrphanProcesses()
266
    {
267 1
        $results = $this->getDatabaseConnection()->exec_SELECTgetRows(
268 1
            'process_id, system_process_id',
269 1
            'tx_crawler_process',
270 1
            'ttl <= ' . intval(time() - $this->extensionSettings['processMaxRunTime']) . ' AND active = 1'
271
        );
272
273 1
        return $results;
274
    }
275
}
276