├── .gitignore ├── CHANGELOG.md ├── Changes ├── README.md ├── composer.json ├── examples └── 01-sleep.php ├── lib └── Prefork.php ├── phpunit.xml.dist └── tests ├── PreforkTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | .DS_Store 4 | ._.DS_Store 5 | .idea 6 | composer.lock 7 | phpunit.xml 8 | vendor 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ======== 3 | 4 | ## 0.1.0 2014-05-01 5 | 6 | * Composerized 7 | * Tidy up README.md and example scripts to compliant with composer 8 | * Port of the Perl module [Parallel::Prefork](https://metacpan.org/pod/release/KAZUHO/Parallel-Prefork-0.05/lib/Parallel/Prefork.pm) 9 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | 0.01 - Wed Mar 18 01:52:58 JST 2010 2 | - initial release 3 | - port of Parallel::Prefork 0.05 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Parallel\Prefork 2 | ======== 3 | 4 | ## NAME 5 | 6 | Parallel\Prefork - A simple prefork server framework 7 | 8 | ## SYNOPSIS 9 | 10 | ```php 11 | 5, 18 | 'trap_signals' => [ 19 | SIGHUP => SIGTERM, 20 | SIGTERM => SIGTERM, 21 | ], 22 | ]); 23 | 24 | while ($pp->signalReceived() !== SIGTERM) { 25 | if ($pp->start()) { 26 | continue; 27 | } 28 | 29 | // ... do some work within the child process ... 30 | 31 | $pp->finish(); 32 | } 33 | 34 | $pp->waitAllChildren(); 35 | ``` 36 | 37 | ## INSTALLATION 38 | 39 | To install this package into your project via composer, add the following snippet to your `composer.json`. Then run `composer install`. 40 | 41 | ``` 42 | "require": { 43 | "travail/parallel-prefork": "dev-master" 44 | } 45 | ``` 46 | 47 | If you want to install from github, add the following: 48 | 49 | ``` 50 | "repositories": [ 51 | { 52 | "type": "vcs", 53 | "url": "git@github.com:travail/php-Parallel-Prefork.git" 54 | } 55 | ] 56 | ``` 57 | 58 | ## DESCRIPTION 59 | 60 | `Parallel\Prefork` supports graceful shutdown and run-time reconfiguration. 61 | 62 | ## DEPENDENCIES 63 | 64 | * posix 65 | * pcntl 66 | 67 | ## METHODS 68 | 69 | ### __construct 70 | 71 | ```php 72 | Parallel\Prefork __construct([ 73 | 'max_workers' => int $max_workers, 74 | 'err_respawn_interval' => int $err_respawn_interval, 75 | 'trap_signals' => [int $signal_trapped_in_parent_process => int $signal_sent_to_child_processes], 76 | ]) 77 | ``` 78 | 79 | Instantiation. Takes an array as an argument. Recognized parameters are as follows. 80 | 81 | #### max_workers 82 | 83 | Number of worker processes (default: 3) 84 | 85 | #### trap_signals 86 | 87 | Array of signals to be trapped. Manager process will trap the signals listed in the keys of the array, and send the signal specified in the associated value (if any) to all worker processes. 88 | 89 | #### err_respawn_interval 90 | 91 | Number of seconds to deter spawning of child processes after a worker exits abnormally (default: 1) 92 | 93 | ### start 94 | 95 | ```php 96 | bool start() 97 | ``` 98 | 99 | The main routine. Returns undef in child processes. Returns a `true` within manager process upon receiving a signal specified in the `trapSignals` array. 100 | 101 | ### finish 102 | 103 | ```php 104 | void finish(int $exit_code) 105 | ``` 106 | 107 | Child processes should call this function for termination. Takes exit code as an optional argument. Only usable from child processes. 108 | 109 | ### signalAllChildren 110 | 111 | ```php 112 | void signalAllChildren(int $signal) 113 | ``` 114 | 115 | Sends signal to all worker processes. Only usable from manager process. 116 | 117 | ### waitAllChildren 118 | 119 | ```php 120 | void waitAllChildren() 121 | ``` 122 | 123 | Blocks until all worker processes exit. Only usable from manager process. 124 | 125 | ### signalReceived 126 | 127 | ```php 128 | int signalReceived() 129 | ``` 130 | 131 | Returns a signal manager process trapped. 132 | 133 | ### THANKS TO 134 | 135 | [Kazuho Oku](https://metacpan.org/pod/release/KAZUHO/Parallel-Prefork-0.05/lib/Parallel/Prefork.pm) 136 | 137 | ### AUTHOR 138 | 139 | travail 140 | 141 | ### LICENSE 142 | 143 | This library is free software. You can redistribute it and/or modify it under the same terms as PHP itself. 144 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "travail/parallel-prefork", 3 | "description": "A simple prefork server framework", 4 | "license": "PHP-3.0", 5 | "minimum-stability": "dev", 6 | "require": { 7 | "php": ">=5.3", 8 | "ext-posix": "*", 9 | "ext-pcntl": "*" 10 | }, 11 | "require-dev": { 12 | "phpunit/phpunit": "3.7.*" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "Parallel\\": "lib" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/01-sleep.php: -------------------------------------------------------------------------------- 1 | 5, 14 | 'trap_signals' => array( 15 | SIGHUP => SIGTERM, 16 | SIGTERM => SIGTERM, 17 | ), 18 | )); 19 | while ($pp->signalReceived() !== SIGTERM) { 20 | loadConfig(); 21 | if ($pp->start()) { 22 | continue; 23 | } 24 | workChildren(); 25 | $pp->finish(); 26 | } 27 | $pp->waitAllChildren(); 28 | } 29 | 30 | function loadConfig() 31 | { 32 | echo "Load configuration\n"; 33 | } 34 | 35 | function workChildren() 36 | { 37 | for ($i = 1; $i <= 3; $i++) { 38 | echo "Sleep $i seconds\n"; 39 | sleep($i); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/Prefork.php: -------------------------------------------------------------------------------- 1 | SIGTERM]; 18 | 19 | /** 20 | * @var int $max_workers The number of the workers. 21 | */ 22 | public $max_workers = 10; 23 | 24 | /** 25 | * @var int $err_respawn_interval Number of seconds to deter spawning of child processes after a worker exits abnormally. 26 | */ 27 | public $err_respawn_interval = 1; 28 | 29 | /** 30 | * @var callable $on_child_reap Lambda function that is called when a child is reaped. 31 | */ 32 | public $on_child_reap; 33 | 34 | private $signal_received; 35 | private $manager_pid; 36 | private $in_child = false; 37 | private $worker_pids = []; 38 | private $generation = 0; 39 | 40 | public function __construct(array $args = []) 41 | { 42 | if (array_key_exists('max_workers', $args)) { 43 | $this->max_workers = (int)$args['max_workers']; 44 | } 45 | if (array_key_exists('trap_signals', $args) && is_array($args['trap_signals'])) { 46 | $this->trap_signals = $args['trap_signals']; 47 | } 48 | if (array_key_exists('err_respawn_interval', $args)) { 49 | $this->err_respawn_interval = (int)$args['err_respawn_interval']; 50 | } 51 | 52 | $self = $this; 53 | foreach (array_keys($this->trap_signals) as $sig) { 54 | pcntl_signal( 55 | $sig, 56 | function ($sig) use ($self) {$self->signal_received = $sig;}, 57 | false 58 | ); 59 | } 60 | } 61 | 62 | /** 63 | * The main routine. Returns true within manager process upon receiving a signal specified in the trap_signals, false in child processes. 64 | * 65 | * @return bool True in manager process, false in child processes 66 | */ 67 | public function start() 68 | { 69 | $this->manager_pid = posix_getpid(); 70 | $this->signal_received = null; 71 | $this->generation++; 72 | 73 | if ($this->in_child) { 74 | die("Cannot start another process while you are in child process\n"); 75 | } 76 | 77 | // For debugging. 78 | if ($this->max_workers === 0) { 79 | return true; 80 | } 81 | 82 | // Main loop. 83 | while ($this->signal_received === null) { 84 | $pid = null; 85 | if (count(array_keys($this->worker_pids)) < $this->max_workers) { 86 | $pid = pcntl_fork(); 87 | if ($pid === -1) { 88 | sleep($this->err_respawn_interval); 89 | continue; 90 | } 91 | if ($pid === 0) { 92 | // Child process. 93 | $this->in_child = true; 94 | foreach (array_keys($this->trap_signals) as $sig) { 95 | pcntl_signal($sig, SIG_DFL); 96 | } 97 | if ($this->signal_received !== null) { 98 | exit(0); 99 | } 100 | 101 | return false; 102 | } 103 | $this->worker_pids[$pid] = $this->generation; 104 | } 105 | 106 | $exit_pid = $pid === null ? pcntl_waitpid(-1, $status) : pcntl_waitpid(-1, $status, WNOHANG); 107 | 108 | if ($exit_pid > 0 && $status !== null) { 109 | $this->runChildReap($exit_pid, $status); 110 | if ( 111 | isset($this->worker_pids[$exit_pid]) && 112 | $this->worker_pids[$exit_pid] === $this->generation && 113 | pcntl_wifexited($status) !== true 114 | ) { 115 | sleep($this->err_respawn_interval); 116 | } 117 | unset($this->worker_pids[$exit_pid]); 118 | } 119 | } 120 | 121 | // Send signals to workers. 122 | if ($sig = $this->trap_signals[$this->signal_received]) { 123 | $this->signalAllChildren($sig); 124 | } 125 | 126 | return true; 127 | } 128 | 129 | /** 130 | * Child processes (when executed by a zero-argument call to start) should call this function for termination. Takes exit code as an optional argument. Only usable from child processes. 131 | * 132 | * @param int $exit_code 133 | */ 134 | public function finish($exit_code = 0) 135 | { 136 | if ($this->max_workers === 0) { 137 | return; 138 | } 139 | 140 | exit($exit_code); 141 | } 142 | 143 | /** 144 | * Blocks until all worker processes exit. Only usable from manager process. 145 | */ 146 | public function waitAllChildren() 147 | { 148 | foreach (array_keys($this->worker_pids) as $pid) { 149 | if ($exit_pid = pcntl_wait($status)) { 150 | unset($this->worker_pids[$exit_pid]); 151 | $this->runChildReap($pid, $status); 152 | } 153 | } 154 | } 155 | 156 | /** 157 | * Sends signal to all worker processes. Only usable from manager process. 158 | * 159 | * @param int $sig 160 | */ 161 | public function signalAllChildren($sig) 162 | { 163 | foreach (array_keys($this->worker_pids) as $pid) { 164 | posix_kill($pid, $sig); 165 | } 166 | } 167 | 168 | /** 169 | * Returns the received signal. 170 | */ 171 | public function signalReceived() 172 | { 173 | return $this->signal_received; 174 | } 175 | 176 | /** 177 | * @param int $exit_pid 178 | * @param int $status 179 | */ 180 | private function runChildReap($exit_pid, $status) 181 | { 182 | $callback = $this->on_child_reap; 183 | if (is_callable($callback)) { 184 | $callback($exit_pid, $status); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 17 | tests 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/PreforkTest.php: -------------------------------------------------------------------------------- 1 | pp = new Prefork(array( 13 | 'max_workers' => 5, 14 | 'trap_signals' => array( 15 | SIGHUP => SIGTERM, 16 | SIGTERM => SIGTERM, 17 | ), 18 | )); 19 | } 20 | 21 | public function test() 22 | { 23 | $this->assertTrue($this->pp instanceof Prefork); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |