Completed
Push — master ( bedd0e...7ec89a )
by Aimeos
08:44
created

lib/mwlib/src/MW/Process/Pcntl.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2017
6
 * @package MW
7
 * @subpackage Process
8
 */
9
10
11
namespace Aimeos\MW\Process;
12
13
14
/**
15
 * Posix process control for parallel processing classes
16
 *
17
 * @package MW
18
 * @subpackage Process
19
 */
20
class Pcntl implements Iface
21
{
22
	private $max;
23
	private $prio;
24
	private $list = [];
25
26
27
	/**
28
	 * Initializes the object and sets up the signal handler
29
	 *
30
	 * @param integer $max Maximum number of tasks allowed to run in parallel
31
	 * @param integer $prio Task priority from -20 (high) to 20 (low)
32
	 * @throws \Aimeos\MW\Process\Exception If setting up the signal handler failed
33
	 */
34
	public function __construct( $max = 4, $prio = 19 )
35
	{
36
		$this->max = $max;
37
		$this->prio = $prio;
38
39
		$handler = function( $signo )
40
		{
41
			foreach( $this->list as $pid => $entry )
42
			{
43
				posix_kill( $pid, $signo );
44
				pcntl_waitpid( $pid );
45
			}
46
47
			exit( 0 );
48
		};
49
50
		if( pcntl_signal( SIGTERM, $handler ) === false ) {
51
			throw new Exception( 'Unable to install signal handler: ' . pcntl_strerror( pcntl_get_last_error() ) );
52
		}
53
	}
54
55
56
	/**
57
	 * Checks if processing tasks in parallel is available
58
	 *
59
	 * @return boolean True if available, false if not
60
	 */
61
	public function isAvailable()
62
	{
63
		if( php_sapi_name() === 'cli' && function_exists( 'pcntl_fork' ) === true ) {
64
			return true;
65
		}
66
67
		return false;
68
	}
69
70
71
	/**
72
	 * Starts a new task by executing the given anonymous function
73
	 *
74
	 * @param \Closure $fcn Anonymous function to execute
75
	 * @param array $data List of parameters that is passed to the closure function
76
	 * @param boolean $restart True if the task should be restarted if it fails (only once)
77
	 * @return void
0 ignored issues
show
Should the return type not be null|Pcntl?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
78
	 * @throws \Aimeos\MW\Process\Exception If starting the new task failed
79
	 */
80
	public function start( \Closure $fcn, array $data, $restart = false )
81
	{
82
		while( count( $this->list ) >= $this->max ) {
83
			$this->waitOne();
84
		}
85
86
		if( ( $pid = pcntl_fork() ) === -1 ) {
87
			throw new Exception( 'Unable to fork new process: ' . pcntl_strerror( pcntl_get_last_error() ) );
88
		}
89
90
		if( $pid === 0 ) // child process
91
		{
92
			pcntl_setpriority( $this->prio );
93
94
			try {
95
				call_user_func_array( $fcn, $data );
96
			} catch( \Exception $e ) {
97
				exit( 1 );
98
			}
99
100
			exit( 0 );
101
		}
102
103
		$this->list[$pid] = [$fcn, $data, $restart];
104
105
		return $this;
106
	}
107
108
109
	/**
110
	 * Waits for the running tasks until all have finished
111
	 *
112
	 * @return void
0 ignored issues
show
Should the return type not be Pcntl?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
113
	 */
114
	public function wait()
115
	{
116
		while( !empty( $this->list ) ) {
117
			$this->waitOne();
118
		}
119
120
		return $this;
121
	}
122
123
124
	/**
125
	 * Waits for the next running tasks to finish
126
	 *
127
	 * @return void
128
	 * @throws \Aimeos\MW\Process\Exception If an error occurs or the task exited with an error
129
	 */
130
	protected function waitOne()
131
	{
132
		$status = -1;
133
134
		if( ( $pid = pcntl_wait( $status ) ) === -1 ) {
135
			throw new Exception( 'Unable to wait for child process: ' . pcntl_strerror( pcntl_get_last_error() ) );
136
		}
137
138
		list( $fcn, $data, $restart ) = $this->list[$pid];
139
		unset( $this->list[$pid] );
140
141
		if( $status > 0 )
142
		{
143
			if( $restart === false ) {
144
				throw new Exception( sprintf( 'Process (PID "%1$s") failed with status "%2$s"', $pid, $status ) );
145
			}
146
147
			$this->start( $fcn, $data, false );
148
		}
149
	}
150
}