diff --git a/README.md b/README.md index 0fb65e5..66a959c 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ Spork: PHP on a Fork fork(function() { +$manager->fork(function () { // do something in another process! - return 'Hello from '.getmypid(); -})->then(function(Spork\Fork $fork) { + return 'Hello from ' . getmypid(); +})->then(function (Spork\Fork $fork) { // do something in the parent process when it's done! echo "{$fork->getPid()} says '{$fork->getResult()}'\n"; }); @@ -27,7 +27,77 @@ multiple batches and spread them across many processes. $files = new RecursiveDirectoryIterator('/path/to/images'); $files = new RecursiveIteratorIterator($files); +$manager = new Spork\ProcessManager(); $manager->process($files, function(SplFileInfo $file) { // upload this file }); + +$manager->wait(); +``` + +### Example: Working with Doctrine DBAL + +When working with database connections, there is a known issue regarding parent/child processes. +From http://php.net/manual/en/function.pcntl-fork.php#70721: + +> the child process inherits the parent's database connection. +> When the child exits, the connection is closed. +> If the parent is performing a query at this very moment, it is doing it on an already closed connection + +This will mean that in our example, we will see a `SQLSTATE[HY000]: General error: 2006 MySQL server has gone away` +exception being thrown in the parent process. + +One work-around for this situation is to force-close the DB connection before forking, by using the PRE_FORK event. + +```php + '...', + 'user' => '...', + 'password' => '...', + 'host' => '...', + 'driver' => 'pdo_mysql', +); + +$forks = 4; +$dataArray = range(0, 15); + +$callback = function ($value) use ($params) { + // Child process acquires its own DB connection + $conn = Doctrine\DBAL\DriverManager::getConnection($params); + $conn->connect(); + + $sql = 'SELECT NOW() AS now'; + $stmt = $conn->prepare($sql); + $stmt->execute(); + $dbResult = $stmt->fetch(); + $conn->close(); + + return ['pid' => getmypid(), 'value' => $value, 'result' => $dbResult]; +}; + +// Get DB connection in parent +$parentConnection = Doctrine\DBAL\DriverManager::getConnection($params); +$parentConnection->connect(); + +$dispatcher = new Spork\EventDispatcher\EventDispatcher(); +$dispatcher->addListener(Spork\EventDispatcher\Events::PRE_FORK, function () use ($parentConnection) { + $parentConnection->close(); +}); + +$manager = new Spork\ProcessManager($dispatcher, null, true); + +/** @var Spork\Fork $fork */ +$fork = $manager->process($dataArray, $callback, new Spork\Batch\Strategy\ChunkStrategy($forks)); +$manager->wait(); + +$result = $fork->getResult(); + +// Safe to use now +$sql = 'SELECT NOW() AS now_parent'; +$stmt = $parentConnection->prepare($sql); +$stmt->execute(); +$dbResult = $stmt->fetch(); +$parentConnection->close(); ```