33 * Copyright © Magento, Inc. All rights reserved.
44 * See COPYING.txt for license details.
55 */
6+ declare (strict_types=1 );
7+
68namespace Magento \Deploy \Process ;
79
810use Magento \Deploy \Package \Package ;
911use Magento \Deploy \Service \DeployPackage ;
1012use Magento \Framework \App \ResourceConnection ;
11- use Psr \Log \LoggerInterface ;
1213use Magento \Framework \App \State as AppState ;
1314use Magento \Framework \Locale \ResolverInterface as LocaleResolver ;
15+ use Psr \Log \LoggerInterface ;
1416
1517/**
1618 * Deployment Queue
@@ -125,6 +127,8 @@ public function __construct(
125127 }
126128
127129 /**
130+ * Adds deployment package.
131+ *
128132 * @param Package $package
129133 * @param Package[] $dependencies
130134 * @return bool true on success
@@ -140,6 +144,8 @@ public function add(Package $package, array $dependencies = [])
140144 }
141145
142146 /**
147+ * Returns packages array.
148+ *
143149 * @return Package[]
144150 */
145151 public function getPackages ()
@@ -159,9 +165,11 @@ public function process()
159165 $ packages = $ this ->packages ;
160166 while (count ($ packages ) && $ this ->checkTimeout ()) {
161167 foreach ($ packages as $ name => $ packageJob ) {
168+ // Unsets each member of $packages array (passed by reference) as each is executed
162169 $ this ->assertAndExecute ($ name , $ packages , $ packageJob );
163170 }
164171 $ this ->logger ->info ('. ' );
172+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
165173 sleep (3 );
166174 foreach ($ this ->inProgress as $ name => $ package ) {
167175 if ($ this ->isDeployed ($ package )) {
@@ -182,8 +190,6 @@ public function process()
182190 * @param array $packages
183191 * @param array $packageJob
184192 * @return void
185- *
186- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
187193 */
188194 private function assertAndExecute ($ name , array & $ packages , array $ packageJob )
189195 {
@@ -207,13 +213,23 @@ private function assertAndExecute($name, array & $packages, array $packageJob)
207213 }
208214 }
209215 }
216+ $ this ->executePackage ($ package , $ name , $ packages , $ dependenciesNotFinished );
217+ }
210218
219+ /**
220+ * Executes deployment package.
221+ *
222+ * @param Package $package
223+ * @param string $name
224+ * @param array $packages
225+ * @param bool $dependenciesNotFinished
226+ * @return void
227+ */
228+ private function executePackage (Package $ package , string $ name , array &$ packages , bool $ dependenciesNotFinished )
229+ {
211230 if (!$ dependenciesNotFinished
212231 && !$ this ->isDeployed ($ package )
213- && (
214- $ this ->maxProcesses < 2
215- || (count ($ this ->inProgress ) < $ this ->maxProcesses )
216- )
232+ && ($ this ->maxProcesses < 2 || (count ($ this ->inProgress ) < $ this ->maxProcesses ))
217233 ) {
218234 unset($ packages [$ name ]);
219235 $ this ->execute ($ package );
@@ -234,6 +250,7 @@ private function awaitForAllProcesses()
234250 }
235251 }
236252 $ this ->logger ->info ('. ' );
253+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
237254 sleep (5 );
238255 }
239256 if ($ this ->isCanBeParalleled ()) {
@@ -243,6 +260,8 @@ private function awaitForAllProcesses()
243260 }
244261
245262 /**
263+ * Checks if can be parallel.
264+ *
246265 * @return bool
247266 */
248267 private function isCanBeParalleled ()
@@ -251,9 +270,12 @@ private function isCanBeParalleled()
251270 }
252271
253272 /**
273+ * Executes the process.
274+ *
254275 * @param Package $package
255276 * @return bool true on success for main process and exit for child process
256277 * @SuppressWarnings(PHPMD.ExitExpression)
278+ * @throws \RuntimeException
257279 */
258280 private function execute (Package $ package )
259281 {
@@ -281,6 +303,7 @@ function () use ($package) {
281303 );
282304
283305 if ($ this ->isCanBeParalleled ()) {
306+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
284307 $ pid = pcntl_fork ();
285308 if ($ pid === -1 ) {
286309 throw new \RuntimeException ('Unable to fork a new process ' );
@@ -295,6 +318,7 @@ function () use ($package) {
295318 // process child process
296319 $ this ->inProgress = [];
297320 $ this ->deployPackageService ->deploy ($ package , $ this ->options , true );
321+ // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
298322 exit (0 );
299323 } else {
300324 $ this ->deployPackageService ->deploy ($ package , $ this ->options );
@@ -303,19 +327,50 @@ function () use ($package) {
303327 }
304328
305329 /**
330+ * Checks if package is deployed.
331+ *
306332 * @param Package $package
307333 * @return bool
308334 */
309335 private function isDeployed (Package $ package )
310336 {
311337 if ($ this ->isCanBeParalleled ()) {
312338 if ($ package ->getState () === null ) {
313- $ pid = pcntl_waitpid ($ this ->getPid ($ package ), $ status , WNOHANG );
314- if ($ pid === $ this ->getPid ($ package )) {
339+ $ pid = $ this ->getPid ($ package );
340+
341+ // When $pid comes back as null the child process for this package has not yet started; prevents both
342+ // hanging until timeout expires (which was behaviour in 2.2.x) and the type error from strict_types
343+ if ($ pid === null ) {
344+ return false ;
345+ }
346+
347+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
348+ $ result = pcntl_waitpid ($ pid , $ status , WNOHANG );
349+ if ($ result === $ pid ) {
315350 $ package ->setState (Package::STATE_COMPLETED );
351+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
352+ $ exitStatus = pcntl_wexitstatus ($ status );
353+
354+ $ this ->logger ->info (
355+ "Exited: " . $ package ->getPath () . "(status: $ exitStatus) " ,
356+ [
357+ 'process ' => $ package ->getPath (),
358+ 'status ' => $ exitStatus ,
359+ ]
360+ );
316361
317362 unset($ this ->inProgress [$ package ->getPath ()]);
363+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
318364 return pcntl_wexitstatus ($ status ) === 0 ;
365+ } elseif ($ result === -1 ) {
366+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
367+ $ errno = pcntl_errno ();
368+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
369+ $ strerror = pcntl_strerror ($ errno );
370+
371+ throw new \RuntimeException (
372+ "Error encountered checking child process status (PID: $ pid): $ strerror (errno: $ errno) "
373+ );
319374 }
320375 return false ;
321376 }
@@ -324,17 +379,19 @@ private function isDeployed(Package $package)
324379 }
325380
326381 /**
382+ * Returns process ID or null if not found.
383+ *
327384 * @param Package $package
328385 * @return int|null
329386 */
330387 private function getPid (Package $ package )
331388 {
332- return isset ($ this ->processIds [$ package ->getPath ()])
333- ? $ this ->processIds [$ package ->getPath ()]
334- : null ;
389+ return $ this ->processIds [$ package ->getPath ()] ?? null ;
335390 }
336391
337392 /**
393+ * Checks timeout.
394+ *
338395 * @return bool
339396 */
340397 private function checkTimeout ()
@@ -347,14 +404,31 @@ private function checkTimeout()
347404 *
348405 * Protect against zombie process
349406 *
407+ * @throws \RuntimeException
408+ * @SuppressWarnings(PHPMD.UnusedLocalVariable)
350409 * @return void
351410 */
352411 public function __destruct ()
353412 {
354413 foreach ($ this ->inProgress as $ package ) {
355- if (pcntl_waitpid ($ this ->getPid ($ package ), $ status ) === -1 ) {
414+ $ pid = $ this ->getPid ($ package );
415+ $ this ->logger ->info (
416+ "Reaping child process: {$ package ->getPath ()} (PID: $ pid) " ,
417+ [
418+ 'process ' => $ package ->getPath (),
419+ 'pid ' => $ pid ,
420+ ]
421+ );
422+
423+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
424+ if (pcntl_waitpid ($ pid , $ status ) === -1 ) {
425+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
426+ $ errno = pcntl_errno ();
427+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
428+ $ strerror = pcntl_strerror ($ errno );
429+
356430 throw new \RuntimeException (
357- ' Error while waiting for package deployed: ' . $ this -> getPid ( $ package ) . ' ; Status: ' . $ status
431+ " Error encountered waiting for child process (PID: $ pid ): $ strerror (errno: $ errno ) "
358432 );
359433 }
360434 }
0 commit comments