Full Code of kriswallsmith/spork for AI

master 530fcf57fce4 cached
42 files
66.7 KB
17.4k tokens
228 symbols
1 requests
Download .txt
Repository: kriswallsmith/spork
Branch: master
Commit: 530fcf57fce4
Files: 42
Total size: 66.7 KB

Directory structure:
gitextract_92oy9ow3/

├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src/
│   └── Spork/
│       ├── Batch/
│       │   ├── BatchJob.php
│       │   ├── BatchRunner.php
│       │   └── Strategy/
│       │       ├── AbstractStrategy.php
│       │       ├── CallbackStrategy.php
│       │       ├── ChunkStrategy.php
│       │       ├── DoctrineMongoStrategy.php
│       │       ├── MongoStrategy.php
│       │       ├── StrategyInterface.php
│       │       └── ThrottleStrategy.php
│       ├── Deferred/
│       │   ├── Deferred.php
│       │   ├── DeferredAggregate.php
│       │   ├── DeferredInterface.php
│       │   └── PromiseInterface.php
│       ├── EventDispatcher/
│       │   ├── EventDispatcher.php
│       │   ├── EventDispatcherInterface.php
│       │   ├── Events.php
│       │   └── WrappedEventDispatcher.php
│       ├── Exception/
│       │   ├── ForkException.php
│       │   ├── ProcessControlException.php
│       │   └── UnexpectedTypeException.php
│       ├── Factory.php
│       ├── Fork.php
│       ├── ProcessManager.php
│       ├── SharedMemory.php
│       └── Util/
│           ├── Error.php
│           ├── ExitMessage.php
│           └── ThrottleIterator.php
└── tests/
    ├── Spork/
    │   └── Test/
    │       ├── Batch/
    │       │   └── Strategy/
    │       │       ├── ChunkStrategyTest.php
    │       │       └── MongoStrategyTest.php
    │       ├── Deferred/
    │       │   ├── DeferredAggregateTest.php
    │       │   └── DeferredTest.php
    │       ├── ProcessManagerTest.php
    │       ├── SignalTest.php
    │       └── Util/
    │           └── ThrottleIteratorTest.php
    └── bootstrap.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
composer.lock
composer.phar
phpunit.xml
vendor/


================================================
FILE: .travis.yml
================================================
language: php

php:
    - 5.3
    - 5.4
    - 5.5

before_script:
    - wget http://getcomposer.org/composer.phar
    - php composer.phar install --dev

script: phpunit --coverage-text


================================================
FILE: CHANGELOG.md
================================================
## 0.3 (May 18, 2015)

 * Changed ProcessManager constructor to accept new Factory class as second
   argument
 * Use shared memory for interprocess communications (@MattJaniszewski)
 * Added progress callbacks to Deferred
 * Added serializable objects for exit and error messages


================================================
FILE: LICENSE
================================================
Copyright (c) 2012-2013 OpenSky Project Inc

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
[![Build Status](https://secure.travis-ci.org/kriswallsmith/spork.png?branch=master)](http://travis-ci.org/kriswallsmith/spork)

Spork: PHP on a Fork
--------------------

```php
<?php

$manager = new Spork\ProcessManager();
$manager->fork(function() {
    // do something in another process!
    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";
});
```

### Example: Upload images to your CDN

Feed an iterator into the process manager and it will break the job into
multiple batches and spread them across many processes.

```php
<?php

$files = new RecursiveDirectoryIterator('/path/to/images');
$files = new RecursiveIteratorIterator($files);

$manager->process($files, function(SplFileInfo $file) {
    // upload this file
});
```


================================================
FILE: composer.json
================================================
{
    "name": "kriswallsmith/spork",
    "description": "Asynchronous PHP",
    "homepage": "https://github.com/kriswallsmith/spork",
    "type": "library",
    "license": "MIT",
    "authors": [
        {
            "name": "Kris Wallsmith",
            "email": "kris.wallsmith@gmail.com",
            "homepage": "http://kriswallsmith.net/"
        }
    ],
    "require": {
        "php": ">=5.3.0",
        "ext-pcntl": "*",
        "ext-posix": "*",
        "ext-shmop": "*",
        "symfony/event-dispatcher": "*"
    },
    "autoload": {
        "psr-0": { "Spork": "src/" }
    }
}


================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="./tests/bootstrap.php" colors="true">
    <testsuites>
        <testsuite name="Spork Test Suite">
            <directory suffix="Test.php">./tests/Spork/Test/</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory suffix=".php">./src/Spork/</directory>
        </whitelist>
    </filter>
</phpunit>


================================================
FILE: src/Spork/Batch/BatchJob.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Batch;

use Spork\Batch\Strategy\ChunkStrategy;
use Spork\Batch\Strategy\StrategyInterface;
use Spork\Exception\UnexpectedTypeException;
use Spork\ProcessManager;

class BatchJob
{
    private $manager;
    private $data;
    private $strategy;
    private $name;
    private $callback;

    public function __construct(ProcessManager $manager, $data = null, StrategyInterface $strategy = null)
    {
        $this->manager = $manager;
        $this->data = $data;
        $this->strategy = $strategy ?: new ChunkStrategy();
        $this->name = '<anonymous>';
    }

    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    public function setStrategy(StrategyInterface $strategy)
    {
        $this->strategy = $strategy;

        return $this;
    }

    public function setData($data)
    {
        $this->data = $data;

        return $this;
    }

    public function setCallback($callback)
    {
        if (!is_callable($callback)) {
            throw new UnexpectedTypeException($callback, 'callable');
        }

        $this->callback = $callback;

        return $this;
    }

    public function execute($callback = null)
    {
        if (null !== $callback) {
            $this->setCallback($callback);
        }

        return $this->manager->fork($this)->setName($this->name.' batch');
    }

    /**
     * Runs in a child process.
     *
     * @see execute()
     */
    public function __invoke()
    {
        $forks = array();
        foreach ($this->strategy->createBatches($this->data) as $index => $batch) {
            $forks[] = $this->manager
                ->fork($this->strategy->createRunner($batch, $this->callback))
                ->setName(sprintf('%s batch #%d', $this->name, $index))
            ;
        }

        // block until all forks have exited
        $this->manager->wait();

        $results = array();
        foreach ($forks as $fork) {
            $results = array_merge($results, $fork->getResult());
        }

        return $results;
    }
}


================================================
FILE: src/Spork/Batch/BatchRunner.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Batch;

use Spork\Exception\UnexpectedTypeException;
use Spork\SharedMemory;

class BatchRunner
{
    private $batch;
    private $callback;

    /**
     * Constructor.
     *
     * The callback should be a callable with the following signature:
     *
     *     function($item, $index, $batch, $sharedMem)
     *
     * @param mixed    $batch    The batch
     * @param callable $callback The callback
     */
    public function __construct($batch, $callback)
    {
        if (!is_callable($callback)) {
            throw new UnexpectedTypeException($callback, 'callable');
        }

        $this->batch = $batch;
        $this->callback = $callback;
    }

    public function __invoke(SharedMemory $shm)
    {
        // lazy batch...
        if ($this->batch instanceof \Closure) {
            $this->batch = call_user_func($this->batch);
        }

        $results = array();
        foreach ($this->batch as $index => $item) {
            $results[$index] = call_user_func($this->callback, $item, $index, $this->batch, $shm);
        }

        return $results;
    }
}


================================================
FILE: src/Spork/Batch/Strategy/AbstractStrategy.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Batch\Strategy;

use Spork\Batch\BatchRunner;

abstract class AbstractStrategy implements StrategyInterface
{
    public function createRunner($batch, $callback)
    {
        return new BatchRunner($batch, $callback);
    }
}


================================================
FILE: src/Spork/Batch/Strategy/CallbackStrategy.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Batch\Strategy;

use Spork\Exception\UnexpectedTypeException;

class CallbackStrategy extends AbstractStrategy
{
    private $callback;

    public function __construct($callback)
    {
        if (!is_callable($callback)) {
            throw new UnexpectedTypeException($callback, 'callable');
        }

        $this->callback = $callback;
    }

    public function createBatches($data)
    {
        return call_user_func($this->callback, $data);
    }
}


================================================
FILE: src/Spork/Batch/Strategy/ChunkStrategy.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Batch\Strategy;

use Spork\Exception\UnexpectedTypeException;

/**
 * Creates the batch iterator using array_chunk().
 */
class ChunkStrategy extends AbstractStrategy
{
    private $forks;
    private $preserveKeys;

    public function __construct($forks = 3, $preserveKeys = false)
    {
        $this->forks = $forks;
        $this->preserveKeys = $preserveKeys;
    }

    public function createBatches($data)
    {
        if (!is_array($data) && !$data instanceof \Traversable) {
            throw new UnexpectedTypeException($data, 'array or Traversable');
        }

        if ($data instanceof \Traversable) {
            $data = iterator_to_array($data);
        }

        $size = ceil(count($data) / $this->forks);

        return array_chunk($data, $size, $this->preserveKeys);
    }
}


================================================
FILE: src/Spork/Batch/Strategy/DoctrineMongoStrategy.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Batch\Strategy;

class DoctrineMongoStrategy extends MongoStrategy
{
    const DATA_CLASS = 'Doctrine\MongoDB\Cursor';
}


================================================
FILE: src/Spork/Batch/Strategy/MongoStrategy.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Batch\Strategy;

use Spork\Exception\UnexpectedTypeException;

/**
 * Processes a Mongo cursor.
 *
 * If you use this strategy you MUST close your Mongo connection on the
 * spork.pre_fork event.
 *
 *     $mongo = new Mongo();
 *     $manager->addListener(Events::PRE_FORK, array($mongo, 'close'));
 */
class MongoStrategy extends AbstractStrategy
{
    const DATA_CLASS = 'MongoCursor';

    private $size;
    private $skip;

    /**
     * Constructor.
     *
     * @param integer $size The number of batches to create
     * @param integer $skip The number of documents to skip
     */
    public function __construct($size = 3, $skip = 0)
    {
        $this->size = $size;
        $this->skip = $skip;
    }

    public function createBatches($cursor)
    {
        $expected = static::DATA_CLASS;
        if (!$cursor instanceof $expected) {
            throw new UnexpectedTypeException($cursor, $expected);
        }

        $skip  = $this->skip;
        $limit = ceil(($cursor->count(true) - $skip) / $this->size);

        $batches = array();
        for ($i = 0; $i < $this->size; $i++) {
            $batches[] = function() use($cursor, $skip, $i, $limit) {
                return $cursor->skip($skip + $i * $limit)->limit($limit);
            };
        }

        return $batches;
    }
}


================================================
FILE: src/Spork/Batch/Strategy/StrategyInterface.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Batch\Strategy;

/**
 * @see BatchJob::__invoke()
 */
interface StrategyInterface
{
    /**
     * Creates an iterator for the supplied data.
     *
     * @param mixed $data The raw batch data
     *
     * @return array|\Traversable An iterator of batches
     */
    function createBatches($data);

    /**
     * Creates a batch runner for the supplied list.
     *
     * A batch runner is a callable that is passed to ProcessManager::fork()
     * that should run each item in the supplied batch through a callable.
     *
     * @param mixed    $batch    A batch of items
     * @param callable $callback The batch callback
     *
     * @return callable A callable for the child process
     */
    function createRunner($batch, $callback);
}


================================================
FILE: src/Spork/Batch/Strategy/ThrottleStrategy.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Batch\Strategy;

use Spork\Util\ThrottleIterator;

class ThrottleStrategy implements StrategyInterface
{
    private $delegate;
    private $threshold;

    public function __construct(StrategyInterface $delegate, $threshold = 3)
    {
        $this->delegate = $delegate;
        $this->threshold = $threshold;
    }

    public function createBatches($data)
    {
        $batches = $this->delegate->createBatches($data);

        // wrap each batch in the throttle iterator
        foreach ($batches as $i => $batch) {
            $batches[$i] = new ThrottleIterator($batch, $this->threshold);
        }

        return $batches;
    }

    public function createRunner($batch, $callback)
    {
        return $this->delegate->createRunner($batch, $callback);
    }
}


================================================
FILE: src/Spork/Deferred/Deferred.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Deferred;

use Spork\Exception\UnexpectedTypeException;

class Deferred implements DeferredInterface
{
    private $state;
    private $progressCallbacks;
    private $alwaysCallbacks;
    private $doneCallbacks;
    private $failCallbacks;
    private $callbackArgs;

    public function __construct()
    {
        $this->state = DeferredInterface::STATE_PENDING;

        $this->progressCallbacks = array();
        $this->alwaysCallbacks = array();
        $this->doneCallbacks = array();
        $this->failCallbacks = array();
    }

    public function getState()
    {
        return $this->state;
    }

    public function progress($progress)
    {
        if (!is_callable($progress)) {
            throw new UnexpectedTypeException($progress, 'callable');
        }

        $this->progressCallbacks[] = $progress;

        return $this;
    }

    public function always($always)
    {
        if (!is_callable($always)) {
            throw new UnexpectedTypeException($always, 'callable');
        }

        switch ($this->state) {
            case DeferredInterface::STATE_PENDING:
                $this->alwaysCallbacks[] = $always;
                break;
            default:
                call_user_func_array($always, $this->callbackArgs);
                break;
        }

        return $this;
    }

    public function done($done)
    {
        if (!is_callable($done)) {
            throw new UnexpectedTypeException($done, 'callable');
        }

        switch ($this->state) {
            case DeferredInterface::STATE_PENDING:
                $this->doneCallbacks[] = $done;
                break;
            case DeferredInterface::STATE_RESOLVED:
                call_user_func_array($done, $this->callbackArgs);
        }

        return $this;
    }

    public function fail($fail)
    {
        if (!is_callable($fail)) {
            throw new UnexpectedTypeException($fail, 'callable');
        }

        switch ($this->state) {
            case DeferredInterface::STATE_PENDING:
                $this->failCallbacks[] = $fail;
                break;
            case DeferredInterface::STATE_REJECTED:
                call_user_func_array($fail, $this->callbackArgs);
                break;
        }

        return $this;
    }

    public function then($done, $fail = null)
    {
        $this->done($done);

        if ($fail) {
            $this->fail($fail);
        }

        return $this;
    }

    public function notify()
    {
        if (DeferredInterface::STATE_PENDING !== $this->state) {
            throw new \LogicException('Cannot notify a deferred object that is no longer pending');
        }

        $args = func_get_args();
        foreach ($this->progressCallbacks as $func) {
            call_user_func_array($func, $args);
        }

        return $this;
    }

    public function resolve()
    {
        if (DeferredInterface::STATE_REJECTED === $this->state) {
            throw new \LogicException('Cannot resolve a deferred object that has already been rejected');
        }

        if (DeferredInterface::STATE_RESOLVED === $this->state) {
            return $this;
        }

        $this->state = DeferredInterface::STATE_RESOLVED;
        $this->callbackArgs = func_get_args();

        while ($func = array_shift($this->alwaysCallbacks)) {
            call_user_func_array($func, $this->callbackArgs);
        }

        while ($func = array_shift($this->doneCallbacks)) {
            call_user_func_array($func, $this->callbackArgs);
        }

        return $this;
    }

    public function reject()
    {
        if (DeferredInterface::STATE_RESOLVED === $this->state) {
            throw new \LogicException('Cannot reject a deferred object that has already been resolved');
        }

        if (DeferredInterface::STATE_REJECTED === $this->state) {
            return $this;
        }

        $this->state = DeferredInterface::STATE_REJECTED;
        $this->callbackArgs = func_get_args();

        while ($func = array_shift($this->alwaysCallbacks)) {
            call_user_func_array($func, $this->callbackArgs);
        }

        while ($func = array_shift($this->failCallbacks)) {
            call_user_func_array($func, $this->callbackArgs);
        }

        return $this;
    }
}


================================================
FILE: src/Spork/Deferred/DeferredAggregate.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Deferred;

use Spork\Exception\UnexpectedTypeException;

class DeferredAggregate implements PromiseInterface
{
    private $children;
    private $delegate;

    public function __construct(array $children)
    {
        // validate children
        foreach ($children as $child) {
            if (!$child instanceof PromiseInterface) {
                throw new UnexpectedTypeException($child, 'Spork\Deferred\PromiseInterface');
            }
        }

        $this->children = $children;
        $this->delegate = new Deferred();

        // connect to each child
        foreach ($this->children as $child) {
            $child->always(array($this, 'tick'));
        }

        // always tick once now
        $this->tick();
    }

    public function getState()
    {
        return $this->delegate->getState();
    }

    public function getChildren()
    {
        return $this->children;
    }

    public function progress($progress)
    {
        $this->delegate->progress($progress);

        return $this;
    }

    public function always($always)
    {
        $this->delegate->always($always);

        return $this;
    }

    public function done($done)
    {
        $this->delegate->done($done);

        return $this;
    }

    public function fail($fail)
    {
        $this->delegate->fail($fail);

        return $this;
    }

    public function then($done, $fail = null)
    {
        $this->delegate->then($done, $fail);

        return $this;
    }

    public function tick()
    {
        $pending = count($this->children);

        foreach ($this->children as $child) {
            switch ($child->getState()) {
                case PromiseInterface::STATE_REJECTED:
                    $this->delegate->reject($this);

                    return;
                case PromiseInterface::STATE_RESOLVED:
                    --$pending;
                    break;
            }
        }

        if (!$pending) {
            $this->delegate->resolve($this);
        }
    }
}


================================================
FILE: src/Spork/Deferred/DeferredInterface.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Deferred;

interface DeferredInterface extends PromiseInterface
{
    /**
     * Notifies the promise of progress.
     *
     * @param mixed $args Any arguments will be passed along to the callbacks
     *
     * @return DeferredInterface The current promise
     * @throws \LogicException   If the promise is not pending
     */
    function notify();

    /**
     * Marks the current promise as successful.
     *
     * Calls "always" callbacks first, followed by "done" callbacks.
     *
     * @param mixed $args Any arguments will be passed along to the callbacks
     *
     * @return DeferredInterface The current promise
     * @throws \LogicException   If the promise was previously rejected
     */
    function resolve();

    /**
     * Marks the current promise as failed.
     *
     * Calls "always" callbacks first, followed by "fail" callbacks.
     *
     * @param mixed $args Any arguments will be passed along to the callbacks
     *
     * @return DeferredInterface The current promise
     * @throws \LogicException   If the promise was previously resolved
     */
    function reject();
}


================================================
FILE: src/Spork/Deferred/PromiseInterface.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Deferred;

interface PromiseInterface
{
    const STATE_PENDING  = 'pending';
    const STATE_RESOLVED = 'resolved';
    const STATE_REJECTED = 'rejected';

    /**
     * Returns the promise state.
     *
     *  * PromiseInterface::STATE_PENDING:  The promise is still open
     *  * PromiseInterface::STATE_RESOLVED: The promise completed successfully
     *  * PromiseInterface::STATE_REJECTED: The promise failed
     *
     * @return string A promise state constant
     */
    function getState();

    /**
     * Adds a callback to be called upon progress.
     *
     * @param callable $progress The callback
     *
     * @return PromiseInterface The current promise
     */
    function progress($progress);

    /**
     * Adds a callback to be called whether the promise is resolved or rejected.
     *
     * The callback will be called immediately if the promise is no longer
     * pending.
     *
     * @param callable $always The callback
     *
     * @return PromiseInterface The current promise
     */
    function always($always);

    /**
     * Adds a callback to be called when the promise completes successfully.
     *
     * The callback will be called immediately if the promise state is resolved.
     *
     * @param callable $done The callback
     *
     * @return PromiseInterface The current promise
     */
    function done($done);

    /**
     * Adds a callback to be called when the promise fails.
     *
     * The callback will be called immediately if the promise state is rejected.
     *
     * @param callable $done The callback
     *
     * @return PromiseInterface The current promise
     */
    function fail($fail);

    /**
     * Adds done and fail callbacks.
     *
     * @param callable $done The done callback
     * @param callable $fail The fail callback
     *
     * @return PromiseInterface The current promise
     */
    function then($done, $fail = null);
}


================================================
FILE: src/Spork/EventDispatcher/EventDispatcher.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(ticks=1);

namespace Spork\EventDispatcher;

use Symfony\Component\EventDispatcher\EventDispatcher as BaseEventDispatcher;

/**
 * Adds support for listening to signals.
 */
class EventDispatcher extends BaseEventDispatcher implements EventDispatcherInterface
{
    public function dispatchSignal($signal)
    {
        $this->dispatch('spork.signal.'.$signal);
    }

    public function addSignalListener($signal, $callable, $priority = 0)
    {
        $this->addListener('spork.signal.'.$signal, $callable, $priority);
        pcntl_signal($signal, array($this, 'dispatchSignal'));
    }

    public function removeSignalListener($signal, $callable)
    {
        $this->removeListener('spork.signal.'.$signal, $callable);
    }
}


================================================
FILE: src/Spork/EventDispatcher/EventDispatcherInterface.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\EventDispatcher;

use Symfony\Component\EventDispatcher\EventDispatcherInterface as BaseEventDispatcherInterface;

interface EventDispatcherInterface extends BaseEventDispatcherInterface
{
    function dispatchSignal($signal);
    function addSignalListener($signal, $callable, $priority = 0);
    function removeSignalListener($signal, $callable);
}


================================================
FILE: src/Spork/EventDispatcher/Events.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\EventDispatcher;

final class Events
{
    /**
     * Dispatched in the parent process before forking.
     */
    const PRE_FORK = 'spork.pre_fork';

    /**
     * Notifies in the child process after forking.
     */
    const POST_FORK = 'spork.post_fork';
}


================================================
FILE: src/Spork/EventDispatcher/WrappedEventDispatcher.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(ticks=1);

namespace Spork\EventDispatcher;

use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface as BaseEventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class WrappedEventDispatcher implements EventDispatcherInterface
{
    private $delegate;

    public function __construct(BaseEventDispatcherInterface $delegate)
    {
        $this->delegate = $delegate;
    }

    public function dispatchSignal($signal)
    {
        $this->delegate->dispatch('spork.signal.'.$signal);
    }

    public function addSignalListener($signal, $callable, $priority = 0)
    {
        $this->delegate->addListener('spork.signal.'.$signal, $callable, $priority);
        pcntl_signal($signal, array($this, 'dispatchSignal'));
    }

    public function removeSignalListener($signal, $callable)
    {
        $this->delegate->removeListener('spork.signal.'.$signal, $callable);
    }

    public function dispatch($eventName, Event $event = null)
    {
        return $this->delegate->dispatch($eventName, $event);
    }

    public function addListener($eventName, $listener, $priority = 0)
    {
        $this->delegate->addListener($eventName, $listener, $priority);
    }

    public function addSubscriber(EventSubscriberInterface $subscriber)
    {
        $this->delegate->addSubscriber($subscriber);
    }

    public function removeListener($eventName, $listener)
    {
        $this->delegate->removeListener($eventName, $listener);
    }

    public function removeSubscriber(EventSubscriberInterface $subscriber)
    {
        $this->delegate->removeSubscriber($subscriber);
    }

    public function getListeners($eventName = null)
    {
        return $this->delegate->getListeners($eventName);
    }

    public function hasListeners($eventName = null)
    {
        return $this->delegate->hasListeners($eventName);
    }
}


================================================
FILE: src/Spork/Exception/ForkException.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Exception;

use Spork\Util\Error;

/**
 * Turns an error passed through shared memory into an exception.
 */
class ForkException extends \RuntimeException
{
    private $name;
    private $pid;
    private $error;

    public function __construct($name, $pid, Error $error = null)
    {
        $this->name = $name;
        $this->pid = $pid;
        $this->error = $error;

        if ($error) {
            if (__CLASS__ === $error->getClass()) {
                parent::__construct(sprintf('%s via "%s" fork (%d)', $error->getMessage(), $name, $pid));
            } else {
                parent::__construct(sprintf(
                    '%s (%d) thrown in "%s" fork (%d): "%s" (%s:%d)',
                    $error->getClass(),
                    $error->getCode(),
                    $name,
                    $pid,
                    $error->getMessage(),
                    $error->getFile(),
                    $error->getLine()
                ));
            }
        } else {
            parent::__construct(sprintf('An unknown error occurred in "%s" fork (%d)', $name, $pid));
        }
    }

    public function getPid()
    {
        return $this->pid;
    }

    public function getError()
    {
        return $this->error;
    }
}


================================================
FILE: src/Spork/Exception/ProcessControlException.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Exception;

class ProcessControlException extends \RuntimeException
{
}


================================================
FILE: src/Spork/Exception/UnexpectedTypeException.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Exception;

class UnexpectedTypeException extends \LogicException
{
    public function __construct($value, $expectedType)
    {
        parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));
    }
}


================================================
FILE: src/Spork/Factory.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork;

use Spork\Batch\BatchJob;
use Spork\Batch\Strategy\StrategyInterface;

class Factory
{
    /**
     * Creates a new batch job instance.
     *
     * @param ProcessManager    $manager  The process manager
     * @param null              $data     Data for the batch job
     * @param StrategyInterface $strategy The strategy
     *
     * @return BatchJob A new batch job instance
     */
    public function createBatchJob(ProcessManager $manager, $data = null, StrategyInterface $strategy = null)
    {
        return new BatchJob($manager, $data, $strategy);
    }

    /**
     * Creates a new shared memory instance.
     *
     * @param integer $pid    The child process id or null if this is the child
     * @param integer $signal The signal to send after writing to shared memory
     *
     * @return SharedMemory A new shared memory instance
     */
    public function createSharedMemory($pid = null, $signal = null)
    {
        return new SharedMemory($pid, $signal);
    }

    /**
     * Creates a new fork instance.
     *
     * @param int          $pid   Process id
     * @param SharedMemory $shm   Shared memory
     * @param bool         $debug Debug mode
     *
     * @return Fork A new fork instance
     */
    public function createFork($pid, SharedMemory $shm, $debug = false)
    {
        return new Fork($pid, $shm, $debug);
    }
}


================================================
FILE: src/Spork/Fork.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork;

use Spork\Deferred\Deferred;
use Spork\Deferred\DeferredInterface;
use Spork\Exception\ForkException;
use Spork\Exception\ProcessControlException;
use Spork\Util\ExitMessage;

class Fork implements DeferredInterface
{
    private $defer;
    private $pid;
    private $shm;
    private $debug;
    private $name;
    private $status;
    private $message;

    public function __construct($pid, SharedMemory $shm, $debug = false)
    {
        $this->defer = new Deferred();
        $this->pid   = $pid;
        $this->shm   = $shm;
        $this->debug = $debug;
        $this->name  = '<anonymous>';
    }

    /**
     * Assign a name to the current fork (useful for debugging).
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    public function getPid()
    {
        return $this->pid;
    }

    public function wait($hang = true)
    {
        if ($this->isExited()) {
            return $this;
        }

        if (-1 === $pid = pcntl_waitpid($this->pid, $status, ($hang ? 0 : WNOHANG) | WUNTRACED)) {
            throw new ProcessControlException('Error while waiting for process '.$this->pid);
        }

        if ($this->pid === $pid) {
            $this->processWaitStatus($status);
        }

        return $this;
    }

    /**
     * Processes a status value retrieved while waiting for this fork to exit.
     */
    public function processWaitStatus($status)
    {
        if ($this->isExited()) {
            throw new \LogicException('Cannot set status on an exited fork');
        }

        $this->status = $status;

        if ($this->isExited()) {
            $this->receive();

            $this->isSuccessful() ? $this->resolve() : $this->reject();

            if ($this->debug && (!$this->isSuccessful() || $this->getError())) {
                throw new ForkException($this->name, $this->pid, $this->getError());
            }
        }
    }

    public function receive()
    {
        $messages = array();

        foreach ($this->shm->receive() as $message) {
            if ($message instanceof ExitMessage) {
                $this->message = $message;
            } else {
                $messages[] = $message;
            }
        }

        return $messages;
    }

    public function kill($signal = SIGINT)
    {
        if (false === $this->shm->signal($signal)) {
            throw new ProcessControlException('Unable to send signal');
        }

        return $this;
    }

    public function getResult()
    {
        if ($this->message) {
            return $this->message->getResult();
        }
    }

    public function getOutput()
    {
        if ($this->message) {
            return $this->message->getOutput();
        }
    }

    public function getError()
    {
        if ($this->message) {
            return $this->message->getError();
        }
    }

    public function isSuccessful()
    {
        return 0 === $this->getExitStatus();
    }

    public function isExited()
    {
        return null !== $this->status && pcntl_wifexited($this->status);
    }

    public function isStopped()
    {
        return null !== $this->status && pcntl_wifstopped($this->status);
    }

    public function isSignaled()
    {
        return null !== $this->status && pcntl_wifsignaled($this->status);
    }

    public function getExitStatus()
    {
        if (null !== $this->status) {
            return pcntl_wexitstatus($this->status);
        }
    }

    public function getTermSignal()
    {
        if (null !== $this->status) {
            return pcntl_wtermsig($this->status);
        }
    }

    public function getStopSignal()
    {
        if (null !== $this->status) {
            return pcntl_wstopsig($this->status);
        }
    }

    public function getState()
    {
        return $this->defer->getState();
    }

    public function progress($progress)
    {
        $this->defer->progress($progress);

        return $this;
    }

    public function always($always)
    {
        $this->defer->always($always);

        return $this;
    }

    public function done($done)
    {
        $this->defer->done($done);

        return $this;
    }

    public function fail($fail)
    {
        $this->defer->fail($fail);

        return $this;
    }

    public function then($done, $fail = null)
    {
        $this->defer->then($done, $fail);

        return $this;
    }

    public function notify()
    {
        $args = func_get_args();
        array_unshift($args, $this);

        call_user_func_array(array($this->defer, 'notify'), $args);

        return $this;
    }

    public function resolve()
    {
        $args = func_get_args();
        array_unshift($args, $this);

        call_user_func_array(array($this->defer, 'resolve'), $args);

        return $this;
    }

    public function reject()
    {
        $args = func_get_args();
        array_unshift($args, $this);

        call_user_func_array(array($this->defer, 'reject'), $args);

        return $this;
    }
}


================================================
FILE: src/Spork/ProcessManager.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork;

use Spork\Batch\Strategy\StrategyInterface;
use Spork\EventDispatcher\EventDispatcher;
use Spork\EventDispatcher\EventDispatcherInterface;
use Spork\EventDispatcher\Events;
use Spork\Exception\ProcessControlException;
use Spork\Exception\UnexpectedTypeException;
use Spork\Util\Error;
use Spork\Util\ExitMessage;

class ProcessManager
{
    private $dispatcher;
    private $factory;
    private $debug;
    private $zombieOkay;
    private $signal;

    /** @var Fork[] */
    private $forks;

    public function __construct(EventDispatcherInterface $dispatcher = null, Factory $factory = null, $debug = false)
    {
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
        $this->factory = $factory ?: new Factory();
        $this->debug = $debug;
        $this->zombieOkay = false;
        $this->forks = array();
    }

    public function __destruct()
    {
        if (!$this->zombieOkay) {
            $this->wait();
        }
    }

    public function getEventDispatcher()
    {
        return $this->dispatcher;
    }

    public function addListener($eventName, $listener, $priority = 0)
    {
        if (is_integer($eventName)) {
            $this->dispatcher->addSignalListener($eventName, $listener, $priority);
        } else {
            $this->dispatcher->addListener($eventName, $listener, $priority);
        }
    }

    public function setDebug($debug)
    {
        $this->debug = $debug;
    }

    public function zombieOkay($zombieOkay = true)
    {
        $this->zombieOkay = $zombieOkay;
    }

    public function createBatchJob($data = null, StrategyInterface $strategy = null)
    {
        return $this->factory->createBatchJob($this, $data, $strategy);
    }

    public function process($data, $callable, StrategyInterface $strategy = null)
    {
        return $this->createBatchJob($data, $strategy)->execute($callable);
    }

    /**
     * Forks something into another process and returns a deferred object.
     */
    public function fork($callable)
    {
        if (!is_callable($callable)) {
            throw new UnexpectedTypeException($callable, 'callable');
        }

        // allow the system to cleanup before forking
        $this->dispatcher->dispatch(Events::PRE_FORK);

        if (-1 === $pid = pcntl_fork()) {
            throw new ProcessControlException('Unable to fork a new process');
        }

        if (0 === $pid) {
            // reset the list of child processes
            $this->forks = array();

            // setup the shared memory
            $shm = $this->factory->createSharedMemory(null, $this->signal);
            $message = new ExitMessage();

            // phone home on shutdown
            register_shutdown_function(function() use($shm, $message) {
                $status = null;

                try {
                    $shm->send($message, false);
                } catch (\Exception $e) {
                    // probably an error serializing the result
                    $message->setResult(null);
                    $message->setError(Error::fromException($e));

                    $shm->send($message, false);

                    exit(2);
                }
            });

            // dispatch an event so the system knows it's in a new process
            $this->dispatcher->dispatch(Events::POST_FORK);

            if (!$this->debug) {
                ob_start();
            }

            try {
                $result = call_user_func($callable, $shm);

                $message->setResult($result);
                $status = is_integer($result) ? $result : 0;
            } catch (\Exception $e) {
                $message->setError(Error::fromException($e));
                $status = 1;
            }

            if (!$this->debug) {
                $message->setOutput(ob_get_clean());
            }

            exit($status);
        }

        // connect to shared memory
        $shm = $this->factory->createSharedMemory($pid);

        return $this->forks[$pid] = $this->factory->createFork($pid, $shm, $this->debug);
    }

    public function monitor($signal = SIGUSR1)
    {
        $this->signal = $signal;
        $this->dispatcher->addSignalListener($signal, array($this, 'check'));
    }

    public function check()
    {
        foreach ($this->forks as $fork) {
            foreach ($fork->receive() as $message) {
                $fork->notify($message);
            }
        }
    }

    public function wait($hang = true)
    {
        foreach ($this->forks as $fork) {
            $fork->wait($hang);
        }
    }

    public function waitForNext($hang = true)
    {
        if (-1 === $pid = pcntl_wait($status, ($hang ? WNOHANG : 0) | WUNTRACED)) {
            throw new ProcessControlException('Error while waiting for next fork to exit');
        }

        if (isset($this->forks[$pid])) {
            $this->forks[$pid]->processWaitStatus($status);

            return $this->forks[$pid];
        }
    }

    public function waitFor($pid, $hang = true)
    {
        if (!isset($this->forks[$pid])) {
            throw new \InvalidArgumentException('There is no fork with PID '.$pid);
        }

        return $this->forks[$pid]->wait($hang);
    }

    /**
     * Sends a signal to all forks.
     */
    public function killAll($signal = SIGINT)
    {
        foreach ($this->forks as $fork) {
            $fork->kill($signal);
        }
    }
}


================================================
FILE: src/Spork/SharedMemory.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork;

use Spork\Exception\ProcessControlException;

/**
 * Sends messages between processes.
 */
class SharedMemory
{
    private $pid;
    private $ppid;
    private $signal;

    /**
     * Constructor.
     *
     * @param integer $pid    The child process id or null if this is the child
     * @param integer $signal The signal to send after writing to shared memory
     */
    public function __construct($pid = null, $signal = null)
    {
        if (null === $pid) {
            // child
            $pid   = posix_getpid();
            $ppid  = posix_getppid();
        } else {
            // parent
            $ppid  = null;
        }

        $this->pid  = $pid;
        $this->ppid = $ppid;
        $this->signal = $signal;
    }

    /**
     * Reads all messages from shared memory.
     *
     * @return array An array of messages
     */
    public function receive()
    {
        if (($shmId = @shmop_open($this->pid, 'a', 0, 0)) > 0) {
            $serializedMessages = shmop_read($shmId, 0, shmop_size($shmId));
            shmop_delete($shmId);
            shmop_close($shmId);

            return unserialize($serializedMessages);
        }

        return array();
    }

    /**
     * Writes a message to the shared memory.
     *
     * @param mixed   $message The message to send
     * @param integer $signal  The signal to send afterward
     * @param integer $pause   The number of microseconds to pause after signalling
     */
    public function send($message, $signal = null, $pause = 500)
    {
        $messageArray = array();

        if (($shmId = @shmop_open($this->pid, 'a', 0, 0)) > 0) {
            // Read any existing messages in shared memory
            $readMessage = shmop_read($shmId, 0, shmop_size($shmId));
            $messageArray[] = unserialize($readMessage);
            shmop_delete($shmId);
            shmop_close($shmId);
        }

        // Add the current message to the end of the array, and serialize it
        $messageArray[] = $message;
        $serializedMessage = serialize($messageArray);

        // Write new serialized message to shared memory
        $shmId = shmop_open($this->pid, 'c', 0644, strlen($serializedMessage));
        if (!$shmId) {
            throw new ProcessControlException(sprintf('Not able to create shared memory segment for PID: %s', $this->pid));
        } else if (shmop_write($shmId, $serializedMessage, 0) !== strlen($serializedMessage)) {
            throw new ProcessControlException(
                sprintf('Not able to write message to shared memory segment for segment ID: %s', $shmId)
            );
        }

        if (false === $signal) {
            return;
        }

        $this->signal($signal ?: $this->signal);
        usleep($pause);
    }

    /**
     * Sends a signal to the other process.
     */
    public function signal($signal)
    {
        $pid = null === $this->ppid ? $this->pid : $this->ppid;

        return posix_kill($pid, $signal);
    }
}


================================================
FILE: src/Spork/Util/Error.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Util;

class Error implements \Serializable
{
    private $class;
    private $message;
    private $file;
    private $line;
    private $code;

    public static function fromException(\Exception $e)
    {
        $flat = new static();
        $flat->setClass(get_class($e));
        $flat->setMessage($e->getMessage());
        $flat->setFile($e->getFile());
        $flat->setLine($e->getLine());
        $flat->setCode($e->getCode());

        return $flat;
    }

    public function getClass()
    {
        return $this->class;
    }

    public function setClass($class)
    {
        $this->class = $class;
    }

    public function getMessage()
    {
        return $this->message;
    }

    public function setMessage($message)
    {
        $this->message = $message;
    }

    public function getFile()
    {
        return $this->file;
    }

    public function setFile($file)
    {
        $this->file = $file;
    }

    public function getLine()
    {
        return $this->line;
    }

    public function setLine($line)
    {
        $this->line = $line;
    }

    public function getCode()
    {
        return $this->code;
    }

    public function setCode($code)
    {
        $this->code = $code;
    }

    public function serialize()
    {
        return serialize(array(
            $this->class,
            $this->message,
            $this->file,
            $this->line,
            $this->code,
        ));
    }

    public function unserialize($str)
    {
        list(
            $this->class,
            $this->message,
            $this->file,
            $this->line,
            $this->code
        ) = unserialize($str);
    }
}


================================================
FILE: src/Spork/Util/ExitMessage.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Util;

class ExitMessage implements \Serializable
{
    private $result;
    private $output;
    private $error;

    public function getResult()
    {
        return $this->result;
    }

    public function setResult($result)
    {
        $this->result = $result;
    }

    public function getOutput()
    {
        return $this->output;
    }

    public function setOutput($output)
    {
        $this->output = $output;
    }

    public function getError()
    {
        return $this->error;
    }

    public function setError(Error $error)
    {
        $this->error = $error;
    }

    public function serialize()
    {
        return serialize(array(
            $this->result,
            $this->output,
            $this->error,
        ));
    }

    public function unserialize($str)
    {
        list(
            $this->result,
            $this->output,
            $this->error
        ) = unserialize($str);
    }
}


================================================
FILE: src/Spork/Util/ThrottleIterator.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Util;

use Spork\Exception\UnexpectedTypeException;

/**
 * Throttles iteration based on a system load threshold.
 */
class ThrottleIterator implements \OuterIterator
{
    private $inner;
    private $threshold;
    private $lastThrottle;

    public function __construct($inner, $threshold)
    {
        if (!is_callable($inner) && !is_array($inner) && !$inner instanceof \Traversable) {
            throw new UnexpectedTypeException($inner, 'callable, array, or Traversable');
        }

        $this->inner = $inner;
        $this->threshold = $threshold;
    }

    /**
     * Attempts to lazily resolve the supplied inner to an instance of Iterator.
     */
    public function getInnerIterator()
    {
        if (is_callable($this->inner)) {
            // callable
            $this->inner = call_user_func($this->inner);
        }

        if (is_array($this->inner)) {
            // array
            $this->inner = new \ArrayIterator($this->inner);
        } elseif ($this->inner instanceof \IteratorAggregate) {
            // IteratorAggregate
            while ($this->inner instanceof \IteratorAggregate) {
                $this->inner = $this->inner->getIterator();
            }
        }

        if (!$this->inner instanceof \Iterator) {
            throw new UnexpectedTypeException($this->inner, 'Iterator');
        }

        return $this->inner;
    }

    public function current()
    {
        // only throttle every 5s
        if ($this->lastThrottle < time() - 5) {
            $this->throttle();
        }

        return $this->getInnerIterator()->current();
    }

    public function key()
    {
        return $this->getInnerIterator()->key();
    }

    public function next()
    {
        return $this->getInnerIterator()->next();
    }

    public function rewind()
    {
        return $this->getInnerIterator()->rewind();
    }

    public function valid()
    {
        return $this->getInnerIterator()->valid();
    }

    protected function getLoad()
    {
        list($load) = sys_getloadavg();

        return $load;
    }

    protected function sleep($period)
    {
        sleep($period);
    }

    private function throttle($period = 1)
    {
        $this->lastThrottle = time();

        if ($this->threshold <= $this->getLoad()) {
            $this->sleep($period);
            $this->throttle($period * 2);
        }
    }
}


================================================
FILE: tests/Spork/Test/Batch/Strategy/ChunkStrategyTest.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Test\Batch\Strategy;

use Spork\Batch\Strategy\ChunkStrategy;

class ChunkStrategyTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider provideNumber
     */
    public function testChunkArray($number, $expectedCounts)
    {
        $strategy = new ChunkStrategy($number);
        $batches = $strategy->createBatches(range(1, 100));

        $this->assertEquals(count($expectedCounts), count($batches));
        foreach ($batches as $i => $batch) {
            $this->assertCount($expectedCounts[$i], $batch);
        }
    }

    public function provideNumber()
    {
        return array(
            array(1, array(100)),
            array(2, array(50, 50)),
            array(3, array(34, 34, 32)),
            array(4, array(25, 25, 25, 25)),
            array(5, array(20, 20, 20, 20, 20)),
        );
    }
}


================================================
FILE: tests/Spork/Test/Batch/Strategy/MongoStrategyTest.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Test\Batch\Strategy;

use Spork\Batch\Strategy\MongoStrategy;
use Spork\EventDispatcher\Events;
use Spork\ProcessManager;

class MongoStrategyTest extends \PHPUnit_Framework_TestCase
{
    private $mongo;
    private $manager;

    protected function setUp()
    {
        if (!class_exists('MongoClient', false)) {
            $this->markTestSkipped('Mongo extension is not loaded');
        }

        try {
            $this->mongo = new \MongoClient();
        } catch (\MongoConnectionException $e) {
            $this->markTestSkipped($e->getMessage());
        }

        $this->manager = new ProcessManager();
        $this->manager->setDebug(true);

        // close the connection prior to forking
        $mongo = $this->mongo;
        $this->manager->addListener(Events::PRE_FORK, function() use($mongo) {
            $mongo->close();
        });
    }

    protected function tearDown()
    {
        if ($this->mongo) {
            $this->mongo->close();
        }

        unset($this->mongo, $this->manager);
    }

    public function testBatchJob()
    {
        $coll = $this->mongo->spork->widgets;

        $coll->remove();
        $coll->batchInsert(array(
            array('name' => 'Widget 1'),
            array('name' => 'Widget 2'),
            array('name' => 'Widget 3'),
        ));

        $this->manager->createBatchJob($coll->find(), new MongoStrategy())
            ->execute(function($doc) use($coll) {
                $coll->update(
                    array('_id' => $doc['_id']),
                    array('$set' => array('seen' => true))
                );
            });

        $this->manager->wait();

        foreach ($coll->find() as $doc) {
            $this->assertArrayHasKey('seen', $doc);
        }
    }
}


================================================
FILE: tests/Spork/Test/Deferred/DeferredAggregateTest.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Test\Deferred;

use Spork\Deferred\Deferred;
use Spork\Deferred\DeferredAggregate;

class DeferredAggregateTest extends \PHPUnit_Framework_TestCase
{
    public function testInvalidChild()
    {
        $this->setExpectedException('Spork\Exception\UnexpectedTypeException', 'PromiseInterface');

        $defer = new DeferredAggregate(array('asdf'));
    }

    public function testNoChildren()
    {
        $defer = new DeferredAggregate(array());

        $log = array();
        $defer->done(function() use(& $log) {
            $log[] = 'done';
        });

        $this->assertEquals(array('done'), $log);
    }

    public function testResolvedChildren()
    {
        $child = new Deferred();
        $child->resolve();

        $defer = new DeferredAggregate(array($child));

        $log = array();
        $defer->done(function() use(& $log) {
            $log[] = 'done';
        });

        $this->assertEquals(array('done'), $log);
    }

    public function testResolution()
    {
        $child1 = new Deferred();
        $child2 = new Deferred();

        $defer = new DeferredAggregate(array($child1, $child2));

        $log = array();
        $defer->done(function() use(& $log) {
            $log[] = 'done';
        });

        $this->assertEquals(array(), $log);

        $child1->resolve();
        $this->assertEquals(array(), $log);

        $child2->resolve();
        $this->assertEquals(array('done'), $log);
    }

    public function testRejection()
    {
        $child1 = new Deferred();
        $child2 = new Deferred();
        $child3 = new Deferred();

        $defer = new DeferredAggregate(array($child1, $child2, $child3));

        $log = array();
        $defer->then(function() use(& $log) {
            $log[] = 'done';
        }, function() use(& $log) {
            $log[] = 'fail';
        });

        $this->assertEquals(array(), $log);

        $child1->resolve();
        $this->assertEquals(array(), $log);

        $child2->reject();
        $this->assertEquals(array('fail'), $log);

        $child3->resolve();
        $this->assertEquals(array('fail'), $log);
    }

    public function testNested()
    {
        $child1a = new Deferred();
        $child1b = new Deferred();
        $child1 = new DeferredAggregate(array($child1a, $child1b));
        $child2 = new Deferred();

        $defer = new DeferredAggregate(array($child1, $child2));

        $child1a->resolve();
        $child1b->resolve();
        $child2->resolve();

        $this->assertEquals('resolved', $defer->getState());
    }

    public function testFail()
    {
        $child = new Deferred();
        $defer = new DeferredAggregate(array($child));

        $log = array();
        $defer->fail(function() use(& $log) {
            $log[] = 'fail';
        });

        $child->reject();

        $this->assertEquals(array('fail'), $log);
    }
}


================================================
FILE: tests/Spork/Test/Deferred/DeferredTest.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Test\Deferred;

use Spork\Deferred\Deferred;

class DeferredTest extends \PHPUnit_Framework_TestCase
{
    private $defer;

    protected function setUp()
    {
        $this->defer = new Deferred();
    }

    protected function tearDown()
    {
        unset($this->defer);
    }

    /**
     * @dataProvider getMethodAndKey
     */
    public function testCallbackOrder($method, $expected)
    {
        $log = array();

        $this->defer->always(function() use(& $log) {
            $log[] = 'always';
            $log[] = func_get_args();
        })->done(function() use(& $log) {
            $log[] = 'done';
            $log[] = func_get_args();
        })->fail(function() use(& $log) {
            $log[] = 'fail';
            $log[] = func_get_args();
        });

        $this->defer->$method(1, 2, 3);

        $this->assertEquals(array(
            'always',
            array(1, 2, 3),
            $expected,
            array(1, 2, 3),
        ), $log);
    }

    /**
     * @dataProvider getMethodAndKey
     */
    public function testThen($method, $expected)
    {
        $log = array();

        $this->defer->then(function() use(& $log) {
            $log[] = 'done';
        }, function() use(& $log) {
            $log[] = 'fail';
        });

        $this->defer->$method();

        $this->assertEquals(array($expected), $log);
    }

    /**
     * @dataProvider getMethod
     */
    public function testMultipleResolve($method)
    {
        $log = array();

        $this->defer->always(function() use(& $log) {
            $log[] = 'always';
        });

        $this->defer->$method();
        $this->defer->$method();

        $this->assertEquals(array('always'), $log);
    }

    /**
     * @dataProvider getMethodAndInvalid
     */
    public function testInvalidResolve($method, $invalid)
    {
        $this->setExpectedException('LogicException', 'that has already been');

        $this->defer->$method();
        $this->defer->$invalid();
    }

    /**
     * @dataProvider getMethodAndQueue
     */
    public function testAlreadyResolved($resolve, $queue, $expect = true)
    {
        // resolve the object
        $this->defer->$resolve();

        $log = array();
        $this->defer->$queue(function() use(& $log, $queue) {
            $log[] = $queue;
        });

        $this->assertEquals($expect ? array($queue) : array(), $log);
    }

    /**
     * @dataProvider getMethodAndInvalidCallback
     */
    public function testInvalidCallback($method, $invalid)
    {
        $this->setExpectedException('Spork\Exception\UnexpectedTypeException', 'callable');

        $this->defer->$method($invalid);
    }

    // providers

    public function getMethodAndKey()
    {
        return array(
            array('resolve', 'done'),
            array('reject', 'fail'),
        );
    }

    public function getMethodAndInvalid()
    {
        return array(
            array('resolve', 'reject'),
            array('reject', 'resolve'),
        );
    }

    public function getMethodAndQueue()
    {
        return array(
            array('resolve', 'always'),
            array('resolve', 'done'),
            array('resolve', 'fail', false),
            array('reject', 'always'),
            array('reject', 'done', false),
            array('reject', 'fail'),
        );
    }

    public function getMethodAndInvalidCallback()
    {
        return array(
            array('always', 'foo!'),
            array('always', array('foo!')),
            array('done', 'foo!'),
            array('done', array('foo!')),
            array('fail', 'foo!'),
            array('fail', array('foo!')),
        );
    }

    public function getMethod()
    {
        return array(
            array('resolve'),
            array('reject'),
        );
    }
}


================================================
FILE: tests/Spork/Test/ProcessManagerTest.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Test;

use Spork\Fork;
use Spork\ProcessManager;

class ProcessManagerTest extends \PHPUnit_Framework_TestCase
{
    /**
     * Process Manager object
     *
     * @var ProcessManager
     */
    private $manager;

    protected function setUp()
    {
        $this->manager = new ProcessManager();
    }

    protected function tearDown()
    {
        unset($this->manager);
    }

    public function testDoneCallbacks()
    {
        $success = null;

        $fork = $this->manager->fork(function() {
            echo 'output';
            return 'result';
        })->done(function() use(& $success) {
            $success = true;
        })->fail(function() use(& $success) {
            $success = false;
        });

        $this->manager->wait();

        $this->assertTrue($success);
        $this->assertEquals('output', $fork->getOutput());
        $this->assertEquals('result', $fork->getResult());
    }

    public function testFailCallbacks()
    {
        $success = null;

        $fork = $this->manager->fork(function() {
            throw new \Exception('child error');
        })->done(function() use(& $success) {
            $success = true;
        })->fail(function() use(& $success) {
            $success = false;
        });

        $this->manager->wait();

        $this->assertFalse($success);
        $this->assertNotEmpty($fork->getError());
    }

    public function testObjectReturn()
    {
        $fork = $this->manager->fork(function() {
            return new Unserializable();
        });

        $this->manager->wait();

        $this->assertNull($fork->getResult());
        $this->assertFalse($fork->isSuccessful());
    }

    public function testBatchProcessing()
    {
        $expected = range(100, 109);

        $fork = $this->manager->process($expected, function($item) {
            return $item;
        });

        $this->manager->wait();

        $this->assertEquals($expected, $fork->getResult());
    }

    /**
     * Test batch processing with return values containing a newline character
     */
    public function testBatchProcessingWithNewlineReturnValues()
    {
        $range = range(100, 109);
        $expected = array (
            0 => "SomeString\n100",
            1 => "SomeString\n101",
            2 => "SomeString\n102",
            3 => "SomeString\n103",
            4 => "SomeString\n104",
            5 => "SomeString\n105",
            6 => "SomeString\n106",
            7 => "SomeString\n107",
            8 => "SomeString\n108",
            9 => "SomeString\n109",
        );

        $this->manager->setDebug(true);
        $fork = $this->manager->process($range, function($item) {
            return "SomeString\n$item";
        });

        $this->manager->wait();

        $this->assertEquals($expected, $fork->getResult());
    }

    /**
     * Data provider for `testLargeBatchProcessing()`
     *
     * @return array
     */
    public function batchProvider()
    {
        return array(
            array(10),
            array(1000),
            array(6941),
            array(6942),
            array(6000),
            array(10000),
            array(20000),
        );
    }

    /**
     * Test large batch sizes
     *
     * @dataProvider batchProvider
     */
    public function testLargeBatchProcessing($rangeEnd)
    {
        $expected = array_fill(0, $rangeEnd, null);

        /** @var Fork $fork */
        $fork = $this->manager->process($expected, function($item) {
            return $item;
        });

        $this->manager->wait();

        $this->assertEquals($expected, $fork->getResult());
    }
}

class Unserializable
{
    public function __sleep()
    {
        throw new \Exception('Hey, don\'t serialize me!');
    }
}


================================================
FILE: tests/Spork/Test/SignalTest.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Test;

use Spork\ProcessManager;

class SignalTest extends \PHPUnit_Framework_TestCase
{
    private $manager;

    protected function setUp()
    {
        $this->manager = new ProcessManager();
    }

    protected function tearDown()
    {
        $this->manager = null;
    }

    public function testSignalParent()
    {
        $signaled = false;
        $this->manager->addListener(SIGUSR1, function() use(& $signaled) {
            $signaled = true;
        });

        $this->manager->fork(function($sharedMem) {
            $sharedMem->signal(SIGUSR1);
        });

        $this->manager->wait();

        $this->assertTrue($signaled);
    }
}


================================================
FILE: tests/Spork/Test/Util/ThrottleIteratorTest.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Spork\Test\Util;

use Spork\Util\ThrottleIterator;

class ThrottleIteratorTest extends \PHPUnit_Framework_TestCase
{
    private $iterator;

    protected function setUp()
    {
        $this->iterator = new ThrottleIteratorStub(array(1, 2, 3, 4, 5), 3);
        $this->iterator->loads = array(4, 4, 4, 1, 1);
    }

    protected function tearDown()
    {
        unset($this->iterator);
    }

    public function testIteration()
    {
        iterator_to_array($this->iterator);
        $this->assertEquals(array(1, 2, 4), $this->iterator->sleeps);
    }
}

class ThrottleIteratorStub extends ThrottleIterator
{
    public $loads = array();
    public $sleeps = array();

    protected function getLoad()
    {
        return (integer) array_shift($this->loads);
    }

    protected function sleep($period)
    {
        $this->sleeps[] = $period;
    }
}


================================================
FILE: tests/bootstrap.php
================================================
<?php

/*
 * This file is part of Spork, an OpenSky project.
 *
 * (c) OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

if (!$loader = @include __DIR__.'/../vendor/autoload.php') {
    echo <<<EOM
You must set up the project dependencies by running the following commands:

    curl -s http://getcomposer.org/installer | php
    php composer.phar install

EOM;

    exit(1);
}

$loader->add('Spork\Test', __DIR__);
Download .txt
gitextract_92oy9ow3/

├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src/
│   └── Spork/
│       ├── Batch/
│       │   ├── BatchJob.php
│       │   ├── BatchRunner.php
│       │   └── Strategy/
│       │       ├── AbstractStrategy.php
│       │       ├── CallbackStrategy.php
│       │       ├── ChunkStrategy.php
│       │       ├── DoctrineMongoStrategy.php
│       │       ├── MongoStrategy.php
│       │       ├── StrategyInterface.php
│       │       └── ThrottleStrategy.php
│       ├── Deferred/
│       │   ├── Deferred.php
│       │   ├── DeferredAggregate.php
│       │   ├── DeferredInterface.php
│       │   └── PromiseInterface.php
│       ├── EventDispatcher/
│       │   ├── EventDispatcher.php
│       │   ├── EventDispatcherInterface.php
│       │   ├── Events.php
│       │   └── WrappedEventDispatcher.php
│       ├── Exception/
│       │   ├── ForkException.php
│       │   ├── ProcessControlException.php
│       │   └── UnexpectedTypeException.php
│       ├── Factory.php
│       ├── Fork.php
│       ├── ProcessManager.php
│       ├── SharedMemory.php
│       └── Util/
│           ├── Error.php
│           ├── ExitMessage.php
│           └── ThrottleIterator.php
└── tests/
    ├── Spork/
    │   └── Test/
    │       ├── Batch/
    │       │   └── Strategy/
    │       │       ├── ChunkStrategyTest.php
    │       │       └── MongoStrategyTest.php
    │       ├── Deferred/
    │       │   ├── DeferredAggregateTest.php
    │       │   └── DeferredTest.php
    │       ├── ProcessManagerTest.php
    │       ├── SignalTest.php
    │       └── Util/
    │           └── ThrottleIteratorTest.php
    └── bootstrap.php
Download .txt
SYMBOL INDEX (228 symbols across 34 files)

FILE: src/Spork/Batch/BatchJob.php
  class BatchJob (line 19) | class BatchJob
    method __construct (line 27) | public function __construct(ProcessManager $manager, $data = null, Str...
    method setName (line 35) | public function setName($name)
    method setStrategy (line 42) | public function setStrategy(StrategyInterface $strategy)
    method setData (line 49) | public function setData($data)
    method setCallback (line 56) | public function setCallback($callback)
    method execute (line 67) | public function execute($callback = null)
    method __invoke (line 81) | public function __invoke()

FILE: src/Spork/Batch/BatchRunner.php
  class BatchRunner (line 17) | class BatchRunner
    method __construct (line 32) | public function __construct($batch, $callback)
    method __invoke (line 42) | public function __invoke(SharedMemory $shm)

FILE: src/Spork/Batch/Strategy/AbstractStrategy.php
  class AbstractStrategy (line 16) | abstract class AbstractStrategy implements StrategyInterface
    method createRunner (line 18) | public function createRunner($batch, $callback)

FILE: src/Spork/Batch/Strategy/CallbackStrategy.php
  class CallbackStrategy (line 16) | class CallbackStrategy extends AbstractStrategy
    method __construct (line 20) | public function __construct($callback)
    method createBatches (line 29) | public function createBatches($data)

FILE: src/Spork/Batch/Strategy/ChunkStrategy.php
  class ChunkStrategy (line 19) | class ChunkStrategy extends AbstractStrategy
    method __construct (line 24) | public function __construct($forks = 3, $preserveKeys = false)
    method createBatches (line 30) | public function createBatches($data)

FILE: src/Spork/Batch/Strategy/DoctrineMongoStrategy.php
  class DoctrineMongoStrategy (line 14) | class DoctrineMongoStrategy extends MongoStrategy

FILE: src/Spork/Batch/Strategy/MongoStrategy.php
  class MongoStrategy (line 25) | class MongoStrategy extends AbstractStrategy
    method __construct (line 38) | public function __construct($size = 3, $skip = 0)
    method createBatches (line 44) | public function createBatches($cursor)

FILE: src/Spork/Batch/Strategy/StrategyInterface.php
  type StrategyInterface (line 17) | interface StrategyInterface
    method createBatches (line 26) | function createBatches($data);
    method createRunner (line 39) | function createRunner($batch, $callback);

FILE: src/Spork/Batch/Strategy/ThrottleStrategy.php
  class ThrottleStrategy (line 16) | class ThrottleStrategy implements StrategyInterface
    method __construct (line 21) | public function __construct(StrategyInterface $delegate, $threshold = 3)
    method createBatches (line 27) | public function createBatches($data)
    method createRunner (line 39) | public function createRunner($batch, $callback)

FILE: src/Spork/Deferred/Deferred.php
  class Deferred (line 16) | class Deferred implements DeferredInterface
    method __construct (line 25) | public function __construct()
    method getState (line 35) | public function getState()
    method progress (line 40) | public function progress($progress)
    method always (line 51) | public function always($always)
    method done (line 69) | public function done($done)
    method fail (line 86) | public function fail($fail)
    method then (line 104) | public function then($done, $fail = null)
    method notify (line 115) | public function notify()
    method resolve (line 129) | public function resolve()
    method reject (line 153) | public function reject()

FILE: src/Spork/Deferred/DeferredAggregate.php
  class DeferredAggregate (line 16) | class DeferredAggregate implements PromiseInterface
    method __construct (line 21) | public function __construct(array $children)
    method getState (line 42) | public function getState()
    method getChildren (line 47) | public function getChildren()
    method progress (line 52) | public function progress($progress)
    method always (line 59) | public function always($always)
    method done (line 66) | public function done($done)
    method fail (line 73) | public function fail($fail)
    method then (line 80) | public function then($done, $fail = null)
    method tick (line 87) | public function tick()

FILE: src/Spork/Deferred/DeferredInterface.php
  type DeferredInterface (line 14) | interface DeferredInterface extends PromiseInterface
    method notify (line 24) | function notify();
    method resolve (line 36) | function resolve();
    method reject (line 48) | function reject();

FILE: src/Spork/Deferred/PromiseInterface.php
  type PromiseInterface (line 14) | interface PromiseInterface
    method getState (line 29) | function getState();
    method progress (line 38) | function progress($progress);
    method always (line 50) | function always($always);
    method done (line 61) | function done($done);
    method fail (line 72) | function fail($fail);
    method then (line 82) | function then($done, $fail = null);

FILE: src/Spork/EventDispatcher/EventDispatcher.php
  class EventDispatcher (line 21) | class EventDispatcher extends BaseEventDispatcher implements EventDispat...
    method dispatchSignal (line 23) | public function dispatchSignal($signal)
    method addSignalListener (line 28) | public function addSignalListener($signal, $callable, $priority = 0)
    method removeSignalListener (line 34) | public function removeSignalListener($signal, $callable)

FILE: src/Spork/EventDispatcher/EventDispatcherInterface.php
  type EventDispatcherInterface (line 16) | interface EventDispatcherInterface extends BaseEventDispatcherInterface
    method dispatchSignal (line 18) | function dispatchSignal($signal);
    method addSignalListener (line 19) | function addSignalListener($signal, $callable, $priority = 0);
    method removeSignalListener (line 20) | function removeSignalListener($signal, $callable);

FILE: src/Spork/EventDispatcher/Events.php
  class Events (line 14) | final class Events

FILE: src/Spork/EventDispatcher/WrappedEventDispatcher.php
  class WrappedEventDispatcher (line 20) | class WrappedEventDispatcher implements EventDispatcherInterface
    method __construct (line 24) | public function __construct(BaseEventDispatcherInterface $delegate)
    method dispatchSignal (line 29) | public function dispatchSignal($signal)
    method addSignalListener (line 34) | public function addSignalListener($signal, $callable, $priority = 0)
    method removeSignalListener (line 40) | public function removeSignalListener($signal, $callable)
    method dispatch (line 45) | public function dispatch($eventName, Event $event = null)
    method addListener (line 50) | public function addListener($eventName, $listener, $priority = 0)
    method addSubscriber (line 55) | public function addSubscriber(EventSubscriberInterface $subscriber)
    method removeListener (line 60) | public function removeListener($eventName, $listener)
    method removeSubscriber (line 65) | public function removeSubscriber(EventSubscriberInterface $subscriber)
    method getListeners (line 70) | public function getListeners($eventName = null)
    method hasListeners (line 75) | public function hasListeners($eventName = null)

FILE: src/Spork/Exception/ForkException.php
  class ForkException (line 19) | class ForkException extends \RuntimeException
    method __construct (line 25) | public function __construct($name, $pid, Error $error = null)
    method getPid (line 51) | public function getPid()
    method getError (line 56) | public function getError()

FILE: src/Spork/Exception/ProcessControlException.php
  class ProcessControlException (line 14) | class ProcessControlException extends \RuntimeException

FILE: src/Spork/Exception/UnexpectedTypeException.php
  class UnexpectedTypeException (line 14) | class UnexpectedTypeException extends \LogicException
    method __construct (line 16) | public function __construct($value, $expectedType)

FILE: src/Spork/Factory.php
  class Factory (line 17) | class Factory
    method createBatchJob (line 28) | public function createBatchJob(ProcessManager $manager, $data = null, ...
    method createSharedMemory (line 41) | public function createSharedMemory($pid = null, $signal = null)
    method createFork (line 55) | public function createFork($pid, SharedMemory $shm, $debug = false)

FILE: src/Spork/Fork.php
  class Fork (line 20) | class Fork implements DeferredInterface
    method __construct (line 30) | public function __construct($pid, SharedMemory $shm, $debug = false)
    method setName (line 42) | public function setName($name)
    method getPid (line 49) | public function getPid()
    method wait (line 54) | public function wait($hang = true)
    method processWaitStatus (line 74) | public function processWaitStatus($status)
    method receive (line 93) | public function receive()
    method kill (line 108) | public function kill($signal = SIGINT)
    method getResult (line 117) | public function getResult()
    method getOutput (line 124) | public function getOutput()
    method getError (line 131) | public function getError()
    method isSuccessful (line 138) | public function isSuccessful()
    method isExited (line 143) | public function isExited()
    method isStopped (line 148) | public function isStopped()
    method isSignaled (line 153) | public function isSignaled()
    method getExitStatus (line 158) | public function getExitStatus()
    method getTermSignal (line 165) | public function getTermSignal()
    method getStopSignal (line 172) | public function getStopSignal()
    method getState (line 179) | public function getState()
    method progress (line 184) | public function progress($progress)
    method always (line 191) | public function always($always)
    method done (line 198) | public function done($done)
    method fail (line 205) | public function fail($fail)
    method then (line 212) | public function then($done, $fail = null)
    method notify (line 219) | public function notify()
    method resolve (line 229) | public function resolve()
    method reject (line 239) | public function reject()

FILE: src/Spork/ProcessManager.php
  class ProcessManager (line 23) | class ProcessManager
    method __construct (line 34) | public function __construct(EventDispatcherInterface $dispatcher = nul...
    method __destruct (line 43) | public function __destruct()
    method getEventDispatcher (line 50) | public function getEventDispatcher()
    method addListener (line 55) | public function addListener($eventName, $listener, $priority = 0)
    method setDebug (line 64) | public function setDebug($debug)
    method zombieOkay (line 69) | public function zombieOkay($zombieOkay = true)
    method createBatchJob (line 74) | public function createBatchJob($data = null, StrategyInterface $strate...
    method process (line 79) | public function process($data, $callable, StrategyInterface $strategy ...
    method fork (line 87) | public function fork($callable)
    method monitor (line 155) | public function monitor($signal = SIGUSR1)
    method check (line 161) | public function check()
    method wait (line 170) | public function wait($hang = true)
    method waitForNext (line 177) | public function waitForNext($hang = true)
    method waitFor (line 190) | public function waitFor($pid, $hang = true)
    method killAll (line 202) | public function killAll($signal = SIGINT)

FILE: src/Spork/SharedMemory.php
  class SharedMemory (line 19) | class SharedMemory
    method __construct (line 31) | public function __construct($pid = null, $signal = null)
    method receive (line 52) | public function receive()
    method send (line 72) | public function send($message, $signal = null, $pause = 500)
    method signal (line 109) | public function signal($signal)

FILE: src/Spork/Util/Error.php
  class Error (line 14) | class Error implements \Serializable
    method fromException (line 22) | public static function fromException(\Exception $e)
    method getClass (line 34) | public function getClass()
    method setClass (line 39) | public function setClass($class)
    method getMessage (line 44) | public function getMessage()
    method setMessage (line 49) | public function setMessage($message)
    method getFile (line 54) | public function getFile()
    method setFile (line 59) | public function setFile($file)
    method getLine (line 64) | public function getLine()
    method setLine (line 69) | public function setLine($line)
    method getCode (line 74) | public function getCode()
    method setCode (line 79) | public function setCode($code)
    method serialize (line 84) | public function serialize()
    method unserialize (line 95) | public function unserialize($str)

FILE: src/Spork/Util/ExitMessage.php
  class ExitMessage (line 14) | class ExitMessage implements \Serializable
    method getResult (line 20) | public function getResult()
    method setResult (line 25) | public function setResult($result)
    method getOutput (line 30) | public function getOutput()
    method setOutput (line 35) | public function setOutput($output)
    method getError (line 40) | public function getError()
    method setError (line 45) | public function setError(Error $error)
    method serialize (line 50) | public function serialize()
    method unserialize (line 59) | public function unserialize($str)

FILE: src/Spork/Util/ThrottleIterator.php
  class ThrottleIterator (line 19) | class ThrottleIterator implements \OuterIterator
    method __construct (line 25) | public function __construct($inner, $threshold)
    method getInnerIterator (line 38) | public function getInnerIterator()
    method current (line 62) | public function current()
    method key (line 72) | public function key()
    method next (line 77) | public function next()
    method rewind (line 82) | public function rewind()
    method valid (line 87) | public function valid()
    method getLoad (line 92) | protected function getLoad()
    method sleep (line 99) | protected function sleep($period)
    method throttle (line 104) | private function throttle($period = 1)

FILE: tests/Spork/Test/Batch/Strategy/ChunkStrategyTest.php
  class ChunkStrategyTest (line 16) | class ChunkStrategyTest extends \PHPUnit_Framework_TestCase
    method testChunkArray (line 21) | public function testChunkArray($number, $expectedCounts)
    method provideNumber (line 32) | public function provideNumber()

FILE: tests/Spork/Test/Batch/Strategy/MongoStrategyTest.php
  class MongoStrategyTest (line 18) | class MongoStrategyTest extends \PHPUnit_Framework_TestCase
    method setUp (line 23) | protected function setUp()
    method tearDown (line 45) | protected function tearDown()
    method testBatchJob (line 54) | public function testBatchJob()

FILE: tests/Spork/Test/Deferred/DeferredAggregateTest.php
  class DeferredAggregateTest (line 17) | class DeferredAggregateTest extends \PHPUnit_Framework_TestCase
    method testInvalidChild (line 19) | public function testInvalidChild()
    method testNoChildren (line 26) | public function testNoChildren()
    method testResolvedChildren (line 38) | public function testResolvedChildren()
    method testResolution (line 53) | public function testResolution()
    method testRejection (line 74) | public function testRejection()
    method testNested (line 101) | public function testNested()
    method testFail (line 117) | public function testFail()

FILE: tests/Spork/Test/Deferred/DeferredTest.php
  class DeferredTest (line 16) | class DeferredTest extends \PHPUnit_Framework_TestCase
    method setUp (line 20) | protected function setUp()
    method tearDown (line 25) | protected function tearDown()
    method testCallbackOrder (line 33) | public function testCallbackOrder($method, $expected)
    method testThen (line 61) | public function testThen($method, $expected)
    method testMultipleResolve (line 79) | public function testMultipleResolve($method)
    method testInvalidResolve (line 96) | public function testInvalidResolve($method, $invalid)
    method testAlreadyResolved (line 107) | public function testAlreadyResolved($resolve, $queue, $expect = true)
    method testInvalidCallback (line 123) | public function testInvalidCallback($method, $invalid)
    method getMethodAndKey (line 132) | public function getMethodAndKey()
    method getMethodAndInvalid (line 140) | public function getMethodAndInvalid()
    method getMethodAndQueue (line 148) | public function getMethodAndQueue()
    method getMethodAndInvalidCallback (line 160) | public function getMethodAndInvalidCallback()
    method getMethod (line 172) | public function getMethod()

FILE: tests/Spork/Test/ProcessManagerTest.php
  class ProcessManagerTest (line 17) | class ProcessManagerTest extends \PHPUnit_Framework_TestCase
    method setUp (line 26) | protected function setUp()
    method tearDown (line 31) | protected function tearDown()
    method testDoneCallbacks (line 36) | public function testDoneCallbacks()
    method testFailCallbacks (line 56) | public function testFailCallbacks()
    method testObjectReturn (line 74) | public function testObjectReturn()
    method testBatchProcessing (line 86) | public function testBatchProcessing()
    method testBatchProcessingWithNewlineReturnValues (line 102) | public function testBatchProcessingWithNewlineReturnValues()
    method batchProvider (line 133) | public function batchProvider()
    method testLargeBatchProcessing (line 151) | public function testLargeBatchProcessing($rangeEnd)
  class Unserializable (line 166) | class Unserializable
    method __sleep (line 168) | public function __sleep()

FILE: tests/Spork/Test/SignalTest.php
  class SignalTest (line 16) | class SignalTest extends \PHPUnit_Framework_TestCase
    method setUp (line 20) | protected function setUp()
    method tearDown (line 25) | protected function tearDown()
    method testSignalParent (line 30) | public function testSignalParent()

FILE: tests/Spork/Test/Util/ThrottleIteratorTest.php
  class ThrottleIteratorTest (line 16) | class ThrottleIteratorTest extends \PHPUnit_Framework_TestCase
    method setUp (line 20) | protected function setUp()
    method tearDown (line 26) | protected function tearDown()
    method testIteration (line 31) | public function testIteration()
  class ThrottleIteratorStub (line 38) | class ThrottleIteratorStub extends ThrottleIterator
    method getLoad (line 43) | protected function getLoad()
    method sleep (line 48) | protected function sleep($period)
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (74K chars).
[
  {
    "path": ".gitignore",
    "chars": 48,
    "preview": "composer.lock\ncomposer.phar\nphpunit.xml\nvendor/\n"
  },
  {
    "path": ".travis.yml",
    "chars": 185,
    "preview": "language: php\n\nphp:\n    - 5.3\n    - 5.4\n    - 5.5\n\nbefore_script:\n    - wget http://getcomposer.org/composer.phar\n    - "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 281,
    "preview": "## 0.3 (May 18, 2015)\n\n * Changed ProcessManager constructor to accept new Factory class as second\n   argument\n * Use sh"
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "Copyright (c) 2012-2013 OpenSky Project Inc\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "README.md",
    "chars": 876,
    "preview": "[![Build Status](https://secure.travis-ci.org/kriswallsmith/spork.png?branch=master)](http://travis-ci.org/kriswallsmith"
  },
  {
    "path": "composer.json",
    "chars": 593,
    "preview": "{\n    \"name\": \"kriswallsmith/spork\",\n    \"description\": \"Asynchronous PHP\",\n    \"homepage\": \"https://github.com/kriswall"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 413,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit bootstrap=\"./tests/bootstrap.php\" colors=\"true\">\n    <testsuites>\n     "
  },
  {
    "path": "src/Spork/Batch/BatchJob.php",
    "chars": 2293,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Batch/BatchRunner.php",
    "chars": 1326,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Batch/Strategy/AbstractStrategy.php",
    "chars": 469,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Batch/Strategy/CallbackStrategy.php",
    "chars": 702,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Batch/Strategy/ChunkStrategy.php",
    "chars": 1042,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Batch/Strategy/DoctrineMongoStrategy.php",
    "chars": 363,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Batch/Strategy/MongoStrategy.php",
    "chars": 1549,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Batch/Strategy/StrategyInterface.php",
    "chars": 993,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Batch/Strategy/ThrottleStrategy.php",
    "chars": 1013,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Deferred/Deferred.php",
    "chars": 4523,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Deferred/DeferredAggregate.php",
    "chars": 2250,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Deferred/DeferredInterface.php",
    "chars": 1357,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Deferred/PromiseInterface.php",
    "chars": 2168,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/EventDispatcher/EventDispatcher.php",
    "chars": 969,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/EventDispatcher/EventDispatcherInterface.php",
    "chars": 593,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/EventDispatcher/Events.php",
    "chars": 504,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/EventDispatcher/WrappedEventDispatcher.php",
    "chars": 2156,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Exception/ForkException.php",
    "chars": 1497,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Exception/ProcessControlException.php",
    "chars": 314,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Exception/UnexpectedTypeException.php",
    "chars": 535,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Factory.php",
    "chars": 1609,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Fork.php",
    "chars": 5280,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/ProcessManager.php",
    "chars": 5661,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/SharedMemory.php",
    "chars": 3222,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Util/Error.php",
    "chars": 1919,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Util/ExitMessage.php",
    "chars": 1182,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "src/Spork/Util/ThrottleIterator.php",
    "chars": 2626,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "tests/Spork/Test/Batch/Strategy/ChunkStrategyTest.php",
    "chars": 1076,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "tests/Spork/Test/Batch/Strategy/MongoStrategyTest.php",
    "chars": 2002,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "tests/Spork/Test/Deferred/DeferredAggregateTest.php",
    "chars": 3124,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "tests/Spork/Test/Deferred/DeferredTest.php",
    "chars": 4055,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "tests/Spork/Test/ProcessManagerTest.php",
    "chars": 3992,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "tests/Spork/Test/SignalTest.php",
    "chars": 898,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "tests/Spork/Test/Util/ThrottleIteratorTest.php",
    "chars": 1096,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  },
  {
    "path": "tests/bootstrap.php",
    "chars": 521,
    "preview": "<?php\n\n/*\n * This file is part of Spork, an OpenSky project.\n *\n * (c) OpenSky Project Inc\n *\n * For the full copyright "
  }
]

About this extraction

This page contains the full source code of the kriswallsmith/spork GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (66.7 KB), approximately 17.4k tokens, and a symbol index with 228 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!