Passed
Push — version-matrix ( df0591 )
by Tomas Norre
13:39 queued 08:00
created

ProcessCleanUpHook::crawler_init()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AOE\Crawler\Hooks;
6
7
/*
8
 * (c) 2020 AOE GmbH <[email protected]>
9
 *
10
 * This file is part of the TYPO3 Crawler Extension.
11
 *
12
 * It is free software; you can redistribute it and/or modify it under
13
 * the terms of the GNU General Public License, either version 2
14
 * of the License, or any later version.
15
 *
16
 * For the full copyright and license information, please read the
17
 * LICENSE.txt file that was distributed with this source code.
18
 *
19
 * The TYPO3 project - inspiring people to share!
20
 */
21
22
use AOE\Crawler\Controller\CrawlerController;
23
use AOE\Crawler\Domain\Repository\ProcessRepository;
24
use AOE\Crawler\Domain\Repository\QueueRepository;
25
use TYPO3\CMS\Core\Core\Environment;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
use TYPO3\CMS\Extbase\Object\ObjectManager;
28
29
/**
30
 * Class ProcessCleanUpHook
31
 * @package AOE\Crawler\Hooks
32
 */
33
class ProcessCleanUpHook implements CrawlerHookInterface
34
{
35
    /**
36
     * @var ProcessRepository
37
     */
38
    protected $processRepository;
39
40
    /**
41
     * @var QueueRepository
42
     */
43
    protected $queueRepository;
44
45
    /**
46
     * @var CrawlerController
47
     */
48
    private $crawlerController;
49
50
    /**
51
     * @var array
52
     */
53
    private $extensionSettings;
54
55 14
    public function __construct()
56
    {
57 14
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
58 14
        $this->processRepository = $objectManager->get(ProcessRepository::class);
59 14
        $this->queueRepository = $objectManager->get(QueueRepository::class);
60 14
    }
61
62
    /**
63
     * Main function of process CleanUp Hook.
64
     *
65
     * @param CrawlerController $crawlerController Crawler Lib class
66
     */
67 4
    public function crawler_init(CrawlerController $crawlerController): void
68
    {
69 4
        $this->crawlerController = $crawlerController;
70 4
        $this->extensionSettings = $this->crawlerController->extensionSettings;
71
72
        // Clean Up
73 4
        $this->removeActiveOrphanProcesses();
74 4
        $this->removeActiveProcessesOlderThanOneHour();
75 4
    }
76
77
    /**
78
     * Remove active processes older than one hour
79
     */
80 4
    private function removeActiveProcessesOlderThanOneHour(): void
81
    {
82 4
        $results = $this->processRepository->getActiveProcessesOlderThanOneHour();
83
84 4
        if (! is_array($results)) {
0 ignored issues
show
introduced by
The condition is_array($results) is always true.
Loading history...
85
            return;
86
        }
87 4
        foreach ($results as $result) {
88
            $systemProcessId = (int) $result['system_process_id'];
89
            $processId = $result['process_id'];
90
            if ($systemProcessId > 1) {
91
                if ($this->doProcessStillExists($systemProcessId)) {
92
                    $this->killProcess($systemProcessId);
93
                }
94
                $this->removeProcessFromProcesslist($processId);
95
            }
96
        }
97 4
    }
98
99
    /**
100
     * Removes active orphan processes from process list
101
     */
102 4
    private function removeActiveOrphanProcesses(): void
103
    {
104 4
        $results = $this->processRepository->getActiveOrphanProcesses();
105
106 4
        if (! is_array($results)) {
0 ignored issues
show
introduced by
The condition is_array($results) is always true.
Loading history...
107
            return;
108
        }
109 4
        foreach ($results as $result) {
110
            $processExists = false;
111
            $systemProcessId = (int) $result['system_process_id'];
112
            $processId = $result['process_id'];
113
            if ($systemProcessId > 1) {
114
                $dispatcherProcesses = $this->findDispatcherProcesses();
115
                if (! is_array($dispatcherProcesses) || empty($dispatcherProcesses)) {
116
                    $this->removeProcessFromProcesslist($processId);
117
                    return;
118
                }
119
                foreach ($dispatcherProcesses as $process) {
120
                    $responseArray = $this->createResponseArray($process);
121
                    if ($systemProcessId === (int) $responseArray[1]) {
122
                        $processExists = true;
123
                    };
124
                }
125
                if (! $processExists) {
126
                    $this->removeProcessFromProcesslist($processId);
127
                }
128
            }
129
        }
130 4
    }
131
132
    /**
133
     * Remove a process from processlist
134
     *
135
     * @param string $processId Unique process Id.
136
     */
137 6
    private function removeProcessFromProcesslist($processId): void
138
    {
139 6
        $this->processRepository->removeByProcessId($processId);
140 6
        $this->queueRepository->unsetQueueProcessId($processId);
141 6
    }
142
143
    /**
144
     * Create response array
145
     * Convert string to array with space character as delimiter,
146
     * removes all empty records to have a cleaner array
147
     *
148
     * @param string $string String to create array from
149
     *
150
     * @return array
151
     */
152 4
    private function createResponseArray($string)
153
    {
154 4
        $responseArray = GeneralUtility::trimExplode(' ', $string, true);
155 4
        return array_values($responseArray);
156
    }
157
158
    /**
159
     * Check if the process still exists
160
     *
161
     * @param int $pid Process id to be checked.
162
     *
163
     * @return bool
164
     * @codeCoverageIgnore
165
     */
166
    private function doProcessStillExists($pid)
167
    {
168
        $doProcessStillExists = false;
169
        if (! Environment::isWindows()) {
170
            // Not windows
171
            if (file_exists('/proc/' . $pid)) {
172
                $doProcessStillExists = true;
173
            }
174
        } else {
175
            // Windows
176
            exec('tasklist | find "' . $pid . '"', $returnArray, $returnValue);
177
            if (count($returnArray) > 0 && stripos($returnArray[0], 'php') !== false) {
178
                $doProcessStillExists = true;
179
            }
180
        }
181
        return $doProcessStillExists;
182
    }
183
184
    /**
185
     * Kills a process
186
     *
187
     * @param int $pid Process id to kill
188
     *
189
     * @codeCoverageIgnore
190
     */
191
    private function killProcess($pid): void
192
    {
193
        if (! Environment::isWindows()) {
194
            // Not windows
195
            posix_kill($pid, 9);
196
        } else {
197
            // Windows
198
            exec('taskkill /PID ' . $pid);
199
        }
200
    }
201
202
    /**
203
     * Find dispatcher processes
204
     *
205
     * @return array
206
     * @codeCoverageIgnore
207
     */
208
    private function findDispatcherProcesses()
209
    {
210
        $returnArray = [];
211
        if (! Environment::isWindows()) {
212
            // Not windows
213
            exec('ps aux | grep \'typo3 crawler:processQueue\'', $returnArray, $returnValue);
214
        } else {
215
            // Windows
216
            exec('tasklist | find \'typo3 crawler:processQueue\'', $returnArray, $returnValue);
217
        }
218
        return $returnArray;
219
    }
220
}
221