Port to Amp

This commit is contained in:
Aaron Piotrowski 2016-08-18 11:04:48 -05:00
parent 54810f1d67
commit da84a772cf
105 changed files with 1369 additions and 1400 deletions

2
.gitattributes vendored
View File

@ -1,4 +1,4 @@
tests export-ignore
test export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.travis.yml export-ignore

View File

@ -3,15 +3,15 @@ All notable changes to this project will be documented in this file. This projec
## [0.3.0] - 2016-01-15
### Added
- Added `Icicle\Concurrent\Worker\factory()` function that accesses or sets the global worker factory.
- Added `Icicle\Concurrent\Worker\get()` function that returns a worker from the global worker pool.
- Added `Amp\Concurrent\Worker\factory()` function that accesses or sets the global worker factory.
- Added `Amp\Concurrent\Worker\get()` function that returns a worker from the global worker pool.
### Changed
- `Icicle\Concurrent\Worker\Environment` is now an interface, with `Icicle\Concurrent\Worker\BasicEnvironment` being the default implementation provided to workers that is then provided to `Icicle\Concurrent\Worker\Task::run()`. Workers with different implementations of `Environment` can be easily created for particular applications.
- `Icicle\Concurrent\Worker\Queue` has been removed. The functionality of queues has been merged into `Icicle\Concurrent\Worker\Pool` through a new `get()` method that returns a worker from the pool. The returned worker is marked as busy until all references have been destroyed. See the example code below.
- `Amp\Concurrent\Worker\Environment` is now an interface, with `Amp\Concurrent\Worker\BasicEnvironment` being the default implementation provided to workers that is then provided to `Amp\Concurrent\Worker\Task::run()`. Workers with different implementations of `Environment` can be easily created for particular applications.
- `Amp\Concurrent\Worker\Queue` has been removed. The functionality of queues has been merged into `Amp\Concurrent\Worker\Pool` through a new `get()` method that returns a worker from the pool. The returned worker is marked as busy until all references have been destroyed. See the example code below.
```php
use Icicle\Concurrent\Worker\DefaultPool;
use Amp\Concurrent\Worker\DefaultPool;
$pool = new DefaultPool();
$pool->start();
@ -24,26 +24,26 @@ $worker = null; // Marks worker as idle in the pool.
## [0.2.2] - 2015-12-21
### Added
- Added the `Icicle\Concurrent\Strand` interface that combines `Icicle\Concurrent\Context` and `Icicle\Concurrent\Sync\Channel`. This interface is implemented by the following classes (note that these classes implemented the two component interface separately, so no changes were made to the implementation):
- `Icicle\Concurrent\Forking\Fork`
- `Icicle\Concurrent\Threading\Thread`
- `Icicle\Concurrent\Process\ChannelledProcess`
- Added the `Amp\Concurrent\Strand` interface that combines `Amp\Concurrent\Context` and `Amp\Concurrent\Sync\Channel`. This interface is implemented by the following classes (note that these classes implemented the two component interface separately, so no changes were made to the implementation):
- `Amp\Concurrent\Forking\Fork`
- `Amp\Concurrent\Threading\Thread`
- `Amp\Concurrent\Process\ChannelledProcess`
### Changed
- `Icicle\Concurrent\Strand` interface is now required by the constructor of `Icicle\Concurrent\Worker\AbstractWorker`.
- `Amp\Concurrent\Strand` interface is now required by the constructor of `Amp\Concurrent\Worker\AbstractWorker`.
## [0.2.1] - 2015-12-16
### Added
- Added `Icicle\Concurrent\Worker\DefaultQueue` implementing `Icicle\Concurrent\Worker\Queue` that provides a queue of workers that can be pulled and pushed from the queue as needed. Pulling a worker marks it as busy and pushing the worker back into the queue marks it as idle. If no idle workers remain in the queue, a worker is selected from those marked as busy. A worker queue allows a set of interdependent tasks (for example, tasks that depend on an environment value in the worker) to be run on a single worker without having to create and start separate workers for each task.
- Added `Amp\Concurrent\Worker\DefaultQueue` implementing `Amp\Concurrent\Worker\Queue` that provides a queue of workers that can be pulled and pushed from the queue as needed. Pulling a worker marks it as busy and pushing the worker back into the queue marks it as idle. If no idle workers remain in the queue, a worker is selected from those marked as busy. A worker queue allows a set of interdependent tasks (for example, tasks that depend on an environment value in the worker) to be run on a single worker without having to create and start separate workers for each task.
### Fixed
- Fixed bug where exit status was not being read in `Icicle\Concurrent\Process\Process`, which also caused `Icicle\Concurrent\Worker\WorkerProcess` to fail.
- Fixed bug where exit status was not being read in `Amp\Concurrent\Process\Process`, which also caused `Amp\Concurrent\Worker\WorkerProcess` to fail.
## [0.2.0] - 2015-12-13
### Changed
- Updated to Icicle `0.9.x` packages.
- All exceptions now implement the `Icicle\Exception\Throwable` interface.
- Updated to Amp `0.9.x` packages.
- All exceptions now implement the `Amp\Exception\Throwable` interface.
- All interface names have been changed to remove the Interface suffix.
- `Sync\Channel` was renamed to `Sync\ChannelledStream`.
- `Sync\Parcel` was renamed to `Sync\SharedMemoryParcel`.

View File

@ -1,3 +1,3 @@
# Contributing
**Icicle (icicle.io)** is an open-source project and welcomes contributions from the community. Please see our [contributing guidelines](https://github.com/icicleio/icicle/blob/master/CONTRIBUTING.md) in the [Icicle repository](https://github.com/icicleio/icicle).
**Amp (icicle.io)** is an open-source project and welcomes contributions from the community. Please see our [contributing guidelines](https://github.com/icicleio/icicle/blob/master/CONTRIBUTING.md) in the [Amp repository](https://github.com/icicleio/icicle).

View File

@ -1,8 +1,8 @@
# Concurrency for Icicle
# Concurrency for Amp
**True concurrency using native threading and multiprocessing for parallelizing code, *without* blocking.**
This library is a component for [Icicle](https://github.com/icicleio/icicle) that provides native threading, multiprocessing, process synchronization, shared memory, and task workers. Like other Icicle components, this library uses [Coroutines](https://icicle.io/docs/manual/coroutines/) built from [Awaitables](https://icicle.io/docs/manual/awaitables/) and [Generators](http://www.php.net/manual/en/language.generators.overview.php) to make writing asynchronous code more like writing synchronous code.
This library is a component for [Amp](https://github.com/icicleio/icicle) that provides native threading, multiprocessing, process synchronization, shared memory, and task workers. Like other Amp components, this library uses [Coroutines](https://icicle.io/docs/manual/coroutines/) built from [Awaitables](https://icicle.io/docs/manual/awaitables/) and [Generators](http://www.php.net/manual/en/language.generators.overview.php) to make writing asynchronous code more like writing synchronous code.
[![Build Status](https://img.shields.io/travis/icicleio/concurrent/v1.x.svg?style=flat-square)](https://travis-ci.org/icicleio/concurrent)
[![Coverage Status](https://img.shields.io/coveralls/icicleio/concurrent/v1.x.svg?style=flat-square)](https://coveralls.io/r/icicleio/concurrent)
@ -54,7 +54,7 @@ You can also manually edit `composer.json` to add this library as a project requ
### Development and Contributing
Interested in contributing to Icicle? Please see our [contributing guidelines](https://github.com/icicleio/icicle/blob/master/CONTRIBUTING.md) in the [Icicle repository](https://github.com/icicleio/icicle).
Interested in contributing to Amp? Please see our [contributing guidelines](https://github.com/icicleio/icicle/blob/master/CONTRIBUTING.md) in the [Amp repository](https://github.com/icicleio/icicle).
Want to hack on the source? A [Vagrant](http://vagrantup.com) box is provided with the repository to give a common development environment for running concurrent threads and processes, and comes with a bunch of handy tools and scripts for testing and experimentation.

View File

@ -1,47 +1,46 @@
#!/usr/bin/env php
<?php
use Amp\Concurrent\{ ChannelException, SerializationException} ;
use Amp\Concurrent\Sync\{ ChannelledStream, Internal\ExitFailure, Internal\ExitSuccess };
use Amp\Concurrent\Worker\{ BasicEnvironment, Internal\TaskRunner };
use Amp\Socket\Socket;
// Redirect all output written using echo, print, printf, etc. to STDERR.
ob_start(function ($data) {
fwrite(STDERR, $data);
return '';
}, 1, 0);
$paths = [
dirname(dirname(dirname(__DIR__))) . DIRECTORY_SEPARATOR . 'autoload.php',
dirname(__DIR__) . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php',
];
$autoloadPath = null;
foreach ($paths as $path) {
if (file_exists($path)) {
$autoloadPath = $path;
break;
(function () {
$paths = [
dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'autoload.php',
dirname(__DIR__) . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php',
];
$autoloadPath = null;
foreach ($paths as $path) {
if (file_exists($path)) {
$autoloadPath = $path;
break;
}
}
}
if (null === $autoloadPath) {
fwrite(STDERR, 'Could not locate autoload.php.');
exit(1);
}
require $autoloadPath;
})();
if (null === $autoloadPath) {
fwrite(STDERR, 'Could not locate autoload.php.');
exit(1);
}
require $autoloadPath;
use Icicle\Concurrent\Exception\{ChannelException, SerializationException};
use Icicle\Concurrent\Sync\{ChannelledStream, Internal\ExitFailure, Internal\ExitSuccess};
use Icicle\Concurrent\Worker\{BasicEnvironment, Internal\TaskRunner};
use Icicle\Coroutine;
use Icicle\Loop;
use Icicle\Stream;
Coroutine\create(function () {
$channel = new ChannelledStream(Stream\stdin(), Stream\stdout());
$environment = new BasicEnvironment();
Amp\execute(function () {
$channel = new ChannelledStream(new Socket(STDIN), new Socket(STDOUT));
$environment = new BasicEnvironment;
$runner = new TaskRunner($channel, $environment);
try {
$result = new ExitSuccess(yield from $runner->run());
$result = new ExitSuccess(yield $runner->run());
} catch (Throwable $exception) {
$result = new ExitFailure($exception);
}
@ -49,15 +48,13 @@ Coroutine\create(function () {
// Attempt to return the result.
try {
try {
return yield from $channel->send($result);
return yield $channel->send($result);
} catch (SerializationException $exception) {
// Serializing the result failed. Send the reason why.
return yield from $channel->send(new ExitFailure($exception));
return yield $channel->send(new ExitFailure($exception));
}
} catch (ChannelException $exception) {
// The result was not sendable! The parent context must have died or killed the context.
return 0;
}
})->done();
Loop\run();
});

View File

@ -21,7 +21,8 @@
}
],
"require": {
"amphp/amp": "^2.0"
"amphp/amp": "^2.0",
"amphp/socket": "dev-amp_v2 as 2.0"
},
"require-dev": {
"amphp/loop": "dev-master",
@ -41,7 +42,7 @@
},
"autoload": {
"psr-4": {
"Icicle\\Concurrent\\": "lib"
"Amp\\Concurrent\\": "lib"
},
"files": [
"lib/Worker/functions.php"
@ -49,9 +50,9 @@
},
"autoload-dev": {
"psr-4": {
"Icicle\\Benchmarks\\Concurrent\\": "benchmarks",
"Icicle\\Examples\\Concurrent\\": "examples",
"Icicle\\Tests\\Concurrent\\": "tests"
"Amp\\Benchmarks\\Concurrent\\": "benchmarks",
"Amp\\Examples\\Concurrent\\": "examples",
"Amp\\Tests\\Concurrent\\": "tests"
}
}
}

View File

@ -1,8 +1,8 @@
<?php
namespace Icicle\Examples\Concurrent;
namespace Amp\Examples\Concurrent;
use Icicle\Concurrent\Worker\Environment;
use Icicle\Concurrent\Worker\Task;
use Amp\Concurrent\Worker\Environment;
use Amp\Concurrent\Worker\Task;
class BlockingTask implements Task
{

View File

@ -2,16 +2,14 @@
<?php
require dirname(__DIR__).'/vendor/autoload.php';
use Icicle\Concurrent\Forking\Fork;
use Icicle\Coroutine;
use Icicle\Loop;
use Amp\Concurrent\Forking\Fork;
Coroutine\create(function () {
Amp\execute(function () {
$context = Fork::spawn(function () {
print "Child sleeping for 4 seconds...\n";
sleep(4);
yield from $this->send('Data sent from child.');
yield $this->send('Data sent from child.');
print "Child sleeping for 2 seconds...\n";
sleep(2);
@ -19,21 +17,19 @@ Coroutine\create(function () {
return 42;
});
$timer = Loop\periodic(1, function () use ($context) {
$timer = Amp\repeat(1000, function () use ($context) {
static $i;
$i = $i ? ++$i : 1;
print "Demonstrating how alive the parent is for the {$i}th time.\n";
});
try {
printf("Received the following from child: %s\n", yield from $context->receive());
printf("Child ended with value %d!\n", yield from $context->join());
} catch (Exception $e) {
printf("Received the following from child: %s\n", yield $context->receive());
printf("Child ended with value %d!\n", yield $context->join());
} catch (Throwable $e) {
print "Error from child!\n";
print $e."\n";
} finally {
$timer->stop();
Amp\cancel($timer);
}
});
Loop\run();

View File

@ -2,41 +2,42 @@
<?php
require dirname(__DIR__).'/vendor/autoload.php';
use Icicle\Concurrent\Threading\Thread;
use Icicle\Coroutine;
use Icicle\Loop;
use Amp\Concurrent\Threading\Thread;
use Amp\Pause;
$timer = Loop\periodic(1, function () {
static $i;
$i = $i ? ++$i : 1;
print "Demonstrating how alive the parent is for the {$i}th time.\n";
});
Coroutine\create(function () {
// Create a new child thread that does some blocking stuff.
$context = Thread::spawn(function () {
printf("\$this: %s\n", get_class($this));
printf("Received the following from parent: %s\n", yield from $this->receive());
print "Sleeping for 3 seconds...\n";
sleep(3); // Blocking call in thread.
yield from $this->send('Data sent from child.');
print "Sleeping for 2 seconds...\n";
sleep(2); // Blocking call in thread.
return 42;
Amp\execute(function () {
$timer = Amp\repeat(1000, function () {
static $i;
$i = $i ? ++$i : 1;
print "Demonstrating how alive the parent is for the {$i}th time.\n";
});
print "Waiting 2 seconds to send start data...\n";
yield Coroutine\sleep(2);
yield from $context->send('Start data');
printf("Received the following from child: %s\n", yield from $context->receive());
printf("Thread ended with value %d!\n", yield from $context->join());
})->cleanup([$timer, 'stop'])->done();
Loop\run();
try {
// Create a new child thread that does some blocking stuff.
$context = Thread::spawn(function () {
printf("\$this: %s\n", get_class($this));
printf("Received the following from parent: %s\n", yield $this->receive());
print "Sleeping for 3 seconds...\n";
sleep(3); // Blocking call in thread.
yield $this->send("Data sent from child.");
print "Sleeping for 2 seconds...\n";
sleep(2); // Blocking call in thread.
return 42;
});
print "Waiting 2 seconds to send start data...\n";
yield new Pause(2000);
yield $context->send("Start data");
printf("Received the following from child: %s\n", yield $context->receive());
printf("Thread ended with value %d!\n", yield $context->join());
} finally {
Amp\cancel($timer);
}
});

View File

@ -2,43 +2,45 @@
<?php
require dirname(__DIR__).'/vendor/autoload.php';
use Icicle\Awaitable;
use Icicle\Concurrent\Worker\DefaultPool;
use Icicle\Coroutine;
use Icicle\Examples\Concurrent\BlockingTask;
use Icicle\Loop;
use Amp\Concurrent\Worker\DefaultPool;
use Amp\Coroutine;
use Amp\Examples\Concurrent\BlockingTask;
Coroutine\create(function() {
Amp\execute(function() {
$timer = Amp\repeat(100, function () {
printf(".\n");
});
Amp\unreference($timer);
$pool = new DefaultPool();
$pool->start();
$coroutines = [];
$coroutines[] = Coroutine\create(function () use ($pool) {
$coroutines[] = function () use ($pool) {
$url = 'https://google.com';
$result = yield from $pool->enqueue(new BlockingTask('file_get_contents', $url));
$result = yield $pool->enqueue(new BlockingTask('file_get_contents', $url));
printf("Read from %s: %d bytes\n", $url, strlen($result));
});
};
$coroutines[] = Coroutine\create(function () use ($pool) {
$url = 'https://icicle.io';
$result = yield from $pool->enqueue(new BlockingTask('file_get_contents', $url));
$coroutines[] = function () use ($pool) {
$url = 'http://amphp.org';
$result = yield $pool->enqueue(new BlockingTask('file_get_contents', $url));
printf("Read from %s: %d bytes\n", $url, strlen($result));
});
};
$coroutines[] = Coroutine\create(function () use ($pool) {
$coroutines[] = function () use ($pool) {
$url = 'https://github.com';
$result = yield from $pool->enqueue(new BlockingTask('file_get_contents', $url));
$result = yield $pool->enqueue(new BlockingTask('file_get_contents', $url));
printf("Read from %s: %d bytes\n", $url, strlen($result));
});
};
$coroutines = array_map(function (callable $coroutine): Coroutine {
return new Coroutine($coroutine());
}, $coroutines);
yield Awaitable\all($coroutines);
yield Amp\all($coroutines);
return yield from $pool->shutdown();
})->done();
return yield $pool->shutdown();
});
Loop\periodic(0.1, function () {
printf(".\n");
})->unreference();
Loop\run();

View File

@ -2,22 +2,18 @@
<?php
require dirname(__DIR__).'/vendor/autoload.php';
use Icicle\Concurrent\Worker\DefaultWorkerFactory;
use Icicle\Coroutine;
use Icicle\Examples\Concurrent\BlockingTask;
use Icicle\Loop;
use Amp\Concurrent\Worker\DefaultWorkerFactory;
use Amp\Examples\Concurrent\BlockingTask;
Coroutine\create(function () {
Amp\execute(function () {
$factory = new DefaultWorkerFactory();
$worker = $factory->create();
$worker->start();
$result = yield from $worker->enqueue(new BlockingTask('file_get_contents', 'https://google.com'));
$result = yield $worker->enqueue(new BlockingTask('file_get_contents', 'https://google.com'));
printf("Read %d bytes\n", strlen($result));
$code = yield from $worker->shutdown();
$code = yield $worker->shutdown();
printf("Code: %d\n", $code);
})->done();
Loop\run();
});

View File

@ -1,10 +1,9 @@
<?php
namespace Icicle\Concurrent\Exception;
class ChannelException extends \Exception implements Exception
{
public function __construct(string $message, \Throwable $previous = null)
{
namespace Amp\Concurrent;
class ChannelException extends \Exception {
public function __construct(string $message, \Throwable $previous = null) {
parent::__construct($message, 0, $previous);
}
}

View File

@ -1,8 +1,10 @@
<?php
namespace Icicle\Concurrent;
interface Context
{
namespace Amp\Concurrent;
use Interop\Async\Awaitable;
interface Context {
/**
* @return bool
*/
@ -19,11 +21,7 @@ interface Context
public function kill();
/**
* @coroutine
*
* @return \Generator
*
* @resolve mixed
* @return \Interop\Async\Awaitable<mixed> Resolves with the returned from the context.
*/
public function join(): \Generator;
public function join(): Awaitable;
}

5
lib/ContextException.php Normal file
View File

@ -0,0 +1,5 @@
<?php
namespace Amp\Concurrent;
class ContextException extends \Exception {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
interface Error extends \Icicle\Exception\Error, Throwable {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
interface Exception extends \Icicle\Exception\Exception, Throwable {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
class ForkException extends \Exception implements Exception {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
class LockAlreadyReleasedError extends \Error implements Error {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
class MutexException extends \Exception implements Exception {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
class ProcessException extends \Exception implements Exception {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
class SemaphoreException extends \Exception implements Exception {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
class SharedMemoryException extends \Exception implements Exception {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
class StatusError extends \Error implements Exception {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
class SynchronizationError extends \Error implements Error {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
class ThreadException extends \Exception implements Exception {}

View File

@ -1,4 +0,0 @@
<?php
namespace Icicle\Concurrent\Exception;
interface Throwable extends \Icicle\Exception\Throwable {}

View File

@ -1,28 +1,33 @@
<?php
namespace Icicle\Concurrent\Forking;
use Icicle\Concurrent\Exception\{ForkException, ChannelException, SerializationException, StatusError, SynchronizationError};
use Icicle\Concurrent\{Process, Strand};
use Icicle\Concurrent\Sync\{Channel, ChannelledStream};
use Icicle\Concurrent\Sync\Internal\{ExitFailure, ExitStatus, ExitSuccess};
use Icicle\Coroutine\Coroutine;
use Icicle\Exception\{InvalidArgumentError, UnsupportedError};
use Icicle\Loop;
use Icicle\Stream;
use Icicle\Stream\Pipe\DuplexPipe;
namespace Amp\Concurrent\Forking;
use Amp\Concurrent\{
ContextException,
ChannelException,
Process,
SerializationException,
StatusError,
Strand,
SynchronizationError
};
use Amp\Concurrent\Sync\{ Channel, ChannelledStream };
use Amp\Concurrent\Sync\Internal\{ ExitFailure, ExitStatus, ExitSuccess };
use Amp\Coroutine;
use Amp\Socket\Socket;
use Interop\Async\Awaitable;
/**
* Implements a UNIX-compatible context using forked processes.
*/
class Fork implements Process, Strand
{
class Fork implements Process, Strand {
/**
* @var \Icicle\Concurrent\Sync\Channel A channel for communicating with the child.
* @var \Amp\Concurrent\Sync\Channel A channel for communicating with the child.
*/
private $channel;
/**
* @var \Icicle\Stream\Pipe\DuplexPipe
* @var \Amp\Socket\Socket
*/
private $pipe;
@ -51,9 +56,8 @@ class Fork implements Process, Strand
*
* @return bool True if forking is enabled, otherwise false.
*/
public static function enabled(): bool
{
return extension_loaded('pcntl');
public static function supported(): bool {
return \extension_loaded('pcntl');
}
/**
@ -61,36 +65,32 @@ class Fork implements Process, Strand
*
* @param callable $function A callable to invoke in the process.
*
* @return \Icicle\Concurrent\Forking\Fork The process object that was spawned.
* @return \Amp\Concurrent\Forking\Fork The process object that was spawned.
*/
public static function spawn(callable $function, ...$args): self
{
public static function spawn(callable $function, ...$args): self {
$fork = new self($function, ...$args);
$fork->start();
return $fork;
}
public function __construct(callable $function, ...$args)
{
if (!self::enabled()) {
throw new UnsupportedError("The pcntl extension is required to create forks.");
public function __construct(callable $function, ...$args) {
if (!self::supported()) {
throw new \Error("The pcntl extension is required to create forks.");
}
$this->function = $function;
$this->args = $args;
}
public function __clone()
{
public function __clone() {
$this->pid = 0;
$this->oid = 0;
$this->pipe = null;
$this->channel = null;
}
public function __destruct()
{
if (0 !== $this->pid && posix_getpid() === $this->oid) { // Only kill in owner process.
public function __destruct() {
if (0 !== $this->pid && \posix_getpid() === $this->oid) { // Only kill in owner process.
$this->kill(); // Will only terminate if the process is still running.
}
}
@ -100,9 +100,8 @@ class Fork implements Process, Strand
*
* @return bool True if the context is running, otherwise false.
*/
public function isRunning(): bool
{
return 0 !== $this->pid && false !== posix_getpgid($this->pid);
public function isRunning(): bool {
return 0 !== $this->pid && false !== \posix_getpgid($this->pid);
}
/**
@ -110,8 +109,7 @@ class Fork implements Process, Strand
*
* @return int The process ID.
*/
public function getPid(): int
{
public function getPid(): int {
return $this->pid;
}
@ -125,15 +123,14 @@ class Fork implements Process, Strand
*
* @return float A priority value between 0 and 1.
*
* @throws ForkException If the operation failed.
* @throws ContextException If the operation failed.
*
* @see Fork::setPriority()
* @see http://linux.die.net/man/2/getpriority
*/
public function getPriority(): float
{
if (($nice = pcntl_getpriority($this->pid)) === false) {
throw new ForkException('Failed to get the fork\'s priority.');
public function getPriority(): float {
if (($nice = \pcntl_getpriority($this->pid)) === false) {
throw new ContextException('Failed to get the fork\'s priority.');
}
return (19 - $nice) / 39;
@ -146,72 +143,61 @@ class Fork implements Process, Strand
*
* @param float $priority A priority value between 0 and 1.
*
* @throws InvalidArgumentError If the given priority is an invalid value.
* @throws ForkException If the operation failed.
* @throws \Error If the given priority is an invalid value.
* @throws ContextException If the operation failed.
*
* @see Fork::getPriority()
*/
public function setPriority(float $priority): float
{
public function setPriority(float $priority): float {
if ($priority < 0 || $priority > 1) {
throw new InvalidArgumentError('Priority value must be between 0.0 and 1.0.');
throw new \Error('Priority value must be between 0.0 and 1.0.');
}
$nice = round(19 - ($priority * 39));
$nice = \round(19 - ($priority * 39));
if (!pcntl_setpriority($nice, $this->pid, PRIO_PROCESS)) {
throw new ForkException('Failed to set the fork\'s priority.');
if (!\pcntl_setpriority($nice, $this->pid, \PRIO_PROCESS)) {
throw new ContextException('Failed to set the fork\'s priority.');
}
}
/**
* Starts the context execution.
*
* @throws \Icicle\Concurrent\Exception\ForkException If forking fails.
* @throws \Icicle\Stream\Exception\FailureException If creating a socket pair fails.
* @throws \Amp\Concurrent\ContextException If forking fails.
* @throws \Amp\Socket\SocketException If creating a socket pair fails.
*/
public function start()
{
public function start() {
if (0 !== $this->oid) {
throw new StatusError('The context has already been started.');
}
list($parent, $child) = Stream\pair();
list($parent, $child) = \Amp\Socket\pair();
switch ($pid = pcntl_fork()) {
switch ($pid = \pcntl_fork()) {
case -1: // Failure
throw new ForkException('Could not fork process!');
throw new ContextException('Could not fork process!');
case 0: // Child
// @codeCoverageIgnoreStart
// Create a new event loop in the fork.
Loop\loop($loop = Loop\create(false));
$channel = new ChannelledStream($pipe = new DuplexPipe($parent));
fclose($child);
$coroutine = new Coroutine($this->execute($channel));
$coroutine->done();
\fclose($child);
try {
$loop->run();
\Amp\execute(function () use ($parent) {
$channel = new ChannelledStream(new Socket($parent));
return new Coroutine($this->execute($channel));
});
$code = 0;
} catch (\Throwable $exception) {
$code = 1;
}
$pipe->close();
exit($code);
// @codeCoverageIgnoreEnd
default: // Parent
$this->pid = $pid;
$this->oid = posix_getpid();
$this->channel = new ChannelledStream($this->pipe = new DuplexPipe($child));
fclose($parent);
$this->oid = \posix_getpid();
$this->channel = new ChannelledStream($this->pipe = new Socket($child));
\fclose($parent);
}
}
@ -220,24 +206,33 @@ class Fork implements Process, Strand
*
* This method is run only on the child.
*
* @param \Icicle\Concurrent\Sync\Channel $channel
* @param \Amp\Concurrent\Sync\Channel $channel
*
* @return \Generator
*
* @codeCoverageIgnore Only executed in the child.
*/
private function execute(Channel $channel): \Generator
{
private function execute(Channel $channel): \Generator {
try {
if ($this->function instanceof \Closure) {
$function = $this->function->bindTo($channel, Channel::class);
}
if (empty($function)) {
$function = $this->function;
}
$result = new ExitSuccess(yield $function(...$this->args));
$result = $function(...$this->args);
if ($result instanceof \Generator) {
$result = new Coroutine($result);
}
if ($result instanceof Awaitable) {
$result = yield $result;
}
$result = new ExitSuccess($result);
} catch (\Throwable $exception) {
$result = new ExitFailure($exception);
}
@ -245,10 +240,10 @@ class Fork implements Process, Strand
// Attempt to return the result.
try {
try {
return yield from $channel->send($result);
return yield $channel->send($result);
} catch (SerializationException $exception) {
// Serializing the result failed. Send the reason why.
return yield from $channel->send(new ExitFailure($exception));
return yield $channel->send(new ExitFailure($exception));
}
} catch (ChannelException $exception) {
// The result was not sendable! The parent context must have died or killed the context.
@ -259,14 +254,13 @@ class Fork implements Process, Strand
/**
* {@inheritdoc}
*/
public function kill()
{
public function kill() {
if ($this->isRunning()) {
// Forcefully kill the process using SIGKILL.
posix_kill($this->pid, SIGKILL);
\posix_kill($this->pid, SIGKILL);
}
if (null !== $this->pipe && $this->pipe->isOpen()) {
if (null !== $this->pipe && $this->pipe->isReadable()) {
$this->pipe->close();
}
@ -278,87 +272,91 @@ class Fork implements Process, Strand
/**
* @param int $signo
*
* @throws \Icicle\Concurrent\Exception\StatusError
* @throws \Amp\Concurrent\StatusError
*/
public function signal(int $signo)
{
public function signal(int $signo) {
if (0 === $this->pid) {
throw new StatusError('The fork has not been started or has already finished.');
}
posix_kill($this->pid, (int) $signo);
\posix_kill($this->pid, (int) $signo);
}
/**
* @coroutine
*
* Gets a promise that resolves when the context ends and joins with the
* parent context.
*
* @return \Generator
* @return \Interop\Async\Awaitable<int>
*
* @resolve mixed Resolved with the return or resolution value of the context once it has completed execution.
*
* @throws \Icicle\Concurrent\Exception\StatusError Thrown if the context has not been started.
* @throws \Icicle\Concurrent\Exception\SynchronizationError Thrown if an exit status object is not received.
* @throws \Amp\Concurrent\StatusError Thrown if the context has not been started.
* @throws \Amp\Concurrent\SynchronizationError Thrown if an exit status object is not received.
*/
public function join(): \Generator
{
public function join(): Awaitable {
if (null === $this->channel) {
throw new StatusError('The fork has not been started or has already finished.');
}
return new Coroutine($this->doJoin());
}
/**
* @coroutine
*
* @return \Generator
*
* @throws \Amp\Concurrent\SynchronizationError
*/
private function doJoin(): \Generator {
try {
$response = yield from $this->channel->receive();
$response = yield $this->channel->receive();
if (!$response instanceof ExitStatus) {
throw new SynchronizationError(sprintf(
throw new SynchronizationError(\sprintf(
'Did not receive an exit status from fork. Instead received data of type %s',
is_object($response) ? get_class($response) : gettype($response)
\is_object($response) ? \get_class($response) : \gettype($response)
));
}
return $response->getResult();
} finally {
$this->kill();
}
}
/**
* {@inheritdoc}
*/
public function receive(): \Generator
{
public function receive(): Awaitable {
if (null === $this->channel) {
throw new StatusError('The fork has not been started or has already finished.');
throw new StatusError('The process has not been started.');
}
$data = yield from $this->channel->receive();
if ($data instanceof ExitStatus) {
$data = $data->getResult();
throw new SynchronizationError(sprintf(
'Fork unexpectedly exited with result of type: %s',
is_object($data) ? get_class($data) : gettype($data)
));
}
return $data;
return \Amp\pipe($this->channel->receive(), static function ($data) {
if ($data instanceof ExitStatus) {
$data = $data->getResult();
throw new SynchronizationError(\sprintf(
'Forked process unexpectedly exited with result of type: %s',
\is_object($data) ? \get_class($data) : \gettype($data)
));
}
return $data;
});
}
/**
* {@inheritdoc}
*/
public function send($data): \Generator
{
public function send($data): Awaitable {
if (null === $this->channel) {
throw new StatusError('The fork has not been started or has already finished.');
}
if ($data instanceof ExitStatus) {
throw new InvalidArgumentError('Cannot send exit status objects.');
throw new \Error('Cannot send exit status objects.');
}
return yield from $this->channel->send($data);
return $this->channel->send($data);
}
}

View File

@ -0,0 +1,5 @@
<?php
namespace Amp\Concurrent;
class LockAlreadyReleasedError extends \Error {}

5
lib/MutexException.php Normal file
View File

@ -0,0 +1,5 @@
<?php
namespace Amp\Concurrent;
class MutexException extends \Exception {}

View File

@ -1,8 +1,8 @@
<?php
namespace Icicle\Concurrent\Exception;
class PanicError extends \Error implements Error
{
namespace Amp\Concurrent;
class PanicError extends \Error {
/**
* @var string Stack trace of the panic.
*/
@ -15,8 +15,7 @@ class PanicError extends \Error implements Error
* @param int $code The panic code.
* @param string $trace The panic stack trace.
*/
public function __construct($message = '', $code = 0, $trace = '')
{
public function __construct(string $message = '', int $code = 0, string $trace = '') {
parent::__construct($message, $code);
$this->trace = $trace;
}
@ -26,8 +25,7 @@ class PanicError extends \Error implements Error
*
* @return string
*/
public function getPanicTrace()
{
public function getPanicTrace() {
return $this->trace;
}
}

View File

@ -1,8 +1,8 @@
<?php
namespace Icicle\Concurrent;
interface Process extends Context
{
namespace Amp\Concurrent;
interface Process extends Context {
/**
* @return int PID of process.
*/
@ -11,7 +11,7 @@ interface Process extends Context
/**
* @param int $signo
*
* @throws \Icicle\Concurrent\Exception\StatusError
* @throws \Amp\Concurrent\StatusError
*/
public function signal(int $signo);
}

View File

@ -1,20 +1,19 @@
<?php
namespace Icicle\Concurrent\Process;
use Icicle\Concurrent\Exception\{StatusError, SynchronizationError};
use Icicle\Concurrent\{Process as ProcessContext, Strand};
use Icicle\Concurrent\Sync\{ChannelledStream, Internal\ExitStatus};
use Icicle\Exception\InvalidArgumentError;
namespace Amp\Concurrent\Process;
class ChannelledProcess implements ProcessContext, Strand
{
use Amp\Concurrent\{ Process as ProcessContext, StatusError, Strand, SynchronizationError };
use Amp\Concurrent\Sync\{ ChannelledStream, Internal\ExitStatus };
use Interop\Async\Awaitable;
class ChannelledProcess implements ProcessContext, Strand {
/**
* @var \Icicle\Concurrent\Process\Process
* @var \Amp\Concurrent\Process\Process
*/
private $process;
/**
* @var \Icicle\Concurrent\Sync\Channel
* @var \Amp\Concurrent\Sync\Channel
*/
private $channel;
@ -23,18 +22,15 @@ class ChannelledProcess implements ProcessContext, Strand
* @param string $cwd Working directory.
* @param mixed[] $env Array of environment variables.
*/
public function __construct(string $path, string $cwd = '', array $env = [])
{
$command = PHP_BINARY . ' ' . $path;
public function __construct(string $path, string $cwd = '', array $env = []) {
$command = \PHP_BINARY . ' ' . $path;
$this->process = new Process($command, $cwd, $env);
}
/**
* Resets process values.
*/
public function __clone()
{
public function __clone() {
$this->process = clone $this->process;
$this->channel = null;
}
@ -42,88 +38,79 @@ class ChannelledProcess implements ProcessContext, Strand
/**
* {@inheritdoc}
*/
public function start()
{
public function start() {
$this->process->start();
$this->channel = new ChannelledStream($this->process->getStdOut(), $this->process->getStdIn());
}
/**
* {@inheritdoc}
*/
public function isRunning(): bool
{
public function isRunning(): bool {
return $this->process->isRunning();
}
/**
* {@inheritdoc}
*/
public function receive(): \Generator
{
public function receive(): Awaitable {
if (null === $this->channel) {
throw new StatusError('The process has not been started.');
}
$data = yield from $this->channel->receive();
if ($data instanceof ExitStatus) {
$data = $data->getResult();
throw new SynchronizationError(sprintf(
'Thread unexpectedly exited with result of type: %s',
is_object($data) ? get_class($data) : gettype($data)
));
}
return $data;
return \Amp\pipe($this->channel->receive(), static function ($data) {
if ($data instanceof ExitStatus) {
$data = $data->getResult();
throw new SynchronizationError(\sprintf(
'Thread unexpectedly exited with result of type: %s',
\is_object($data) ? \get_class($data) : \gettype($data)
));
}
return $data;
});
}
/**
* {@inheritdoc}
*/
public function send($data): \Generator
{
public function send($data): Awaitable {
if (null === $this->channel) {
throw new StatusError('The process has not been started.');
}
if ($data instanceof ExitStatus) {
throw new InvalidArgumentError('Cannot send exit status objects.');
throw new \Error('Cannot send exit status objects.');
}
return yield from $this->channel->send($data);
return $this->channel->send($data);
}
/**
* {@inheritdoc}
*/
public function join(): \Generator
{
public function join(): Awaitable {
return $this->process->join();
}
/**
* {@inheritdoc}
*/
public function kill()
{
public function kill() {
$this->process->kill();
}
/**
* {@inheritdoc}
*/
public function getPid(): int
{
public function getPid(): int {
return $this->process->getPid();
}
/**
* {@inheritdoc}
*/
public function signal(int $signo)
{
public function signal(int $signo) {
$this->process->signal($signo);
}
}

View File

@ -1,15 +1,14 @@
<?php
namespace Icicle\Concurrent\Process;
use Icicle\Awaitable\Delayed;
use Icicle\Concurrent\Exception\{ProcessException, StatusError};
use Icicle\Concurrent\Process as ProcessContext;
use Icicle\Loop;
use Icicle\Stream\Pipe\{ReadablePipe, WritablePipe};
use Icicle\Stream\{ReadableStream, WritableStream};
namespace Amp\Concurrent\Process;
class Process implements ProcessContext
{
use Amp\Deferred;
use Amp\Concurrent\{ ContextException, Process as ProcessContext, StatusError };
use Amp\Socket\Socket;
use Amp\Stream\Stream;
use Interop\Async\{ Awaitable, Loop };
class Process implements ProcessContext {
/**
* @var resource|null
*/
@ -36,17 +35,17 @@ class Process implements ProcessContext
private $options;
/**
* @var \Icicle\Stream\Pipe\WritablePipe|null
* @var \Amp\Stream\Stream|null
*/
private $stdin;
/**
* @var \Icicle\Stream\Pipe\ReadablePipe|null
* @var \Amp\Stream\Stream|null
*/
private $stdout;
/**
* @var \Icicle\Stream\Pipe\ReadablePipe|null
* @var \Amp\Stream\Stream|null
*/
private $stderr;
@ -61,14 +60,14 @@ class Process implements ProcessContext
private $oid = 0;
/**
* @var \Icicle\Awaitable\Delayed|null
* @var \Amp\Deferred|null
*/
private $delayed;
private $deferred;
/**
* @var \Icicle\Loop\Watcher\Io|null
* @var string
*/
private $poll;
private $watcher;
/**
* @param string $command Command to run.
@ -77,8 +76,7 @@ class Process implements ProcessContext
* @param mixed[] $env Environment variables or use an empty array to inherit from the current PHP process.
* @param mixed[] $options Options for proc_open().
*/
public function __construct(string $command, string $cwd = '', array $env = [], array $options = [])
{
public function __construct(string $command, string $cwd = '', array $env = [], array $options = []) {
$this->command = $command;
if ('' !== $cwd) {
@ -86,7 +84,7 @@ class Process implements ProcessContext
}
foreach ($env as $key => $value) {
if (!is_array($value)) { // $env cannot accept array values.
if (!\is_array($value)) { // $env cannot accept array values.
$this->env[(string) $key] = (string) $value;
}
}
@ -97,9 +95,8 @@ class Process implements ProcessContext
/**
* Stops the process if it is still running.
*/
public function __destruct()
{
if (getmypid() === $this->oid) {
public function __destruct() {
if (\getmypid() === $this->oid) {
$this->kill(); // Will only terminate if the process is still running.
if (null !== $this->stdin) {
@ -119,11 +116,10 @@ class Process implements ProcessContext
/**
* Resets process values.
*/
public function __clone()
{
public function __clone() {
$this->process = null;
$this->delayed = null;
$this->poll = null;
$this->deferred = null;
$this->watcher = null;
$this->pid = 0;
$this->oid = 0;
$this->stdin = null;
@ -132,16 +128,15 @@ class Process implements ProcessContext
}
/**
* @throws \Icicle\Concurrent\Exception\ProcessException If starting the process fails.
* @throws \Icicle\Concurrent\Exception\StatusError If the process is already running.
* @throws \Amp\Concurrent\ContextException If starting the process fails.
* @throws \Amp\Concurrent\StatusError If the process is already running.
*/
public function start()
{
if (null !== $this->delayed) {
public function start() {
if (null !== $this->deferred) {
throw new StatusError('The process has already been started.');
}
$this->delayed = new Delayed();
$this->deferred = new Deferred;
$fd = [
['pipe', 'r'], // stdin
@ -150,50 +145,49 @@ class Process implements ProcessContext
['pipe', 'w'], // exit code pipe
];
$nd = 0 === strncasecmp(PHP_OS, 'WIN', 3) ? 'NUL' : '/dev/null';
$nd = 0 === \strncasecmp(PHP_OS, 'WIN', 3) ? 'NUL' : '/dev/null';
$command = sprintf('(%s) 3>%s; code=$?; echo $code >&3; exit $code', $this->command, $nd);
$command = \sprintf('(%s) 3>%s; code=$?; echo $code >&3; exit $code', $this->command, $nd);
$this->process = proc_open($command, $fd, $pipes, $this->cwd ?: null, $this->env ?: null, $this->options);
$this->process = \proc_open($command, $fd, $pipes, $this->cwd ?: null, $this->env ?: null, $this->options);
if (!is_resource($this->process)) {
throw new ProcessException('Could not start process.');
if (!\is_resource($this->process)) {
throw new ContextException('Could not start process.');
}
$this->oid = getmypid();
$status = proc_get_status($this->process);
$this->oid = \getmypid();
$status = \proc_get_status($this->process);
if (!$status) {
proc_close($this->process);
\proc_close($this->process);
$this->process = null;
throw new ProcessException('Could not get process status.');
throw new ContextException('Could not get process status.');
}
$this->pid = $status['pid'];
$this->stdin = new WritablePipe($pipes[0]);
$this->stdout = new ReadablePipe($pipes[1]);
$this->stderr = new ReadablePipe($pipes[2]);
$this->stdin = new Socket($pipes[0]);
$this->stdout = new Socket($pipes[1]);
$this->stderr = new Socket($pipes[2]);
$stream = $pipes[3];
stream_set_blocking($stream, 0);
\stream_set_blocking($stream, false);
$this->poll = Loop\poll($stream, function ($resource) {
if (!is_resource($resource) || feof($resource)) {
$this->watcher = Loop::onReadable($stream, function ($watcher, $resource) {
if (!\is_resource($resource) || \feof($resource)) {
$this->close($resource);
$this->delayed->reject(new ProcessException('Process ended unexpectedly.'));
$this->deferred->fail(new ContextException('Process ended unexpectedly.'));
} else {
$code = fread($resource, 1);
$code = \fread($resource, 1);
$this->close($resource);
if (!strlen($code) || !is_numeric($code)) {
$this->delayed->reject(new ProcessException('Process ended without providing a status code.'));
if (!\strlen($code) || !\is_numeric($code)) {
$this->deferred->fail(new ContextException('Process ended without providing a status code.'));
} else {
$this->delayed->resolve((int) $code);
$this->deferred->resolve((int) $code);
}
}
$this->poll->free();
Loop::cancel($this->watcher);
});
}
@ -202,14 +196,13 @@ class Process implements ProcessContext
*
* @param resource $resource
*/
private function close($resource)
{
if (is_resource($resource)) {
fclose($resource);
private function close($resource) {
if (\is_resource($resource)) {
\fclose($resource);
}
if (is_resource($this->process)) {
proc_close($this->process);
if (\is_resource($this->process)) {
\proc_close($this->process);
$this->process = null;
}
@ -217,36 +210,34 @@ class Process implements ProcessContext
}
/**
* @coroutine
* @return \Interop\Async\Awaitable<int> Resolves with exit status.
*
* @return \Generator
*
* @throws \Icicle\Concurrent\Exception\StatusError If the process has not been started.
* @throws \Amp\Concurrent\StatusError If the process has not been started.
*/
public function join(): \Generator
{
if (null === $this->delayed) {
public function join(): Awaitable {
if (null === $this->deferred) {
throw new StatusError('The process has not been started.');
}
$this->poll->listen();
Loop::enable($this->watcher);
try {
return yield $this->delayed;
} finally {
$awaitable = $this->deferred->getAwaitable();
$awaitable->when(function () {
$this->stdout->close();
$this->stderr->close();
}
});
return $awaitable;
}
/**
* {@inheritdoc}
*/
public function kill()
{
if (is_resource($this->process)) {
public function kill() {
if (\is_resource($this->process)) {
// Forcefully kill the process using SIGKILL.
proc_terminate($this->process, 9);
\proc_terminate($this->process, 9);
// "Detach" from the process and let it die asynchronously.
$this->process = null;
@ -258,15 +249,14 @@ class Process implements ProcessContext
*
* @param int $signo Signal number to send to process.
*
* @throws \Icicle\Concurrent\Exception\StatusError If the process is not running.
* @throws \Amp\Concurrent\StatusError If the process is not running.
*/
public function signal(int $signo)
{
public function signal(int $signo) {
if (!$this->isRunning()) {
throw new StatusError('The process is not running.');
}
proc_terminate($this->process, (int) $signo);
\proc_terminate($this->process, (int) $signo);
}
/**
@ -275,8 +265,7 @@ class Process implements ProcessContext
*
* @return int
*/
public function getPid(): int
{
public function getPid(): int {
return $this->pid;
}
@ -285,8 +274,7 @@ class Process implements ProcessContext
*
* @return string The command to execute.
*/
public function getCommand(): string
{
public function getCommand(): string {
return $this->command;
}
@ -295,10 +283,9 @@ class Process implements ProcessContext
*
* @return string The current working directory or null if inherited from the current PHP process.
*/
public function getWorkingDirectory(): string
{
public function getWorkingDirectory(): string {
if ('' === $this->cwd) {
return getcwd() ?: '';
return \getcwd() ?: '';
}
return $this->cwd;
@ -309,8 +296,7 @@ class Process implements ProcessContext
*
* @return mixed[] Array of environment variables.
*/
public function getEnv(): array
{
public function getEnv(): array {
return $this->env;
}
@ -329,20 +315,18 @@ class Process implements ProcessContext
*
* @return bool
*/
public function isRunning(): bool
{
return is_resource($this->process);
public function isRunning(): bool {
return \is_resource($this->process);
}
/**
* Gets the process input stream (STDIN).
*
* @return \Icicle\Stream\WritableStream
* @return \Amp\Stream\Stream
*
* @throws \Icicle\Concurrent\Exception\StatusError If the process is not running.
* @throws \Amp\Concurrent\StatusError If the process is not running.
*/
public function getStdIn(): WritableStream
{
public function getStdIn(): Stream {
if (null === $this->stdin) {
throw new StatusError('The process has not been started.');
}
@ -353,12 +337,11 @@ class Process implements ProcessContext
/**
* Gets the process output stream (STDOUT).
*
* @return \Icicle\Stream\ReadableStream
* @return \Amp\Stream\Stream
*
* @throws \Icicle\Concurrent\Exception\StatusError If the process is not running.
* @throws \Amp\Concurrent\StatusError If the process is not running.
*/
public function getStdOut(): ReadableStream
{
public function getStdOut(): Stream {
if (null === $this->stdout) {
throw new StatusError('The process has not been started.');
}
@ -369,12 +352,11 @@ class Process implements ProcessContext
/**
* Gets the process error stream (STDERR).
*
* @return \Icicle\Stream\ReadableStream
* @return \Amp\Stream\Stream
*
* @throws \Icicle\Concurrent\Exception\StatusError If the process is not running.
* @throws \Amp\Concurrent\StatusError If the process is not running.
*/
public function getStdErr(): ReadableStream
{
public function getStdErr(): Stream {
if (null === $this->stderr) {
throw new StatusError('The process has not been started.');
}

View File

@ -0,0 +1,5 @@
<?php
namespace Amp\Concurrent;
class SemaphoreException extends \Exception {}

View File

@ -1,4 +1,5 @@
<?php
namespace Icicle\Concurrent\Exception;
namespace Amp\Concurrent;
class SerializationException extends ChannelException {}

View File

@ -0,0 +1,5 @@
<?php
namespace Amp\Concurrent;
class SharedMemoryException extends \Exception {}

5
lib/StatusError.php Normal file
View File

@ -0,0 +1,5 @@
<?php
namespace Amp\Concurrent;
class StatusError extends \Error {}

View File

@ -1,4 +1,5 @@
<?php
namespace Icicle\Concurrent;
namespace Amp\Concurrent;
interface Strand extends Context, Sync\Channel {}

View File

@ -1,41 +1,35 @@
<?php
namespace Icicle\Concurrent\Sync;
namespace Amp\Concurrent\Sync;
use Interop\Async\Awaitable;
/**
* Interface for sending messages between execution contexts.
*/
interface Channel
{
interface Channel {
/**
* @coroutine
* @return \Interop\Async\Awaitable<mixed>
*
* @return \Generator
*
* @resolve mixed
*
* @throws \Icicle\Concurrent\Exception\StatusError Thrown if the context has not been started.
* @throws \Icicle\Concurrent\Exception\SynchronizationError If the context has not been started or the context
* @throws \Amp\Concurrent\StatusError Thrown if the context has not been started.
* @throws \Amp\Concurrent\SynchronizationError If the context has not been started or the context
* unexpectedly ends.
* @throws \Icicle\Concurrent\Exception\ChannelException If receiving from the channel fails.
* @throws \Icicle\Concurrent\Exception\SerializationException If unserializing the data fails.
* @throws \Amp\Concurrent\ChannelException If receiving from the channel fails.
* @throws \Amp\Concurrent\SerializationException If unserializing the data fails.
*/
public function receive(): \Generator;
public function receive(): Awaitable;
/**
* @coroutine
*
* @param mixed $data
*
* @return \Generator
* @return \Interop\Async\Awaitable<int> Resolves with the number of bytes sent on the channel.
*
* @resolve int
*
* @throws \Icicle\Concurrent\Exception\StatusError Thrown if the context has not been started.
* @throws \Icicle\Concurrent\Exception\SynchronizationError If the context has not been started or the context
* @throws \Amp\Concurrent\StatusError Thrown if the context has not been started.
* @throws \Amp\Concurrent\SynchronizationError If the context has not been started or the context
* unexpectedly ends.
* @throws \Icicle\Concurrent\Exception\ChannelException If sending on the channel fails.
* @throws \Icicle\Exception\InvalidArgumentError If an ExitStatus object is given.
* @throws \Icicle\Concurrent\Exception\SerializationException If serializing the data fails.
* @throws \Amp\Concurrent\ChannelException If sending on the channel fails.
* @throws \Error If an ExitStatus object is given.
* @throws \Amp\Concurrent\SerializationException If serializing the data fails.
*/
public function send($data): \Generator;
public function send($data): Awaitable;
}

View File

@ -1,26 +1,27 @@
<?php
namespace Icicle\Concurrent\Sync;
use Icicle\Concurrent\Exception\{ChannelException, SerializationException};
use Icicle\Exception\InvalidArgumentError;
use Icicle\Stream\{DuplexStream, ReadableStream, WritableStream};
namespace Amp\Concurrent\Sync;
use Amp\Concurrent\{ChannelException, SerializationException};
use Amp\Coroutine;
use Amp\Stream\Stream;
use Interop\Async\Awaitable;
/**
* An asynchronous channel for sending data between threads and processes.
*
* Supports full duplex read and write.
*/
class ChannelledStream implements Channel
{
class ChannelledStream implements Channel {
const HEADER_LENGTH = 5;
/**
* @var \Icicle\Stream\ReadableStream
* @var \Amp\Stream\Stream
*/
private $read;
/**
* @var \Icicle\Stream\WritableStream
* @var \Amp\Stream\Stream
*/
private $write;
@ -32,18 +33,11 @@ class ChannelledStream implements Channel
/**
* Creates a new channel instance.
*
* @param \Icicle\Stream\ReadableStream $read
* @param \Icicle\Stream\WritableStream|null $write
*
* @throws \Icicle\Exception\InvalidArgumentError Thrown if no write stream is provided and the read
* stream is not a duplex stream.
* @param \Amp\Stream\Stream $read
* @param \Amp\Stream\Stream|null $write
*/
public function __construct(ReadableStream $read, WritableStream $write = null)
{
public function __construct(Stream $read, Stream $write = null) {
if (null === $write) {
if (!$read instanceof DuplexStream) {
throw new InvalidArgumentError('Must provide a duplex stream if not providing a write stream.');
}
$this->write = $read;
} else {
$this->write = $write;
@ -51,76 +45,71 @@ class ChannelledStream implements Channel
$this->read = $read;
$this->errorHandler = function ($errno, $errstr) {
throw new ChannelException(sprintf('Received corrupted data. Errno: %d; %s', $errno, $errstr));
$this->errorHandler = static function ($errno, $errstr) {
throw new ChannelException(\sprintf('Received corrupted data. Errno: %d; %s', $errno, $errstr));
};
}
/**
* {@inheritdoc}
*/
public function send($data): \Generator
{
public function send($data): Awaitable {
return new Coroutine($this->doSend($data));
}
public function doSend($data): \Generator {
// Serialize the data to send into the channel.
try {
$serialized = serialize($data);
$serialized = \serialize($data);
} catch (\Throwable $exception) {
throw new SerializationException(
'The given data cannot be sent because it is not serializable.', $exception
"The given data cannot be sent because it is not serializable.", $exception
);
}
$length = strlen($serialized);
$length = \strlen($serialized);
try {
yield from $this->write->write(pack('CL', 0, $length) . $serialized);
yield $this->write->write(\pack("CL", 0, $length) . $serialized);
} catch (\Throwable $exception) {
throw new ChannelException('Sending on the channel failed. Did the context die?', $exception);
throw new ChannelException("Sending on the channel failed. Did the context die?", $exception);
}
return $length;
}
/**
* {@inheritdoc}
*/
public function receive(): \Generator
{
// Read the message length first to determine how much needs to be read from the stream.
$length = self::HEADER_LENGTH;
$buffer = '';
$remaining = $length;
public function receive(): Awaitable {
return new Coroutine($this->doReceive());
}
public function doReceive(): \Generator {
try {
do {
$buffer .= yield from $this->read->read($remaining);
} while ($remaining = $length - strlen($buffer));
// Read the message length first to determine how much needs to be read from the stream.
$buffer = yield $this->read->read(self::HEADER_LENGTH);
$data = \unpack("Cprefix/Llength", $buffer);
$data = unpack('Cprefix/Llength', $buffer);
if (0 !== $data['prefix']) {
throw new ChannelException('Invalid header received.');
if ($data["prefix"] !== 0) {
throw new ChannelException("Invalid header received");
}
$buffer = '';
$remaining = $length = $data['length'];
do {
$buffer .= yield from $this->read->read($remaining);
} while ($remaining = $length - strlen($buffer));
$buffer = yield $this->read->read($data["length"]);
} catch (\Throwable $exception) {
throw new ChannelException('Reading from the channel failed. Did the context die?', $exception);
throw new ChannelException("Reading from the channel failed. Did the context die?", $exception);
}
set_error_handler($this->errorHandler);
\set_error_handler($this->errorHandler);
// Attempt to unserialize the received data.
try {
$data = unserialize($buffer);
$data = \unserialize($buffer);
} catch (\Throwable $exception) {
throw new SerializationException('Exception thrown when unserializing data.', $exception);
throw new SerializationException("Exception thrown when unserializing data", $exception);
} finally {
restore_error_handler();
\restore_error_handler();
}
return $data;

View File

@ -1,8 +1,10 @@
<?php
namespace Icicle\Concurrent\Sync;
use Icicle\Concurrent\Exception\MutexException;
use Icicle\Coroutine;
namespace Amp\Concurrent\Sync;
use Amp\Concurrent\MutexException;
use Amp\{ Coroutine, Pause };
use Interop\Async\Awaitable;
/**
* A cross-platform mutex that uses exclusive files as the lock mechanism.
@ -19,8 +21,7 @@ use Icicle\Coroutine;
*
* @see http://php.net/fopen
*/
class FileMutex implements Mutex
{
class FileMutex implements Mutex {
const LATENCY_TIMEOUT = 0.01; // 10 ms
/**
@ -33,27 +34,35 @@ class FileMutex implements Mutex
*/
public function __construct()
{
$this->fileName = tempnam(sys_get_temp_dir(), 'mutex-') . '.lock';
$this->fileName = \tempnam(\sys_get_temp_dir(), 'mutex-') . '.lock';
}
/**
* {@inheritdoc}
*/
public function acquire(): \Generator
{
public function acquire(): Awaitable {
return new Coroutine($this->doAcquire());
}
/**
* @coroutine
*
* @return \Generator
*/
private function doAcquire(): \Generator {
// Try to create the lock file. If the file already exists, someone else
// has the lock, so set an asynchronous timer and try again.
while (($handle = @fopen($this->fileName, 'x')) === false) {
yield from Coroutine\sleep(self::LATENCY_TIMEOUT);
while (($handle = @\fopen($this->fileName, 'x')) === false) {
yield new Pause(self::LATENCY_TIMEOUT);
}
// Return a lock object that can be used to release the lock on the mutex.
$lock = new Lock(function (Lock $lock) {
$lock = new Lock(function () {
$this->release();
});
fclose($handle);
\fclose($handle);
return $lock;
}
@ -62,9 +71,8 @@ class FileMutex implements Mutex
*
* @throws MutexException If the unlock operation failed.
*/
protected function release()
{
$success = @unlink($this->fileName);
protected function release() {
$success = @\unlink($this->fileName);
if (!$success) {
throw new MutexException('Failed to unlock the mutex file.');

View File

@ -1,10 +1,10 @@
<?php
namespace Icicle\Concurrent\Sync\Internal;
use Icicle\Concurrent\Exception\PanicError;
namespace Amp\Concurrent\Sync\Internal;
class ExitFailure implements ExitStatus
{
use Amp\Concurrent\PanicError;
class ExitFailure implements ExitStatus {
/**
* @var string
*/
@ -25,8 +25,7 @@ class ExitFailure implements ExitStatus
*/
private $trace;
public function __construct(\Throwable $exception)
{
public function __construct(\Throwable $exception) {
$this->type = get_class($exception);
$this->message = $exception->getMessage();
$this->code = $exception->getCode();
@ -36,10 +35,9 @@ class ExitFailure implements ExitStatus
/**
* {@inheritdoc}
*/
public function getResult()
{
public function getResult() {
throw new PanicError(
sprintf(
\sprintf(
'Uncaught exception in execution context of type "%s" with message "%s"',
$this->type,
$this->message

View File

@ -1,12 +1,12 @@
<?php
namespace Icicle\Concurrent\Sync\Internal;
interface ExitStatus
{
namespace Amp\Concurrent\Sync\Internal;
interface ExitStatus {
/**
* @return mixed Return value of the callable given to the execution context.
*
* @throws \Icicle\Concurrent\Exception\PanicError If the context exited with an uncaught exception.
* @throws \Amp\Concurrent\PanicError If the context exited with an uncaught exception.
*/
public function getResult();
}

View File

@ -1,23 +1,21 @@
<?php
namespace Icicle\Concurrent\Sync\Internal;
class ExitSuccess implements ExitStatus
{
namespace Amp\Concurrent\Sync\Internal;
class ExitSuccess implements ExitStatus {
/**
* @var mixed
*/
private $result;
public function __construct($result)
{
public function __construct($result) {
$this->result = $result;
}
/**
* {@inheritdoc}
*/
public function getResult()
{
public function getResult() {
return $this->result;
}
}

View File

@ -1,7 +1,8 @@
<?php
namespace Icicle\Concurrent\Sync;
use Icicle\Concurrent\Exception\LockAlreadyReleasedError;
namespace Amp\Concurrent\Sync;
use Amp\Concurrent\LockAlreadyReleasedError;
/**
* A handle on an acquired lock from a synchronization object.
@ -10,8 +11,7 @@ use Icicle\Concurrent\Exception\LockAlreadyReleasedError;
* semaphore, the lock should reside in the same thread or process until it is
* released.
*/
class Lock
{
class Lock {
/**
* @var callable The function to be called on release.
*/
@ -27,8 +27,7 @@ class Lock
*
* @param callable<Lock> $releaser A function to be called upon release.
*/
public function __construct(callable $releaser)
{
public function __construct(callable $releaser) {
$this->releaser = $releaser;
}
@ -37,8 +36,7 @@ class Lock
*
* @return bool True if the lock has already been released, otherwise false.
*/
public function isReleased(): bool
{
public function isReleased(): bool {
return $this->released;
}
@ -47,8 +45,7 @@ class Lock
*
* @throws LockAlreadyReleasedError If the lock was already released.
*/
public function release()
{
public function release() {
if ($this->released) {
throw new LockAlreadyReleasedError('The lock has already been released!');
}
@ -62,8 +59,7 @@ class Lock
/**
* Releases the lock when there are no more references to it.
*/
public function __destruct()
{
public function __destruct() {
if (!$this->released) {
$this->release();
}

View File

@ -1,5 +1,8 @@
<?php
namespace Icicle\Concurrent\Sync;
namespace Amp\Concurrent\Sync;
use Interop\Async\Awaitable;
/**
* A non-blocking synchronization primitive that can be used for mutual exclusion across contexts.
@ -15,9 +18,8 @@ interface Mutex
*
* Acquires a lock on the mutex.
*
* @return \Generator Resolves with a lock object when the acquire is successful.
*
* @resolve \Icicle\Concurrent\Sync\Lock
* @return \Interop\Async\Awaitable<\Amp\Concurrent\Sync\Lock> Resolves with a lock object when the acquire is
* successful.
*/
public function acquire(): \Generator;
public function acquire(): Awaitable;
}

View File

@ -1,5 +1,6 @@
<?php
namespace Icicle\Concurrent\Sync;
namespace Amp\Concurrent\Sync;
/**
* A parcel object for sharing data across execution contexts.
@ -14,8 +15,7 @@ namespace Icicle\Concurrent\Sync;
* methods to acquire a lock for exclusive access to the parcel first before
* accessing the contained value.
*/
interface Parcel extends Synchronizable
{
interface Parcel extends Synchronizable {
/**
* Unwraps the parcel and returns the value inside the parcel.
*

View File

@ -1,9 +1,10 @@
<?php
namespace Icicle\Concurrent\Sync;
use Icicle\Concurrent\Exception\SemaphoreException;
use Icicle\Exception\UnsupportedError;
use Icicle\Coroutine;
namespace Amp\Concurrent\Sync;
use Amp\Concurrent\SemaphoreException;
use Amp\{ Coroutine, Pause };
use Interop\Async\Awaitable;
/**
* A non-blocking, interprocess POSIX semaphore.
@ -13,8 +14,7 @@ use Icicle\Coroutine;
*
* Not compatible with Windows.
*/
class PosixSemaphore implements Semaphore, \Serializable
{
class PosixSemaphore implements Semaphore, \Serializable {
const LATENCY_TIMEOUT = 0.01; // 10 ms
/**
@ -40,10 +40,9 @@ class PosixSemaphore implements Semaphore, \Serializable
*
* @throws SemaphoreException If the semaphore could not be created due to an internal error.
*/
public function __construct($maxLocks, $permissions = 0600)
{
if (!extension_loaded("sysvmsg")) {
throw new UnsupportedError(__CLASS__ . " requires the sysvmsg extension.");
public function __construct($maxLocks, $permissions = 0600) {
if (!\extension_loaded("sysvmsg")) {
throw new \Error(__CLASS__ . " requires the sysvmsg extension.");
}
$this->init($maxLocks, $permissions);
@ -55,17 +54,16 @@ class PosixSemaphore implements Semaphore, \Serializable
*
* @throws SemaphoreException If the semaphore could not be created due to an internal error.
*/
private function init($maxLocks, $permissions)
{
private function init($maxLocks, $permissions) {
$maxLocks = (int) $maxLocks;
if ($maxLocks < 1) {
$maxLocks = 1;
}
$this->key = abs(crc32(spl_object_hash($this)));
$this->key = \abs(\crc32(\spl_object_hash($this)));
$this->maxLocks = $maxLocks;
$this->queue = msg_get_queue($this->key, $permissions);
$this->queue = \msg_get_queue($this->key, $permissions);
if (!$this->queue) {
throw new SemaphoreException('Failed to create the semaphore.');
}
@ -81,9 +79,8 @@ class PosixSemaphore implements Semaphore, \Serializable
*
* @return bool True if the semaphore has been freed, otherwise false.
*/
public function isFreed(): bool
{
return !is_resource($this->queue) || !msg_queue_exists($this->key);
public function isFreed(): bool {
return !\is_resource($this->queue) || !\msg_queue_exists($this->key);
}
/**
@ -91,8 +88,7 @@ class PosixSemaphore implements Semaphore, \Serializable
*
* @return int The maximum number of locks held by the semaphore.
*/
public function getSize(): int
{
public function getSize(): int {
return $this->maxLocks;
}
@ -101,9 +97,8 @@ class PosixSemaphore implements Semaphore, \Serializable
*
* @return int A permissions mode.
*/
public function getPermissions(): int
{
$stat = msg_stat_queue($this->queue);
public function getPermissions(): int {
$stat = \msg_stat_queue($this->queue);
return $stat['msg_perm.mode'];
}
@ -116,9 +111,8 @@ class PosixSemaphore implements Semaphore, \Serializable
*
* @throws SemaphoreException If the operation failed.
*/
public function setPermissions(int $mode)
{
if (!msg_set_queue($this->queue, [
public function setPermissions(int $mode) {
if (!\msg_set_queue($this->queue, [
'msg_perm.mode' => $mode
])) {
throw new SemaphoreException('Failed to change the semaphore permissions.');
@ -128,20 +122,22 @@ class PosixSemaphore implements Semaphore, \Serializable
/**
* {@inheritdoc}
*/
public function count(): int
{
$stat = msg_stat_queue($this->queue);
public function count(): int {
$stat = \msg_stat_queue($this->queue);
return $stat['msg_qnum'];
}
public function acquire(): Awaitable {
return new Coroutine($this->doAcquire());
}
/**
* {@inheritdoc}
*/
public function acquire(): \Generator
{
private function doAcquire(): \Generator {
do {
// Attempt to acquire a lock from the semaphore.
if (@msg_receive($this->queue, 0, $type, 1, $chr, false, MSG_IPC_NOWAIT, $errno)) {
if (@\msg_receive($this->queue, 0, $type, 1, $chr, false, MSG_IPC_NOWAIT, $errno)) {
// A free lock was found, so resolve with a lock object that can
// be used to release the lock.
return new Lock(function (Lock $lock) {
@ -153,7 +149,7 @@ class PosixSemaphore implements Semaphore, \Serializable
if ($errno !== MSG_ENOMSG) {
throw new SemaphoreException('Failed to acquire a lock.');
}
} while (yield from Coroutine\sleep(self::LATENCY_TIMEOUT));
} while (yield new Pause(self::LATENCY_TIMEOUT));
}
/**
@ -161,10 +157,9 @@ class PosixSemaphore implements Semaphore, \Serializable
*
* @throws SemaphoreException If the operation failed.
*/
public function free()
{
if (is_resource($this->queue) && msg_queue_exists($this->key)) {
if (!msg_remove_queue($this->queue)) {
public function free() {
if (\is_resource($this->queue) && \msg_queue_exists($this->key)) {
if (!\msg_remove_queue($this->queue)) {
throw new SemaphoreException('Failed to free the semaphore.');
}
@ -177,9 +172,8 @@ class PosixSemaphore implements Semaphore, \Serializable
*
* @return string The serialized semaphore.
*/
public function serialize(): string
{
return serialize([$this->key, $this->maxLocks]);
public function serialize(): string {
return \serialize([$this->key, $this->maxLocks]);
}
/**
@ -187,21 +181,19 @@ class PosixSemaphore implements Semaphore, \Serializable
*
* @param string $serialized The serialized semaphore.
*/
public function unserialize($serialized)
{
public function unserialize($serialized) {
// Get the semaphore key and attempt to re-connect to the semaphore in memory.
list($this->key, $this->maxLocks) = unserialize($serialized);
list($this->key, $this->maxLocks) = \unserialize($serialized);
if (msg_queue_exists($this->key)) {
$this->queue = msg_get_queue($this->key);
if (\msg_queue_exists($this->key)) {
$this->queue = \msg_get_queue($this->key);
}
}
/**
* Clones the semaphore, creating a new semaphore with the same size and permissions.
*/
public function __clone()
{
public function __clone() {
$this->init($this->maxLocks, $this->getPermissions());
}
@ -210,11 +202,10 @@ class PosixSemaphore implements Semaphore, \Serializable
*
* @throws SemaphoreException If the operation failed.
*/
protected function release()
{
protected function release() {
// Call send in non-blocking mode. If the call fails because the queue
// is full, then the number of locks configured is too large.
if (!@msg_send($this->queue, 1, "\0", false, false, $errno)) {
if (!@\msg_send($this->queue, 1, "\0", false, false, $errno)) {
if ($errno === MSG_EAGAIN) {
throw new SemaphoreException('The semaphore size is larger than the system allows.');
}

View File

@ -1,5 +1,8 @@
<?php
namespace Icicle\Concurrent\Sync;
namespace Amp\Concurrent\Sync;
use Interop\Async\Awaitable;
/**
* A non-blocking counting semaphore.
@ -8,8 +11,7 @@ namespace Icicle\Concurrent\Sync;
* are atomic. Implementations do not have to guarantee that acquiring a lock
* is first-come, first serve.
*/
interface Semaphore extends \Countable
{
interface Semaphore extends \Countable {
/**
* Gets the number of currently available locks.
*
@ -32,9 +34,8 @@ interface Semaphore extends \Countable
* If there are one or more locks available, this function resolves immediately with a lock and the lock count is
* decreased. If no locks are available, the semaphore waits asynchronously for a lock to become available.
*
* @return \Generator Resolves with a lock object when the acquire is successful.
*
* @resolve \Icicle\Concurrent\Sync\Lock
* @return \Interop\Async\Awaitable<\Amp\Concurrent\Sync\Lock> Resolves with a lock object when the acquire is
* successful.
*/
public function acquire(): \Generator;
public function acquire(): Awaitable;
}

View File

@ -1,8 +1,10 @@
<?php
namespace Icicle\Concurrent\Sync;
use Icicle\Concurrent\Exception\SharedMemoryException;
use Icicle\Exception\UnsupportedError;
namespace Amp\Concurrent\Sync;
use Amp\Concurrent\SharedMemoryException;
use Amp\Coroutine;
use Interop\Async\Awaitable;
/**
* A container object for sharing a value across contexts.
@ -26,8 +28,7 @@ use Icicle\Exception\UnsupportedError;
* @see http://man7.org/linux/man-pages/man2/shmctl.2.html How shared memory works on Linux.
* @see https://msdn.microsoft.com/en-us/library/ms810613.aspx How shared memory works on Windows.
*/
class SharedMemoryParcel implements Parcel, \Serializable
{
class SharedMemoryParcel implements Parcel, \Serializable {
/**
* @var int The byte offset to the start of the object data in memory.
*/
@ -66,10 +67,9 @@ class SharedMemoryParcel implements Parcel, \Serializable
* @param int $permissions The access permissions to set for the object.
* If not specified defaults to 0600.
*/
public function __construct($value, int $size = 16384, int $permissions = 0600)
{
if (!extension_loaded("shmop")) {
throw new UnsupportedError(__CLASS__ . " requires the shmop extension.");
public function __construct($value, int $size = 16384, int $permissions = 0600) {
if (!\extension_loaded("shmop")) {
throw new \Error(__CLASS__ . " requires the shmop extension.");
}
$this->init($value, $size, $permissions);
@ -80,9 +80,8 @@ class SharedMemoryParcel implements Parcel, \Serializable
* @param int $size
* @param int $permissions
*/
private function init($value, int $size = 16384, int $permissions = 0600)
{
$this->key = abs(crc32(spl_object_hash($this)));
private function init($value, int $size = 16384, int $permissions = 0600) {
$this->key = \abs(\crc32(\spl_object_hash($this)));
$this->memOpen($this->key, 'n', $permissions, $size + self::MEM_DATA_OFFSET);
$this->setHeader(self::STATE_ALLOCATED, 0, $permissions);
$this->wrap($value);
@ -98,8 +97,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
*
* @return bool True if the object is freed, otherwise false.
*/
public function isFreed(): bool
{
public function isFreed(): bool {
// If we are no longer connected to the memory segment, check if it has
// been invalidated.
if ($this->handle !== null) {
@ -114,8 +112,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
/**
* {@inheritdoc}
*/
public function unwrap()
{
public function unwrap() {
if ($this->isFreed()) {
throw new SharedMemoryException('The object has already been freed.');
}
@ -129,7 +126,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
// Read the actual value data from memory and unserialize it.
$data = $this->memGet(self::MEM_DATA_OFFSET, $header['size']);
return unserialize($data);
return \unserialize($data);
}
/**
@ -140,14 +137,13 @@ class SharedMemoryParcel implements Parcel, \Serializable
* memory segment on the next read attempt. Once all running processes and
* threads disconnect from the old segment, it will be freed by the OS.
*/
protected function wrap($value)
{
protected function wrap($value) {
if ($this->isFreed()) {
throw new SharedMemoryException('The object has already been freed.');
}
$serialized = serialize($value);
$size = strlen($serialized);
$serialized = \serialize($value);
$size = \strlen($serialized);
$header = $this->getHeader();
/* If we run out of space, we need to allocate a new shared memory
@ -157,12 +153,12 @@ class SharedMemoryParcel implements Parcel, \Serializable
automatically after all other processes notice the change and close
the old handle.
*/
if (shmop_size($this->handle) < $size + self::MEM_DATA_OFFSET) {
$this->key = $this->key < 0xffffffff ? $this->key + 1 : mt_rand(0x10, 0xfffffffe);
if (\shmop_size($this->handle) < $size + self::MEM_DATA_OFFSET) {
$this->key = $this->key < 0xffffffff ? $this->key + 1 : \mt_rand(0x10, 0xfffffffe);
$this->setHeader(self::STATE_MOVED, $this->key, 0);
$this->memDelete();
shmop_close($this->handle);
\shmop_close($this->handle);
$this->memOpen($this->key, 'n', $header['permissions'], $size * 2);
}
@ -175,22 +171,42 @@ class SharedMemoryParcel implements Parcel, \Serializable
/**
* {@inheritdoc}
*/
public function synchronized(callable $callback): \Generator
{
/** @var \Icicle\Concurrent\Sync\Lock $lock */
$lock = yield from $this->semaphore->acquire();
public function synchronized(callable $callback): Awaitable {
return new Coroutine($this->doSynchronized($callback));
}
/**
* @coroutine
*
* @param callable $callback
*
* @return \Generator
*/
private function doSynchronized(callable $callback): \Generator {
/** @var \Amp\Concurrent\Sync\Lock $lock */
$lock = yield $this->semaphore->acquire();
try {
$value = $this->unwrap();
$result = yield $callback($value);
$result = $callback($value);
if ($result instanceof \Generator) {
$result = new Coroutine($result);
}
if ($result instanceof Awaitable) {
yield $result;
}
$this->wrap(null === $result ? $value : $result);
} finally {
$lock->release();
}
return $result;
}
/**
* Frees the shared object from memory.
*
@ -208,7 +224,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
// Request the block to be deleted, then close our local handle.
$this->memDelete();
shmop_close($this->handle);
\shmop_close($this->handle);
$this->handle = null;
$this->semaphore->free();
@ -225,7 +241,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
*/
public function serialize(): string
{
return serialize([$this->key, $this->semaphore]);
return \serialize([$this->key, $this->semaphore]);
}
/**
@ -235,7 +251,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
*/
public function unserialize($serialized)
{
list($this->key, $this->semaphore) = unserialize($serialized);
list($this->key, $this->semaphore) = \unserialize($serialized);
$this->memOpen($this->key, 'w', 0, 0);
}
@ -288,7 +304,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
break;
}
shmop_close($this->handle);
\shmop_close($this->handle);
$this->key = $header['size'];
$this->memOpen($this->key, 'w', 0, 0);
}
@ -302,7 +318,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
private function getHeader(): array
{
$data = $this->memGet(0, self::MEM_DATA_OFFSET);
return unpack('Cstate/Lsize/Spermissions', $data);
return \unpack('Cstate/Lsize/Spermissions', $data);
}
/**
@ -314,7 +330,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
*/
private function setHeader(int $state, int $size, int $permissions)
{
$header = pack('CLS', $state, $size, $permissions);
$header = \pack('CLS', $state, $size, $permissions);
$this->memSet(0, $header);
}
@ -328,7 +344,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
*/
private function memOpen(int $key, string $mode, int $permissions, int $size)
{
$this->handle = @shmop_open($key, $mode, $permissions, $size);
$this->handle = @\shmop_open($key, $mode, $permissions, $size);
if ($this->handle === false) {
throw new SharedMemoryException('Failed to create shared memory block.');
}
@ -344,7 +360,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
*/
private function memGet(int $offset, int $size): string
{
$data = shmop_read($this->handle, $offset, $size);
$data = \shmop_read($this->handle, $offset, $size);
if ($data === false) {
throw new SharedMemoryException('Failed to read from shared memory block.');
}
@ -359,7 +375,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
*/
private function memSet(int $offset, string $data)
{
if (!shmop_write($this->handle, $data, $offset)) {
if (!\shmop_write($this->handle, $data, $offset)) {
throw new SharedMemoryException('Failed to write to shared memory block.');
}
}
@ -369,7 +385,7 @@ class SharedMemoryParcel implements Parcel, \Serializable
*/
private function memDelete()
{
if (!shmop_delete($this->handle)) {
if (!\shmop_delete($this->handle)) {
throw new SharedMemoryException('Failed to discard shared memory block.');
}
}

View File

@ -1,5 +1,8 @@
<?php
namespace Icicle\Concurrent\Sync;
namespace Amp\Concurrent\Sync;
use Interop\Async\Awaitable;
/**
* An object that can be synchronized for exclusive access across contexts.
@ -7,8 +10,6 @@ namespace Icicle\Concurrent\Sync;
interface Synchronizable
{
/**
* @coroutine
*
* Asynchronously invokes a callback while maintaining an exclusive lock on the object.
*
* The arguments passed to the callback depend on the implementing object. If the callback throws an exception,
@ -17,9 +18,8 @@ interface Synchronizable
* @param callable<(mixed ...$args): \Generator|mixed> $callback The synchronized callback to invoke.
* The callback may be a regular function or a coroutine.
*
* @return \Generator
*
* @resolve mixed The return value of $callback.
* @return \Interop\Async\Awaitable<mixed> Resolves with the return value of $callback or fails if $callback
* throws an exception.
*/
public function synchronized(callable $callback): \Generator;
public function synchronized(callable $callback): Awaitable;
}

View File

@ -0,0 +1,5 @@
<?php
namespace Amp\Concurrent;
class SynchronizationError extends \Error {}

View File

@ -1,8 +1,8 @@
<?php
namespace Icicle\Concurrent\Exception;
class TaskException extends \Exception implements Exception
{
namespace Amp\Concurrent;
class TaskException extends \Exception {
/**
* @var string Stack trace of the panic.
*/
@ -15,8 +15,7 @@ class TaskException extends \Exception implements Exception
* @param int $code The panic code.
* @param string $trace The panic stack trace.
*/
public function __construct(string $message = '', int $code = 0, string $trace = '')
{
public function __construct(string $message = '', int $code = 0, string $trace = '') {
parent::__construct($message, $code);
$this->trace = $trace;
}
@ -26,8 +25,7 @@ class TaskException extends \Exception implements Exception
*
* @return string
*/
public function getWorkerTrace(): string
{
public function getWorkerTrace(): string {
return $this->trace;
}
}

View File

@ -1,34 +1,42 @@
<?php
namespace Icicle\Concurrent\Threading\Internal;
use Icicle\Concurrent\Sync\Lock;
use Icicle\Coroutine;
namespace Amp\Concurrent\Threading\Internal;
use Amp\Concurrent\Sync\Lock;
use Amp\Coroutine;
use Amp\Pause;
use Interop\Async\Awaitable;
/**
* @internal
*/
class Mutex extends \Threaded
{
const LATENCY_TIMEOUT = 0.01; // 10 ms
class Mutex extends \Threaded {
const LATENCY_TIMEOUT = 10;
/**
* @var bool
*/
private $lock = true;
/**
* @return \Interop\Async\Awaitable
*/
public function acquire(): Awaitable {
return new Coroutine($this->doAcquire());
}
/**
* Attempts to acquire the lock and sleeps for a time if the lock could not be acquired.
*
* @return \Generator
*/
public function acquire(): \Generator
{
public function doAcquire(): \Generator {
$tsl = function () {
return ($this->lock ? $this->lock = false : true);
};
while (!$this->lock || $this->synchronized($tsl)) {
yield from Coroutine\sleep(self::LATENCY_TIMEOUT);
yield new Pause(self::LATENCY_TIMEOUT);
}
return new Lock(function () {
@ -39,8 +47,7 @@ class Mutex extends \Threaded
/**
* Releases the lock.
*/
protected function release()
{
protected function release() {
$this->lock = true;
}
}

View File

@ -1,17 +1,19 @@
<?php
namespace Icicle\Concurrent\Threading\Internal;
use Icicle\Concurrent\Sync\Lock;
use Icicle\Coroutine;
namespace Amp\Concurrent\Threading\Internal;
use Amp\Concurrent\Sync\Lock;
use Amp\Coroutine;
use Amp\Pause;
use Interop\Async\Awaitable;
/**
* An asynchronous semaphore based on pthreads' synchronization methods.
*
* @internal
*/
class Semaphore extends \Threaded
{
const LATENCY_TIMEOUT = 0.01; // 10 ms
class Semaphore extends \Threaded {
const LATENCY_TIMEOUT = 10;
/**
* @var int The number of available locks.
@ -23,8 +25,7 @@ class Semaphore extends \Threaded
*
* @param int $locks The maximum number of locks that can be acquired from the semaphore.
*/
public function __construct(int $locks)
{
public function __construct(int $locks) {
$this->locks = $locks;
}
@ -33,11 +34,17 @@ class Semaphore extends \Threaded
*
* @return int The number of available locks.
*/
public function count(): int
{
public function count(): int {
return $this->locks;
}
/**
* @return \Interop\Async\Awaitable
*/
public function acquire(): Awaitable {
return new Coroutine($this->doAcquire());
}
/**
* Uses a double locking mechanism to acquire a lock without blocking. A
* synchronous mutex is used to make sure that the semaphore is queried one
@ -47,8 +54,7 @@ class Semaphore extends \Threaded
* If a lock is not available, we add the request to a queue and set a timer
* to check again in the future.
*/
public function acquire(): \Generator
{
private function doAcquire(): \Generator {
$tsl = function () {
// If there are no locks available or the wait queue is not empty,
// we need to wait our turn to acquire a lock.
@ -60,7 +66,7 @@ class Semaphore extends \Threaded
};
while ($this->locks < 1 || $this->synchronized($tsl)) {
yield from Coroutine\sleep(self::LATENCY_TIMEOUT);
yield new Pause(self::LATENCY_TIMEOUT);
}
return new Lock(function () {
@ -71,8 +77,7 @@ class Semaphore extends \Threaded
/**
* Releases a lock from the semaphore.
*/
protected function release()
{
protected function release() {
$this->synchronized(function () {
++$this->locks;
});

View File

@ -1,11 +1,11 @@
<?php
namespace Icicle\Concurrent\Threading\Internal;
namespace Amp\Concurrent\Threading\Internal;
/**
* @internal
*/
class Storage extends \Threaded
{
class Storage extends \Threaded {
/**
* @var mixed
*/
@ -14,24 +14,21 @@ class Storage extends \Threaded
/**
* @param mixed $value
*/
public function __construct($value)
{
public function __construct($value) {
$this->value = $value;
}
/**
* @return mixed
*/
public function get()
{
public function get() {
return $this->value;
}
/**
* @param mixed $value
*/
public function set($value)
{
public function set($value) {
$this->value = $value;
}
}

View File

@ -1,20 +1,20 @@
<?php
namespace Icicle\Concurrent\Threading\Internal;
use Icicle\Concurrent\Exception\{ChannelException, SerializationException};
use Icicle\Concurrent\Sync\{Channel, ChannelledStream, Internal\ExitFailure, Internal\ExitSuccess};
use Icicle\Coroutine\Coroutine;
use Icicle\Loop;
use Icicle\Stream\Pipe\DuplexPipe;
namespace Amp\Concurrent\Threading\Internal;
use Amp\Concurrent\{ChannelException, SerializationException};
use Amp\Concurrent\Sync\{Channel, ChannelledStream, Internal\ExitFailure, Internal\ExitSuccess};
use Amp\Coroutine;
use Amp\Socket\Socket;
use Interop\Async\Awaitable;
/**
* An internal thread that executes a given function concurrently.
*
* @internal
*/
class Thread extends \Thread
{
const KILL_CHECK_FREQUENCY = 0.25;
class Thread extends \Thread {
const KILL_CHECK_FREQUENCY = 250;
/**
* @var callable The function to execute in the thread.
@ -43,8 +43,7 @@ class Thread extends \Thread
* @param callable $function The function to execute in the thread.
* @param mixed[] $args Arguments to pass to the function.
*/
public function __construct($socket, callable $function, array $args = [])
{
public function __construct($socket, callable $function, array $args = []) {
$this->function = $function;
$this->args = $args;
$this->socket = $socket;
@ -55,66 +54,77 @@ class Thread extends \Thread
*
* @codeCoverageIgnore Only executed in thread.
*/
public function run()
{
public function run() {
/* First thing we need to do is re-initialize the class autoloader. If
* we don't do this first, any object of a class that was loaded after
* the thread started will just be garbage data and unserializable
* values (like resources) will be lost. This happens even with
* thread-safe objects.
*/
foreach (get_declared_classes() as $className) {
if (strpos($className, 'ComposerAutoloaderInit') === 0) {
// Calling getLoader() will register the class loader for us
$className::getLoader();
$paths = [
\dirname(__DIR__, 5) . \DIRECTORY_SEPARATOR . 'autoload.php',
\dirname(__DIR__, 3) . \DIRECTORY_SEPARATOR . 'vendor' . \DIRECTORY_SEPARATOR . 'autoload.php',
];
$autoloadPath = null;
foreach ($paths as $path) {
if (\file_exists($path)) {
$autoloadPath = $path;
break;
}
}
Loop\loop($loop = Loop\create(false)); // Disable signals in thread.
if (null === $autoloadPath) {
echo 'Could not locate autoload.php.';
exit(1);
}
require $autoloadPath;
// At this point, the thread environment has been prepared so begin using the thread.
try {
$channel = new ChannelledStream(new DuplexPipe($this->socket, false));
\Amp\execute(function () {
try {
$channel = new ChannelledStream(new Socket($this->socket, false));
} catch (\Throwable $exception) {
return 1; // Parent has destroyed Thread object, so just exit.
}
$watcher = \Amp\repeat(self::KILL_CHECK_FREQUENCY, function () {
if ($this->killed) {
\Amp\stop();
}
});
\Amp\unreference($watcher);
return new Coroutine($this->execute($channel));
});
} catch (\Throwable $exception) {
return; // Parent has destroyed Thread object, so just exit.
return 1;
}
$coroutine = new Coroutine($this->execute($channel));
$coroutine->done();
$timer = $loop->timer(self::KILL_CHECK_FREQUENCY, true, function () use ($loop) {
if ($this->killed) {
$loop->stop();
}
});
$timer->unreference();
$loop->run();
return 0;
}
/**
* Sets a local variable to true so the running event loop can check for a kill signal.
*/
public function kill()
{
public function kill() {
return $this->killed = true;
}
/**
* @coroutine
*
* @param \Icicle\Concurrent\Sync\Channel $channel
* @param \Amp\Concurrent\Sync\Channel $channel
*
* @return \Generator
*
* @resolve int
*
* @codeCoverageIgnore Only executed in thread.
*/
private function execute(Channel $channel): \Generator
{
private function execute(Channel $channel): \Generator {
try {
if ($this->function instanceof \Closure) {
$function = $this->function->bindTo($channel, Channel::class);
@ -123,8 +133,18 @@ class Thread extends \Thread
if (empty($function)) {
$function = $this->function;
}
$result = $function(...$this->args);
if ($result instanceof \Generator) {
$result = new Coroutine($result);
}
if ($result instanceof Awaitable) {
$result = yield $result;
}
$result = new ExitSuccess(yield $function(...$this->args));
$result = new ExitSuccess($result);
} catch (\Throwable $exception) {
$result = new ExitFailure($exception);
}
@ -132,10 +152,10 @@ class Thread extends \Thread
// Attempt to return the result.
try {
try {
return yield from $channel->send($result);
return yield $channel->send($result);
} catch (SerializationException $exception) {
// Serializing the result failed. Send the reason why.
return yield from $channel->send(new ExitFailure($exception));
return yield $channel->send(new ExitFailure($exception));
}
} catch (ChannelException $exception) {
// The result was not sendable! The parent context must have died or killed the context.

View File

@ -1,49 +1,46 @@
<?php
namespace Icicle\Concurrent\Threading;
use Icicle\Concurrent\Sync\Mutex as SyncMutex;
namespace Amp\Concurrent\Threading;
use Amp\Concurrent\Sync\Mutex as SyncMutex;
use Interop\Async\Awaitable;
/**
* A thread-safe, asynchronous mutex using the pthreads locking mechanism.
*
* Compatible with POSIX systems and Microsoft Windows.
*/
class Mutex implements SyncMutex
{
class Mutex implements SyncMutex {
/**
* @var \Icicle\Concurrent\Threading\Internal\Mutex
* @var \Amp\Concurrent\Threading\Internal\Mutex
*/
private $mutex;
/**
* Creates a new threaded mutex.
*/
public function __construct()
{
public function __construct() {
$this->init();
}
/**
* Initializes the mutex.
*/
private function init()
{
private function init() {
$this->mutex = new Internal\Mutex();
}
/**
* {@inheritdoc}
*/
public function acquire(): \Generator
{
public function acquire(): Awaitable {
return $this->mutex->acquire();
}
/**
* Makes a copy of the mutex in the unlocked state.
*/
public function __clone()
{
public function __clone() {
$this->init();
}
}

View File

@ -1,20 +1,22 @@
<?php
namespace Icicle\Concurrent\Threading;
use Icicle\Concurrent\Sync\Parcel as SyncParcel;
namespace Amp\Concurrent\Threading;
use Amp\Concurrent\Sync\Parcel as SyncParcel;
use Amp\Coroutine;
use Interop\Async\Awaitable;
/**
* A thread-safe container that shares a value between multiple threads.
*/
class Parcel implements SyncParcel
{
class Parcel implements SyncParcel {
/**
* @var \Icicle\Concurrent\Threading\Mutex
* @var \Amp\Concurrent\Threading\Mutex
*/
private $mutex;
/**
* @var \Icicle\Concurrent\Threading\Internal\Storage
* @var \Amp\Concurrent\Threading\Internal\Storage
*/
private $storage;
@ -40,19 +42,24 @@ class Parcel implements SyncParcel
/**
* {@inheritdoc}
*/
public function unwrap()
{
public function unwrap() {
return $this->storage->get();
}
/**
* {@inheritdoc}
*/
protected function wrap($value)
{
protected function wrap($value) {
$this->storage->set($value);
}
/**
* @return \Interop\Async\Awaitable
*/
public function synchronized(callable $callback): Awaitable {
return new Coroutine($this->doSynchronized($callback));
}
/**
* @coroutine
*
@ -63,14 +70,22 @@ class Parcel implements SyncParcel
*
* @return \Generator
*/
public function synchronized(callable $callback): \Generator
{
/** @var \Icicle\Concurrent\Sync\Lock $lock */
$lock = yield from $this->mutex->acquire();
private function doSynchronized(callable $callback): \Generator {
/** @var \Amp\Concurrent\Sync\Lock $lock */
$lock = yield $this->mutex->acquire();
try {
$value = $this->unwrap();
$result = yield $callback($value);
$result = $callback($value);
if ($result instanceof \Generator) {
$result = new Coroutine($result);
}
if ($result instanceof Awaitable) {
yield $result;
}
$this->wrap(null === $result ? $value : $result);
} finally {
$lock->release();
@ -82,8 +97,7 @@ class Parcel implements SyncParcel
/**
* {@inheritdoc}
*/
public function __clone()
{
public function __clone() {
$this->init($this->unwrap());
}
}

View File

@ -1,7 +1,8 @@
<?php
namespace Icicle\Concurrent\Threading;
use Icicle\Concurrent\Sync\Semaphore as SyncSemaphore;
namespace Amp\Concurrent\Threading;
use Amp\Concurrent\Sync\Semaphore as SyncSemaphore;
/**
* An asynchronous semaphore based on pthreads' synchronization methods.
@ -11,8 +12,7 @@ use Icicle\Concurrent\Sync\Semaphore as SyncSemaphore;
* may not acquire a lock immediately when one is available and there may be a
* small delay. However, the small delay will not block the thread.
*/
class Semaphore implements SyncSemaphore
{
class Semaphore implements SyncSemaphore {
/**
* @var Internal\Semaphore
*/

View File

@ -1,13 +1,12 @@
<?php
namespace Icicle\Concurrent\Threading;
use Icicle\Concurrent\Exception\{StatusError, SynchronizationError, ThreadException};
use Icicle\Concurrent\Strand;
use Icicle\Concurrent\Sync\{ChannelledStream, Internal\ExitStatus};
use Icicle\Coroutine;
use Icicle\Exception\{InvalidArgumentError, UnsupportedError};
use Icicle\Stream;
use Icicle\Stream\Pipe\DuplexPipe;
namespace Amp\Concurrent\Threading;
use Amp\Concurrent\{ContextException, StatusError, SynchronizationError, Strand};
use Amp\Concurrent\Sync\{ChannelledStream, Internal\ExitStatus};
use Amp\Coroutine;
use Amp\Socket\Socket;
use Interop\Async\Awaitable;
/**
* Implements an execution context using native multi-threading.
@ -16,20 +15,19 @@ use Icicle\Stream\Pipe\DuplexPipe;
* maintained both in the context that creates the thread and in the thread
* itself.
*/
class Thread implements Strand
{
class Thread implements Strand {
/**
* @var Internal\Thread An internal thread instance.
*/
private $thread;
/**
* @var \Icicle\Concurrent\Sync\Channel A channel for communicating with the thread.
* @var \Amp\Concurrent\Sync\Channel A channel for communicating with the thread.
*/
private $channel;
/**
* @var \Icicle\Stream\Pipe\DuplexPipe
* @var \Amp\Socket\Socket
*/
private $pipe;
@ -58,9 +56,8 @@ class Thread implements Strand
*
* @return bool True if threading is enabled, otherwise false.
*/
public static function enabled(): bool
{
return extension_loaded('pthreads');
public static function supported(): bool {
return \extension_loaded('pthreads');
}
/**
@ -70,8 +67,7 @@ class Thread implements Strand
*
* @return Thread The thread object that was spawned.
*/
public static function spawn(callable $function, ...$args)
{
public static function spawn(callable $function, ...$args) {
$thread = new self($function, ...$args);
$thread->start();
return $thread;
@ -82,13 +78,11 @@ class Thread implements Strand
*
* @param callable $function The callable to invoke in the thread when run.
*
* @throws InvalidArgumentError If the given function cannot be safely invoked in a thread.
* @throws UnsupportedError Thrown if the pthreads extension is not available.
* @throws \Error Thrown if the pthreads extension is not available.
*/
public function __construct(callable $function, ...$args)
{
if (!self::enabled()) {
throw new UnsupportedError("The pthreads extension is required to create threads.");
public function __construct(callable $function, ...$args) {
if (!self::supported()) {
throw new \Error("The pthreads extension is required to create threads.");
}
$this->function = $function;
@ -99,8 +93,7 @@ class Thread implements Strand
* Returns the thread to the condition before starting. The new thread can be started and run independently of the
* first thread.
*/
public function __clone()
{
public function __clone() {
$this->thread = null;
$this->socket = null;
$this->pipe = null;
@ -111,11 +104,10 @@ class Thread implements Strand
/**
* Kills the thread if it is still running.
*
* @throws \Icicle\Concurrent\Exception\ThreadException
* @throws \Amp\Concurrent\ContextException
*/
public function __destruct()
{
if (getmypid() === $this->oid) {
public function __destruct() {
if (\getmypid() === $this->oid) {
$this->kill();
}
}
@ -125,48 +117,44 @@ class Thread implements Strand
*
* @return bool True if the context is running, otherwise false.
*/
public function isRunning(): bool
{
return null !== $this->pipe && $this->pipe->isOpen();
public function isRunning(): bool {
return null !== $this->pipe && $this->pipe->isReadable();
}
/**
* Spawns the thread and begins the thread's execution.
*
* @throws \Icicle\Concurrent\Exception\StatusError If the thread has already been started.
* @throws \Icicle\Concurrent\Exception\ThreadException If starting the thread was unsuccessful.
* @throws \Icicle\Stream\Exception\FailureException If creating a socket pair fails.
* @throws \Amp\Concurrent\StatusError If the thread has already been started.
* @throws \Amp\Concurrent\ContextException If starting the thread was unsuccessful.
*/
public function start()
{
public function start() {
if (0 !== $this->oid) {
throw new StatusError('The thread has already been started.');
}
$this->oid = getmypid();
$this->oid = \getmypid();
list($channel, $this->socket) = Stream\pair();
list($channel, $this->socket) = \Amp\Socket\pair();
$this->thread = new Internal\Thread($this->socket, $this->function, $this->args);
if (!$this->thread->start(PTHREADS_INHERIT_INI | PTHREADS_INHERIT_FUNCTIONS | PTHREADS_INHERIT_CLASSES)) {
throw new ThreadException('Failed to start the thread.');
if (!$this->thread->start(PTHREADS_INHERIT_INI)) {
throw new ContextException('Failed to start the thread.');
}
$this->channel = new ChannelledStream($this->pipe = new DuplexPipe($channel));
$this->channel = new ChannelledStream($this->pipe = new Socket($channel));
}
/**
* Immediately kills the context.
*
* @throws ThreadException If killing the thread was unsuccessful.
* @throws ContextException If killing the thread was unsuccessful.
*/
public function kill()
{
public function kill() {
if (null !== $this->thread) {
try {
if ($this->thread->isRunning() && !$this->thread->kill()) {
throw new ThreadException('Could not kill thread.');
throw new ContextException('Could not kill thread.');
}
} finally {
$this->close();
@ -177,14 +165,13 @@ class Thread implements Strand
/**
* Closes channel and socket if still open.
*/
private function close()
{
if (null !== $this->pipe && $this->pipe->isOpen()) {
private function close() {
if (null !== $this->pipe && $this->pipe->isReadable()) {
$this->pipe->close();
}
if (is_resource($this->socket)) {
fclose($this->socket);
if (\is_resource($this->socket)) {
@\fclose($this->socket);
}
$this->thread = null;
@ -192,26 +179,32 @@ class Thread implements Strand
}
/**
* @coroutine
*
* Gets a promise that resolves when the context ends and joins with the
* parent context.
*
* @return \Generator
*
* @resolve mixed Resolved with the return or resolution value of the context once it has completed execution.
* @return \Interop\Async\Awaitable<mixed>
*
* @throws StatusError Thrown if the context has not been started.
* @throws SynchronizationError Thrown if an exit status object is not received.
*/
public function join(): \Generator
{
public function join(): Awaitable {
if (null === $this->channel || null === $this->thread) {
throw new StatusError('The thread has not been started or has already finished.');
}
return new Coroutine($this->doJoin());
}
/**
* @coroutine
*
* @return \Generator
*
* @throws \Amp\Concurrent\SynchronizationError If the thread does not send an exit status.
*/
private function doJoin(): \Generator {
try {
$response = yield from $this->channel->receive();
$response = yield $this->channel->receive();
if (!$response instanceof ExitStatus) {
throw new SynchronizationError('Did not receive an exit status from thread.');
@ -233,40 +226,37 @@ class Thread implements Strand
/**
* {@inheritdoc}
*/
public function receive(): \Generator
{
public function receive(): Awaitable {
if (null === $this->channel) {
throw new StatusError('The thread has not been started or has already finished.');
throw new StatusError('The process has not been started.');
}
$data = yield from $this->channel->receive();
if ($data instanceof ExitStatus) {
$this->kill();
$data = $data->getResult();
throw new SynchronizationError(sprintf(
'Thread unexpectedly exited with result of type: %s',
is_object($data) ? get_class($data) : gettype($data)
));
}
return $data;
return \Amp\pipe($this->channel->receive(), static function ($data) {
if ($data instanceof ExitStatus) {
$data = $data->getResult();
throw new SynchronizationError(\sprintf(
'Thread unexpectedly exited with result of type: %s',
\is_object($data) ? \get_class($data) : \gettype($data)
));
}
return $data;
});
}
/**
* {@inheritdoc}
*/
public function send($data): \Generator
{
public function send($data): Awaitable {
if (null === $this->channel) {
throw new StatusError('The thread has not been started or has already finished.');
}
if ($data instanceof ExitStatus) {
$this->kill();
throw new InvalidArgumentError('Cannot send exit status objects.');
throw new \Error('Cannot send exit status objects.');
}
return yield from $this->channel->send($data);
return $this->channel->send($data);
}
}

View File

@ -1,19 +1,19 @@
<?php
namespace Icicle\Concurrent\Worker;
use Icicle\Awaitable\Delayed;
use Icicle\Concurrent\Strand;
use Icicle\Concurrent\Exception\{StatusError, WorkerException};
use Icicle\Concurrent\Worker\Internal\TaskFailure;
use Icicle\Coroutine\Coroutine;
namespace Amp\Concurrent\Worker;
use Amp\Concurrent\{ StatusError, Strand, WorkerException} ;
use Amp\Concurrent\Worker\Internal\TaskFailure;
use Amp\Coroutine;
use Amp\Deferred;
use Interop\Async\Awaitable;
/**
* Base class for most common types of task workers.
*/
abstract class AbstractWorker implements Worker
{
abstract class AbstractWorker implements Worker {
/**
* @var \Icicle\Concurrent\Strand
* @var \Amp\Concurrent\Strand
*/
private $context;
@ -23,7 +23,7 @@ abstract class AbstractWorker implements Worker
private $shutdown = false;
/**
* @var \Icicle\Coroutine\Coroutine
* @var \Amp\Coroutine
*/
private $active;
@ -33,56 +33,65 @@ abstract class AbstractWorker implements Worker
private $busyQueue;
/**
* @param \Icicle\Concurrent\Strand $strand
* @param \Amp\Concurrent\Strand $strand
*/
public function __construct(Strand $strand)
{
public function __construct(Strand $strand) {
$this->context = $strand;
$this->busyQueue = new \SplQueue();
$this->busyQueue = new \SplQueue;
}
/**
* {@inheritdoc}
*/
public function isRunning(): bool
{
public function isRunning(): bool {
return $this->context->isRunning();
}
/**
* {@inheritdoc}
*/
public function isIdle(): bool
{
public function isIdle(): bool {
return null === $this->active;
}
/**
* {@inheritdoc}
*/
public function start()
{
public function start() {
$this->context->start();
}
/**
* {@inheritdoc}
*/
public function enqueue(Task $task): \Generator
{
public function enqueue(Task $task): Awaitable {
if (!$this->context->isRunning()) {
throw new StatusError('The worker has not been started.');
}
if ($this->shutdown) {
throw new StatusError('The worker has been shut down.');
}
return new Coroutine($this->doEnqueue($task));
}
/**
* @coroutine
*
* @param \Amp\Concurrent\Worker\Task $task
*
* @return \Generator
* @throws \Amp\Concurrent\StatusError
* @throws \Amp\Concurrent\TaskException
* @throws \Amp\Concurrent\WorkerException
*/
private function doEnqueue(Task $task): \Generator {
// If the worker is currently busy, store the task in a busy queue.
if (null !== $this->active) {
$delayed = new Delayed();
$this->busyQueue->enqueue($delayed);
yield $delayed;
$deferred = new Deferred;
$this->busyQueue->enqueue($deferred);
yield $deferred->getAwaitable();
}
$this->active = new Coroutine($this->send($task));
@ -111,23 +120,28 @@ abstract class AbstractWorker implements Worker
/**
* @coroutine
*
* @param \Icicle\Concurrent\Worker\Task $task
* @param \Amp\Concurrent\Worker\Task $task
*
* @return \Generator
*
* @resolve mixed
*/
private function send(Task $task): \Generator
{
yield from $this->context->send($task);
return yield from $this->context->receive();
private function send(Task $task): \Generator {
yield $this->context->send($task);
return yield $this->context->receive();
}
/**
* {@inheritdoc}
*/
public function shutdown(): \Generator
{
public function shutdown(): Awaitable {
return new Coroutine($this->doShutdown());
}
/**
* {@inheritdoc}
*/
private function doShutdown(): \Generator {
if (!$this->context->isRunning() || $this->shutdown) {
throw new StatusError('The worker is not running.');
}
@ -146,15 +160,14 @@ abstract class AbstractWorker implements Worker
}
}
yield from $this->context->send(0);
return yield from $this->context->join();
yield $this->context->send(0);
return yield $this->context->join();
}
/**
* {@inheritdoc}
*/
public function kill()
{
public function kill() {
$this->cancelPending();
$this->context->kill();
}
@ -162,13 +175,12 @@ abstract class AbstractWorker implements Worker
/**
* Cancels all pending tasks.
*/
private function cancelPending()
{
private function cancelPending() {
if (!$this->busyQueue->isEmpty()) {
$exception = new WorkerException('Worker was shut down.');
do {
$this->busyQueue->dequeue()->cancel($exception);
$this->busyQueue->dequeue()->fail($exception);
} while (!$this->busyQueue->isEmpty());
}
}

View File

@ -1,10 +1,10 @@
<?php
namespace Icicle\Concurrent\Worker;
use Icicle\Loop;
namespace Amp\Concurrent\Worker;
class BasicEnvironment implements Environment
{
use Interop\Async\Loop;
class BasicEnvironment implements Environment {
/**
* @var array
*/
@ -26,16 +26,15 @@ class BasicEnvironment implements Environment
private $queue;
/**
* @var \Icicle\Loop\Watcher\Timer
* @var string
*/
private $timer;
public function __construct()
{
$this->queue = new \SplPriorityQueue();
public function __construct() {
$this->queue = new \SplPriorityQueue;
$this->timer = Loop\periodic(1, function () {
$time = time();
$this->timer = Loop::repeat(1000, function () {
$time = \time();
while (!$this->queue->isEmpty()) {
$key = $this->queue->top();
@ -51,12 +50,12 @@ class BasicEnvironment implements Environment
}
if ($this->queue->isEmpty()) {
$this->timer->stop();
Loop::disable($this->timer);
}
});
$this->timer->stop();
$this->timer->unreference();
Loop::disable($this->timer);
Loop::unreference($this->timer);
}
/**
@ -64,8 +63,7 @@ class BasicEnvironment implements Environment
*
* @return bool
*/
public function exists(string $key): bool
{
public function exists(string $key): bool {
return isset($this->data[$key]);
}
@ -74,8 +72,7 @@ class BasicEnvironment implements Environment
*
* @return mixed|null Returns null if the key does not exist.
*/
public function get(string $key)
{
public function get(string $key) {
if (isset($this->ttl[$key]) && 0 !== $this->ttl[$key]) {
$this->expire[$key] = time() + $this->ttl[$key];
$this->queue->insert($key, -$this->expire[$key]);
@ -89,8 +86,7 @@ class BasicEnvironment implements Environment
* @param mixed $value Using null for the value deletes the key.
* @param int $ttl Number of seconds until data is automatically deleted. Use 0 for unlimited TTL.
*/
public function set(string $key, $value, int $ttl = 0)
{
public function set(string $key, $value, int $ttl = 0) {
if (null === $value) {
$this->delete($key);
return;
@ -106,9 +102,7 @@ class BasicEnvironment implements Environment
$this->expire[$key] = time() + $ttl;
$this->queue->insert($key, -$this->expire[$key]);
if (!$this->timer->isPending()) {
$this->timer->start();
}
Loop::enable($this->timer);
} else {
unset($this->expire[$key], $this->ttl[$key]);
}
@ -119,8 +113,7 @@ class BasicEnvironment implements Environment
/**
* @param string $key
*/
public function delete(string $key)
{
public function delete(string $key) {
$key = (string) $key;
unset($this->data[$key], $this->expire[$key], $this->ttl[$key]);
}
@ -132,8 +125,7 @@ class BasicEnvironment implements Environment
*
* @return bool
*/
public function offsetExists($key)
{
public function offsetExists($key) {
return $this->exists($key);
}
@ -144,8 +136,7 @@ class BasicEnvironment implements Environment
*
* @return mixed
*/
public function offsetGet($key)
{
public function offsetGet($key) {
return $this->get($key);
}
@ -155,8 +146,7 @@ class BasicEnvironment implements Environment
* @param string $key
* @param mixed $value
*/
public function offsetSet($key, $value)
{
public function offsetSet($key, $value) {
$this->set($key, $value);
}
@ -165,29 +155,26 @@ class BasicEnvironment implements Environment
*
* @param string $key
*/
public function offsetUnset($key)
{
public function offsetUnset($key) {
$this->delete($key);
}
/**
* @return int
*/
public function count(): int
{
public function count(): int {
return count($this->data);
}
/**
* Removes all values.
*/
public function clear()
{
public function clear() {
$this->data = [];
$this->expire = [];
$this->ttl = [];
$this->timer->stop();
Loop::disable($this->timer);
$this->queue = new \SplPriorityQueue();
}
}

View File

@ -1,10 +1,11 @@
<?php
namespace Icicle\Concurrent\Worker;
use Icicle\Awaitable;
use Icicle\Exception\InvalidArgumentError;
use Icicle\Concurrent\Exception\StatusError;
use Icicle\Coroutine\Coroutine;
namespace Amp\Concurrent\Worker;
use Amp;
use Amp\Concurrent\StatusError;
use Amp\Coroutine;
use Interop\Async\Awaitable;
/**
* Provides a pool of workers that can be used to execute multiple tasks asynchronously.
@ -13,8 +14,7 @@ use Icicle\Coroutine\Coroutine;
* tasks simultaneously. The load on each worker is balanced such that tasks
* are completed as soon as possible and workers are used efficiently.
*/
class DefaultPool implements Pool
{
class DefaultPool implements Pool {
/**
* @var bool Indicates if the pool is currently running.
*/
@ -62,22 +62,21 @@ class DefaultPool implements Pool
* Defaults to `Pool::DEFAULT_MIN_SIZE`.
* @param int|null $maxSize The maximum number of workers the pool should spawn.
* Defaults to `Pool::DEFAULT_MAX_SIZE`.
* @param \Icicle\Concurrent\Worker\WorkerFactory|null $factory A worker factory to be used to create
* @param \Amp\Concurrent\Worker\WorkerFactory|null $factory A worker factory to be used to create
* new workers.
*
* @throws \Icicle\Exception\InvalidArgumentError
* @throws \Error
*/
public function __construct(int $minSize = null, int $maxSize = null, WorkerFactory $factory = null)
{
public function __construct(int $minSize = null, int $maxSize = null, WorkerFactory $factory = null) {
$minSize = $minSize ?: self::DEFAULT_MIN_SIZE;
$maxSize = $maxSize ?: self::DEFAULT_MAX_SIZE;
if (!is_int($minSize) || $minSize < 0) {
throw new InvalidArgumentError('Minimum size must be a non-negative integer.');
if ($minSize < 0) {
throw new \Error('Minimum size must be a non-negative integer.');
}
if (!is_int($maxSize) || $maxSize < 0 || $maxSize < $minSize) {
throw new InvalidArgumentError('Maximum size must be a non-negative integer at least '.$minSize.'.');
if ($maxSize < 0 || $maxSize < $minSize) {
throw new \Error('Maximum size must be a non-negative integer at least '.$minSize.'.');
}
$this->maxSize = $maxSize;
@ -86,13 +85,17 @@ class DefaultPool implements Pool
// Use the global factory if none is given.
$this->factory = $factory ?: factory();
$this->workers = new \SplObjectStorage();
$this->idleWorkers = new \SplQueue();
$this->busyQueue = new \SplQueue();
$this->workers = new \SplObjectStorage;
$this->idleWorkers = new \SplQueue;
$this->busyQueue = new \SplQueue;
$this->push = function (Worker $worker) {
$this->push($worker);
};
if (PHP_VERSION_ID >= 70100) {
$this->push = \Closure::fromCallable([$this, 'push']);
} else {
$this->push = function (Worker $worker) {
$this->push($worker);
};
}
}
/**
@ -100,8 +103,7 @@ class DefaultPool implements Pool
*
* @return bool True if the pool is running, otherwise false.
*/
public function isRunning(): bool
{
public function isRunning(): bool {
return $this->running;
}
@ -110,32 +112,28 @@ class DefaultPool implements Pool
*
* @return bool True if the pool has at least one idle worker, otherwise false.
*/
public function isIdle(): bool
{
public function isIdle(): bool {
return $this->idleWorkers->count() > 0;
}
/**
* {@inheritdoc}
*/
public function getMinSize(): int
{
public function getMinSize(): int {
return $this->minSize;
}
/**
* {@inheritdoc}
*/
public function getMaxSize(): int
{
public function getMaxSize(): int {
return $this->maxSize;
}
/**
* {@inheritdoc}
*/
public function getWorkerCount(): int
{
public function getWorkerCount(): int {
return $this->workers->count();
}
@ -153,8 +151,7 @@ class DefaultPool implements Pool
* When the worker pool starts up, the minimum number of workers will be created. This adds some overhead to
* starting the pool, but allows for greater performance during runtime.
*/
public function start()
{
public function start() {
if ($this->isRunning()) {
throw new StatusError('The worker pool has already been started.');
}
@ -176,17 +173,31 @@ class DefaultPool implements Pool
*
* @param Task $task The task to enqueue.
*
* @return \Generator
* @return \Interop\Async\Awaitable<mixed> The return value of Task::run().
*
* @resolve mixed The return value of the task.
*
* @throws \Icicle\Concurrent\Exception\StatusError If the pool has not been started.
* @throws \Icicle\Concurrent\Exception\TaskException If the task throws an exception.
* @throws \Amp\Concurrent\StatusError If the pool has not been started.
* @throws \Amp\Concurrent\TaskException If the task throws an exception.
*/
public function enqueue(Task $task): \Generator
{
public function enqueue(Task $task): Awaitable {
$worker = $this->get();
return yield from $worker->enqueue($task);
return $worker->enqueue($task);
}
/**
* Shuts down the pool and all workers in it.
*
* @coroutine
*
* @return \Interop\Async\Awaitable<int[]> Array of exit status from all workers.
*
* @throws \Amp\Concurrent\StatusError If the pool has not been started.
*/
public function shutdown(): Awaitable {
if (!$this->isRunning()) {
throw new StatusError('The pool is not running.');
}
return new Coroutine($this->doShutdown());
}
/**
@ -196,34 +207,26 @@ class DefaultPool implements Pool
*
* @return \Generator
*
* @throws \Icicle\Concurrent\Exception\StatusError If the pool has not been started.
* @throws \Amp\Concurrent\StatusError If the pool has not been started.
*/
public function shutdown(): \Generator
{
if (!$this->isRunning()) {
throw new StatusError('The pool is not running.');
}
private function doShutdown(): \Generator {
$this->running = false;
$shutdowns = [];
foreach ($this->workers as $worker) {
if ($worker->isRunning()) {
$shutdowns[] = new Coroutine($worker->shutdown());
$shutdowns[] = $worker->shutdown();
}
}
return yield Awaitable\reduce($shutdowns, function ($carry, $value) {
return $carry ?: $value;
}, 0);
return yield Amp\all($shutdowns);
}
/**
* Kills all workers in the pool and halts the worker pool.
*/
public function kill()
{
public function kill() {
$this->running = false;
foreach ($this->workers as $worker) {
@ -236,8 +239,7 @@ class DefaultPool implements Pool
*
* @return Worker The worker created.
*/
private function createWorker()
{
private function createWorker() {
$worker = $this->factory->create();
$worker->start();
@ -248,10 +250,9 @@ class DefaultPool implements Pool
/**
* {@inheritdoc}
*/
public function get(): Worker
{
public function get(): Worker {
if (!$this->isRunning()) {
throw new StatusError('The queue is not running.');
throw new StatusError("The queue is not running");
}
do {
@ -284,16 +285,13 @@ class DefaultPool implements Pool
/**
* Pushes the worker back into the queue.
*
* @param \Icicle\Concurrent\Worker\Worker $worker
* @param \Amp\Concurrent\Worker\Worker $worker
*
* @throws \Icicle\Exception\InvalidArgumentError If the worker was not part of this queue.
* @throws \Error If the worker was not part of this queue.
*/
private function push(Worker $worker)
{
private function push(Worker $worker) {
if (!$this->workers->contains($worker)) {
throw new InvalidArgumentError(
'The provided worker was not part of this queue.'
);
throw new \Error("The provided worker was not part of this queue");
}
if (0 === ($this->workers[$worker] -= 1)) {

View File

@ -1,13 +1,13 @@
<?php
namespace Icicle\Concurrent\Worker;
use Icicle\Concurrent\{Forking\Fork, Threading\Thread};
namespace Amp\Concurrent\Worker;
use Amp\Concurrent\{ Forking\Fork, Threading\Thread };
/**
* The built-in worker factory type.
*/
class DefaultWorkerFactory implements WorkerFactory
{
class DefaultWorkerFactory implements WorkerFactory {
/**
* {@inheritdoc}
*
@ -15,13 +15,12 @@ class DefaultWorkerFactory implements WorkerFactory
* will be created. If threads are not available, a WorkerFork will be created if forking is available, otherwise
* a WorkerProcess will be created.
*/
public function create(): Worker
{
if (Thread::enabled()) {
public function create(): Worker {
if (Thread::supported()) {
return new WorkerThread();
}
if (Fork::enabled()) {
if (Fork::supported()) {
return new WorkerFork();
}

View File

@ -1,10 +1,8 @@
<?php
namespace Icicle\Concurrent\Worker;
use Icicle\Loop;
namespace Amp\Concurrent\Worker;
interface Environment extends \ArrayAccess, \Countable
{
interface Environment extends \ArrayAccess, \Countable {
/**
* @param string $key
*

View File

@ -1,27 +1,26 @@
<?php
namespace Icicle\Concurrent\Worker\Internal;
use Icicle\Concurrent\Worker\Task;
use Icicle\Concurrent\Worker\Worker;
namespace Amp\Concurrent\Worker\Internal;
class PooledWorker implements Worker
{
use Amp\Concurrent\Worker\{ Task, Worker };
use Interop\Async\Awaitable;
class PooledWorker implements Worker {
/**
* @var callable
*/
private $push;
/**
* @var \Icicle\Concurrent\Worker\Worker
* @var \Amp\Concurrent\Worker\Worker
*/
private $worker;
/**
* @param \Icicle\Concurrent\Worker\Worker $worker
* @param \Amp\Concurrent\Worker\Worker $worker
* @param callable $push Callable to push the worker back into the queue.
*/
public function __construct(Worker $worker, callable $push)
{
public function __construct(Worker $worker, callable $push) {
$this->worker = $worker;
$this->push = $push;
}
@ -29,56 +28,49 @@ class PooledWorker implements Worker
/**
* Automatically pushes the worker back into the queue.
*/
public function __destruct()
{
public function __destruct() {
($this->push)($this->worker);
}
/**
* {@inheritdoc}
*/
public function isRunning(): bool
{
public function isRunning(): bool {
return $this->worker->isRunning();
}
/**
* {@inheritdoc}
*/
public function isIdle(): bool
{
public function isIdle(): bool {
return $this->worker->isIdle();
}
/**
* {@inheritdoc}
*/
public function start()
{
public function start() {
$this->worker->start();
}
/**
* {@inheritdoc}
*/
public function enqueue(Task $task): \Generator
{
public function enqueue(Task $task): Awaitable {
return $this->worker->enqueue($task);
}
/**
* {@inheritdoc}
*/
public function shutdown(): \Generator
{
public function shutdown(): Awaitable {
return $this->worker->shutdown();
}
/**
* {@inheritdoc}
*/
public function kill()
{
public function kill() {
$this->worker->kill();
}
}

View File

@ -1,10 +1,10 @@
<?php
namespace Icicle\Concurrent\Worker\Internal;
use Icicle\Concurrent\Exception\TaskException;
namespace Amp\Concurrent\Worker\Internal;
class TaskFailure
{
use Amp\Concurrent\TaskException;
class TaskFailure {
/**
* @var string
*/
@ -25,8 +25,7 @@ class TaskFailure
*/
private $trace;
public function __construct(\Throwable $exception)
{
public function __construct(\Throwable $exception) {
$this->type = get_class($exception);
$this->message = $exception->getMessage();
$this->code = $exception->getCode();
@ -36,8 +35,7 @@ class TaskFailure
/**
* {@inheritdoc}
*/
public function getException()
{
public function getException() {
return new TaskException(
sprintf('Uncaught exception in worker of type "%s" with message "%s"', $this->type, $this->message),
$this->code,

View File

@ -1,56 +1,72 @@
<?php
namespace Icicle\Concurrent\Worker\Internal;
use Icicle\Concurrent\Sync\Channel;
use Icicle\Concurrent\Worker\Environment;
use Icicle\Concurrent\Worker\Task;
namespace Amp\Concurrent\Worker\Internal;
class TaskRunner
{
use Amp\Concurrent\Sync\Channel;
use Amp\Concurrent\Worker\{ Environment, Task };
use Amp\Coroutine;
use Interop\Async\Awaitable;
class TaskRunner {
/**
* @var bool
*/
private $idle = true;
/**
* @var \Icicle\Concurrent\Sync\Channel
* @var \Amp\Concurrent\Sync\Channel
*/
private $channel;
/**
* @var \Icicle\Concurrent\Worker\Environment
* @var \Amp\Concurrent\Worker\Environment
*/
private $environment;
public function __construct(Channel $channel, Environment $environment)
{
public function __construct(Channel $channel, Environment $environment) {
$this->channel = $channel;
$this->environment = $environment;
}
/**
* Runs the task runner, receiving tasks from the parent and sending the result of those tasks.
*
* @return \Interop\Async\Awaitable
*/
public function run(): Awaitable {
return new Coroutine($this->execute());
}
/**
* @coroutine
*
* @return \Generator
*/
public function run(): \Generator
{
$task = yield from $this->channel->receive();
private function execute(): \Generator {
$task = yield $this->channel->receive();
while ($task instanceof Task) {
$this->idle = false;
try {
$result = yield $task->run($this->environment);
$result = $task->run($this->environment);
if ($result instanceof \Generator) {
$result = new Coroutine($result);
}
if ($result instanceof Awaitable) {
$result = yield $result;
}
} catch (\Throwable $exception) {
$result = new TaskFailure($exception);
}
yield from $this->channel->send($result);
yield $this->channel->send($result);
$this->idle = true;
$task = yield from $this->channel->receive();
$task = yield $this->channel->receive();
}
return $task;
@ -59,8 +75,7 @@ class TaskRunner
/**
* @return bool
*/
public function isIdle(): bool
{
public function isIdle(): bool {
return $this->idle;
}
}

View File

@ -1,11 +1,11 @@
<?php
namespace Icicle\Concurrent\Worker;
namespace Amp\Concurrent\Worker;
/**
* An interface for worker pools.
*/
interface Pool extends Worker
{
interface Pool extends Worker {
/**
* @var int The default minimum pool size.
*/
@ -20,9 +20,9 @@ interface Pool extends Worker
* Gets a worker from the pool. The worker is marked as busy and will only be reused if the pool runs out of
* idle workers. The worker will be automatically marked as idle once no references to the returned worker remain.
*
* @return \Icicle\Concurrent\Worker\Worker
* @return \Amp\Concurrent\Worker\Worker
*
* @throws \Icicle\Concurrent\Exception\StatusError If the queue is not running.
* @throws \Amp\Concurrent\StatusError If the queue is not running.
*/
public function get(): Worker;

View File

@ -1,23 +1,19 @@
<?php
namespace Icicle\Concurrent\Worker;
namespace Amp\Concurrent\Worker;
/**
* A runnable unit of execution.
*/
interface Task
{
interface Task {
/**
* @coroutine
*
* Runs the task inside the caller's context.
*
* Does not have to be a coroutine, can also be a regular function returning a value.
*
* @param \Icicle\Concurrent\Worker\Environment
* @param \Amp\Concurrent\Worker\Environment
*
* @return mixed|\Icicle\Awaitable\Awaitable|\Generator
*
* @resolve mixed
* @return mixed|\Interop\Async\Awaitable|\Generator
*/
public function run(Environment $environment);
}

View File

@ -1,11 +1,13 @@
<?php
namespace Icicle\Concurrent\Worker;
namespace Amp\Concurrent\Worker;
use Interop\Async\Awaitable;
/**
* An interface for a parallel worker thread that runs a queue of tasks.
*/
interface Worker
{
interface Worker {
/**
* Checks if the worker is running.
*
@ -26,26 +28,18 @@ interface Worker
public function start();
/**
* @coroutine
*
* Enqueues a task to be executed by the worker.
*
* @param Task $task The task to enqueue.
*
* @return \Generator
*
* @resolve mixed Task return value.
* @return \Interop\Async\Awaitable<mixed> Resolves with the return value of Task::run().
*/
public function enqueue(Task $task): \Generator;
public function enqueue(Task $task): Awaitable;
/**
* @coroutine
*
* @return \Generator
*
* @resolve int Exit code.
* @return \Interop\Async\Awaitable<int> Exit code.
*/
public function shutdown(): \Generator;
public function shutdown(): Awaitable;
/**
* Immediately kills the context.

View File

@ -1,11 +1,11 @@
<?php
namespace Icicle\Concurrent\Worker;
namespace Amp\Concurrent\Worker;
/**
* Interface for factories used to create new workers.
*/
interface WorkerFactory
{
interface WorkerFactory {
/**
* Creates a new worker instance.
*

View File

@ -1,19 +1,19 @@
<?php
namespace Icicle\Concurrent\Worker;
use Icicle\Concurrent\Forking\Fork;
use Icicle\Concurrent\Worker\Internal\TaskRunner;
namespace Amp\Concurrent\Worker;
use Amp\Concurrent\Forking\Fork;
use Amp\Concurrent\Worker\Internal\TaskRunner;
use Interop\Async\Awaitable;
/**
* A worker thread that executes task objects.
*/
class WorkerFork extends AbstractWorker
{
public function __construct()
{
parent::__construct(new Fork(function (): \Generator {
$runner = new TaskRunner($this, new BasicEnvironment());
return yield from $runner->run();
class WorkerFork extends AbstractWorker {
public function __construct() {
parent::__construct(new Fork(function (): Awaitable {
$runner = new TaskRunner($this, new BasicEnvironment);
return $runner->run();
}));
}
}

View File

@ -1,16 +1,15 @@
<?php
namespace Icicle\Concurrent\Worker;
use Icicle\Concurrent\Process\ChannelledProcess;
namespace Amp\Concurrent\Worker;
use Amp\Concurrent\Process\ChannelledProcess;
/**
* A worker thread that executes task objects.
*/
class WorkerProcess extends AbstractWorker
{
public function __construct()
{
$dir = dirname(dirname(__DIR__)) . '/bin';
class WorkerProcess extends AbstractWorker {
public function __construct() {
$dir = \dirname(\dirname(__DIR__)) . '/bin';
parent::__construct(new ChannelledProcess($dir . '/worker.php', $dir));
}
}

View File

@ -1,19 +1,19 @@
<?php
namespace Icicle\Concurrent\Worker;
use Icicle\Concurrent\Threading\Thread;
use Icicle\Concurrent\Worker\Internal\TaskRunner;
namespace Amp\Concurrent\Worker;
use Amp\Concurrent\Threading\Thread;
use Amp\Concurrent\Worker\Internal\TaskRunner;
use Interop\Async\Awaitable;
/**
* A worker thread that executes task objects.
*/
class WorkerThread extends AbstractWorker
{
public function __construct()
{
parent::__construct(new Thread(function (): \Generator {
$runner = new TaskRunner($this, new BasicEnvironment());
return yield from $runner->run();
class WorkerThread extends AbstractWorker {
public function __construct() {
parent::__construct(new Thread(function (): Awaitable {
$runner = new TaskRunner($this, new BasicEnvironment);
return $runner->run();
}));
}
}

View File

@ -1,86 +1,78 @@
<?php
namespace Icicle\Concurrent\Worker;
if (!function_exists(__NAMESPACE__ . '\pool')) {
/**
* Returns the global worker pool for the current context.
*
* @param \Icicle\Concurrent\Worker\Pool|null $pool A worker pool instance.
*
* @return \Icicle\Concurrent\Worker\Pool The global worker pool instance.
*/
function pool(Pool $pool = null): Pool
{
static $instance;
namespace Amp\Concurrent\Worker;
if (null !== $pool) {
$instance = $pool;
} elseif (null === $instance) {
$instance = new DefaultPool();
}
use Interop\Async\Awaitable;
if (!$instance->isRunning()) {
$instance->start();
}
/**
* Returns the global worker pool for the current context.
*
* @param \Amp\Concurrent\Worker\Pool|null $pool A worker pool instance.
*
* @return \Amp\Concurrent\Worker\Pool The global worker pool instance.
*/
function pool(Pool $pool = null): Pool {
static $instance;
return $instance;
if (null !== $pool) {
$instance = $pool;
} elseif (null === $instance) {
$instance = new DefaultPool();
}
/**
* @coroutine
*
* Enqueues a task to be executed by the global worker pool.
*
* @param \Icicle\Concurrent\Worker\Task $task The task to enqueue.
*
* @return \Generator
*
* @resolve mixed The return value of the task.
*/
function enqueue(Task $task): \Generator
{
return pool()->enqueue($task);
if (!$instance->isRunning()) {
$instance->start();
}
/**
* Creates a worker using the global worker factory.
*
* @return \Icicle\Concurrent\Worker\Worker
*/
function create(): Worker
{
$worker = factory()->create();
$worker->start();
return $worker;
}
/**
* Gets or sets the global worker factory.
*
* @param \Icicle\Concurrent\Worker\WorkerFactory|null $factory
*
* @return \Icicle\Concurrent\Worker\WorkerFactory
*/
function factory(WorkerFactory $factory = null): WorkerFactory
{
static $instance;
if (null !== $factory) {
$instance = $factory;
} elseif (null === $instance) {
$instance = new DefaultWorkerFactory();
}
return $instance;
}
/**
* Gets a worker from the global worker pool.
*
* @return \Icicle\Concurrent\Worker\Worker
*/
function get(): Worker
{
return pool()->get();
}
return $instance;
}
/**
* Enqueues a task to be executed by the global worker pool.
*
* @param \Amp\Concurrent\Worker\Task $task The task to enqueue.
*
* @return \Interop\Async\Awaitable<mixed>
*/
function enqueue(Task $task): Awaitable {
return pool()->enqueue($task);
}
/**
* Creates a worker using the global worker factory.
*
* @return \Amp\Concurrent\Worker\Worker
*/
function create(): Worker {
$worker = factory()->create();
$worker->start();
return $worker;
}
/**
* Gets or sets the global worker factory.
*
* @param \Amp\Concurrent\Worker\WorkerFactory|null $factory
*
* @return \Amp\Concurrent\Worker\WorkerFactory
*/
function factory(WorkerFactory $factory = null): WorkerFactory {
static $instance;
if (null !== $factory) {
$instance = $factory;
} elseif (null === $instance) {
$instance = new DefaultWorkerFactory();
}
return $instance;
}
/**
* Gets a worker from the global worker pool.
*
* @return \Amp\Concurrent\Worker\Worker
*/
function get(): Worker {
return pool()->get();
}

View File

@ -1,14 +1,13 @@
<?php
namespace Icicle\Concurrent\Exception;
class WorkerException extends \Exception implements Exception
{
namespace Amp\Concurrent;
class WorkerException extends \Exception {
/**
* @param string $message
* @param \Throwable|null $previous
*/
public function __construct(string $message, \Throwable $previous = null)
{
public function __construct(string $message, \Throwable $previous = null) {
parent::__construct($message, 0, $previous);
}
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpdoc>
<title>Icicle</title>
<title>Amp</title>
<parser>
<target>build/docs</target>
<encoding>utf8</encoding>

View File

@ -13,8 +13,8 @@
stopOnFailure="false"
>
<testsuites>
<testsuite name="Icicle Concurrent">
<directory>tests</directory>
<testsuite name="Amp Concurrent">
<directory>test</directory>
</testsuite>
</testsuites>
@ -25,6 +25,6 @@
</filter>
<logging>
<log type="coverage-html" target="build/coverage" title="Icicle" highlight="true"/>
<log type="coverage-html" target="build/coverage" title="Amp" highlight="true"/>
</logging>
</phpunit>

View File

@ -1,9 +1,10 @@
<?php
namespace Icicle\Tests\Concurrent;
use Icicle\Concurrent\Sync\Internal\ExitSuccess;
use Icicle\Coroutine;
use Icicle\Loop;
namespace Amp\Tests\Concurrent;
use Amp\Concurrent\Sync\Internal\ExitSuccess;
use Amp\Coroutine;
use Amp\Loop;
abstract class AbstractContextTest extends TestCase
{
@ -44,7 +45,7 @@ abstract class AbstractContextTest extends TestCase
}
/**
* @expectedException \Icicle\Concurrent\Exception\StatusError
* @expectedException \Amp\Concurrent\Exception\StatusError
*/
public function testStartWhileRunningThrowsError()
{
@ -57,7 +58,7 @@ abstract class AbstractContextTest extends TestCase
}
/**
* @expectedException \Icicle\Concurrent\Exception\StatusError
* @expectedException \Amp\Concurrent\Exception\StatusError
*/
public function testStartMultipleTimesThrowsError()
{
@ -81,7 +82,7 @@ abstract class AbstractContextTest extends TestCase
}
/**
* @expectedException \Icicle\Concurrent\Exception\PanicError
* @expectedException \Amp\Concurrent\Exception\PanicError
*/
public function testExceptionInContextPanics()
{
@ -98,7 +99,7 @@ abstract class AbstractContextTest extends TestCase
}
/**
* @expectedException \Icicle\Concurrent\Exception\PanicError
* @expectedException \Amp\Concurrent\Exception\PanicError
*/
public function testReturnUnserializableDataPanics()
{
@ -133,7 +134,7 @@ abstract class AbstractContextTest extends TestCase
}
/**
* @expectedException \Icicle\Concurrent\Exception\StatusError
* @expectedException \Amp\Concurrent\Exception\StatusError
*/
public function testJoinWithoutStartThrowsError()
{
@ -184,7 +185,7 @@ abstract class AbstractContextTest extends TestCase
/**
* @depends testSendAndReceive
* @expectedException \Icicle\Concurrent\Exception\SynchronizationError
* @expectedException \Amp\Concurrent\Exception\SynchronizationError
*/
public function testJoinWhenContextSendingData()
{
@ -203,7 +204,7 @@ abstract class AbstractContextTest extends TestCase
/**
* @depends testSendAndReceive
* @expectedException \Icicle\Concurrent\Exception\StatusError
* @expectedException \Amp\Concurrent\Exception\StatusError
*/
public function testReceiveBeforeContextHasStarted()
{
@ -221,7 +222,7 @@ abstract class AbstractContextTest extends TestCase
/**
* @depends testSendAndReceive
* @expectedException \Icicle\Concurrent\Exception\StatusError
* @expectedException \Amp\Concurrent\Exception\StatusError
*/
public function testSendBeforeContextHasStarted()
{
@ -239,7 +240,7 @@ abstract class AbstractContextTest extends TestCase
/**
* @depends testSendAndReceive
* @expectedException \Icicle\Concurrent\Exception\SynchronizationError
* @expectedException \Amp\Concurrent\Exception\SynchronizationError
*/
public function testReceiveWhenContextHasReturned()
{
@ -260,7 +261,7 @@ abstract class AbstractContextTest extends TestCase
/**
* @depends testSendAndReceive
* @expectedException \Icicle\Exception\InvalidArgumentError
* @expectedException \Amp\Exception\InvalidArgumentError
*/
public function testSendExitStatus()
{

View File

@ -1,10 +1,11 @@
<?php
namespace Icicle\Tests\Concurrent\Forking;
use Icicle\Concurrent\Forking\Fork;
use Icicle\Coroutine;
use Icicle\Loop;
use Icicle\Tests\Concurrent\AbstractContextTest;
namespace Amp\Tests\Concurrent\Forking;
use Amp\Concurrent\Forking\Fork;
use Amp\Coroutine;
use Amp\Loop;
use Amp\Tests\Concurrent\AbstractContextTest;
/**
* @group forking

View File

@ -1,5 +1,6 @@
<?php
namespace Icicle\Tests\Concurrent\Stub;
namespace Amp\Tests\Concurrent\Stub;
class CallbackStub
{

View File

@ -1,14 +1,15 @@
<?php
namespace Icicle\Tests\Concurrent\Sync;
use Icicle\Coroutine\Coroutine;
use Icicle\Loop;
use Icicle\Tests\Concurrent\TestCase;
namespace Amp\Tests\Concurrent\Sync;
use Amp\Coroutine;
use Amp\Loop;
use Amp\Tests\Concurrent\TestCase;
abstract class AbstractParcelTest extends TestCase
{
/**
* @return \Icicle\Concurrent\Sync\Parcel
* @return \Amp\Concurrent\Sync\Parcel
*/
abstract protected function createParcel($value);

View File

@ -1,20 +1,21 @@
<?php
namespace Icicle\Tests\Concurrent\Sync;
use Icicle\Concurrent\Sync\Lock;
use Icicle\Coroutine;
use Icicle\Loop;
use Icicle\Tests\Concurrent\TestCase;
namespace Amp\Tests\Concurrent\Sync;
use Amp\Concurrent\Sync\Lock;
use Amp\Coroutine;
use Amp\Loop;
use Amp\Tests\Concurrent\TestCase;
abstract class AbstractSemaphoreTest extends TestCase
{
/**
* @var \Icicle\Concurrent\Sync\Semaphore
* @var \Amp\Concurrent\Sync\Semaphore
*/
protected $semaphore;
/**
* @return \Icicle\Concurrent\Sync\Semaphore
* @return \Amp\Concurrent\Sync\Semaphore
*/
abstract public function createSemaphore($locks);
@ -117,6 +118,6 @@ abstract class AbstractSemaphoreTest extends TestCase
$lock->release();
});
$this->assertRunTimeGreaterThan('Icicle\Loop\run', 1);
$this->assertRunTimeGreaterThan('Amp\Loop\run', 1);
}
}

View File

@ -1,17 +1,18 @@
<?php
namespace Icicle\Tests\Concurrent\Sync;
use Icicle\Concurrent\Sync\ChannelledStream;
use Icicle\Coroutine;
use Icicle\Loop;
use Icicle\Stream\{DuplexStream, ReadableStream};
use Icicle\Stream\Exception\{UnreadableException, UnwritableException};
use Icicle\Tests\Concurrent\TestCase;
namespace Amp\Tests\Concurrent\Sync;
use Amp\Concurrent\Sync\ChannelledStream;
use Amp\Coroutine;
use Amp\Loop;
use Amp\Stream\{DuplexStream, ReadableStream};
use Amp\Stream\Exception\{UnreadableException, UnwritableException};
use Amp\Tests\Concurrent\TestCase;
class ChannelledStreamTest extends TestCase
{
/**
* @return \Icicle\Stream\DuplexStream|\PHPUnit_Framework_MockObject_MockObject
* @return \Amp\Stream\DuplexStream|\PHPUnit_Framework_MockObject_MockObject
*/
protected function createMockStream()
{
@ -36,7 +37,7 @@ class ChannelledStreamTest extends TestCase
}
/**
* @expectedException \Icicle\Exception\InvalidArgumentError
* @expectedException \Amp\Exception\InvalidArgumentError
*/
public function testReadableWithoutWritable()
{
@ -88,7 +89,7 @@ class ChannelledStreamTest extends TestCase
/**
* @depends testSendReceive
* @expectedException \Icicle\Concurrent\Exception\ChannelException
* @expectedException \Amp\Concurrent\Exception\ChannelException
*/
public function testInvalidDataReceived()
{
@ -107,7 +108,7 @@ class ChannelledStreamTest extends TestCase
/**
* @depends testSendReceive
* @expectedException \Icicle\Concurrent\Exception\ChannelException
* @expectedException \Amp\Concurrent\Exception\ChannelException
*/
public function testSendUnserializableData()
{
@ -126,7 +127,7 @@ class ChannelledStreamTest extends TestCase
/**
* @depends testSendReceive
* @expectedException \Icicle\Concurrent\Exception\ChannelException
* @expectedException \Amp\Concurrent\Exception\ChannelException
*/
public function testSendAfterClose()
{
@ -147,7 +148,7 @@ class ChannelledStreamTest extends TestCase
/**
* @depends testSendReceive
* @expectedException \Icicle\Concurrent\Exception\ChannelException
* @expectedException \Amp\Concurrent\Exception\ChannelException
*/
public function testReceiveAfterClose()
{

View File

@ -1,10 +1,11 @@
<?php
namespace Icicle\Tests\Concurrent\Sync;
use Icicle\Concurrent\Sync\FileMutex;
use Icicle\Coroutine;
use Icicle\Loop;
use Icicle\Tests\Concurrent\TestCase;
namespace Amp\Tests\Concurrent\Sync;
use Amp\Concurrent\Sync\FileMutex;
use Amp\Coroutine;
use Amp\Loop;
use Amp\Tests\Concurrent\TestCase;
class FileMutexTest extends TestCase
{

View File

@ -1,8 +1,9 @@
<?php
namespace Icicle\Tests\Concurrent\Sync;
use Icicle\Concurrent\Sync\Lock;
use Icicle\Tests\Concurrent\TestCase;
namespace Amp\Tests\Concurrent\Sync;
use Amp\Concurrent\Sync\Lock;
use Amp\Tests\Concurrent\TestCase;
class LockTest extends TestCase
{
@ -21,7 +22,7 @@ class LockTest extends TestCase
}
/**
* @expectedException \Icicle\Concurrent\Exception\LockAlreadyReleasedError
* @expectedException \Amp\Concurrent\Exception\LockAlreadyReleasedError
*/
public function testThrowsOnMultiRelease()
{

View File

@ -1,10 +1,11 @@
<?php
namespace Icicle\Tests\Concurrent\Sync;
use Icicle\Concurrent\Forking\Fork;
use Icicle\Concurrent\Sync\{PosixSemaphore, Semaphore};
use Icicle\Coroutine;
use Icicle\Loop;
namespace Amp\Tests\Concurrent\Sync;
use Amp\Concurrent\Forking\Fork;
use Amp\Concurrent\Sync\{PosixSemaphore, Semaphore};
use Amp\Coroutine;
use Amp\Loop;
/**
* @group posix

View File

@ -1,8 +1,9 @@
<?php
namespace Icicle\Tests\Concurrent\Sync;
use Icicle\Concurrent\Sync\SharedMemoryParcel;
use Icicle\Coroutine\Coroutine;
namespace Amp\Tests\Concurrent\Sync;
use Amp\Concurrent\Sync\SharedMemoryParcel;
use Amp\Coroutine;
/**
* @requires extension shmop
@ -40,7 +41,7 @@ class SharedMemoryParcelTest extends AbstractParcelTest
}
/**
* @expectedException \Icicle\Concurrent\Exception\SharedMemoryException
* @expectedException \Amp\Concurrent\Exception\SharedMemoryException
*/
public function testUnwrapThrowsErrorIfFreed()
{

View File

@ -1,7 +1,8 @@
<?php
namespace Icicle\Tests\Concurrent;
use Icicle\Tests\Concurrent\Stub\CallbackStub;
namespace Amp\Tests\Concurrent;
use Amp\Tests\Concurrent\Stub\CallbackStub;
/**
* Abstract test class with methods for creating callbacks and asserting runtimes.

View File

@ -1,10 +1,11 @@
<?php
namespace Icicle\Tests\Concurrent\Threading;
use Icicle\Concurrent\Threading\Mutex;
use Icicle\Coroutine;
use Icicle\Loop;
use Icicle\Tests\Concurrent\TestCase;
namespace Amp\Tests\Concurrent\Threading;
use Amp\Concurrent\Threading\Mutex;
use Amp\Coroutine;
use Amp\Loop;
use Amp\Tests\Concurrent\TestCase;
/**
* @group threading

View File

@ -1,8 +1,9 @@
<?php
namespace Icicle\Tests\Concurrent\Threading;
use Icicle\Concurrent\Threading\Parcel;
use Icicle\Tests\Concurrent\Sync\AbstractParcelTest;
namespace Amp\Tests\Concurrent\Threading;
use Amp\Concurrent\Threading\Parcel;
use Amp\Tests\Concurrent\Sync\AbstractParcelTest;
/**
* @requires extension pthreads

View File

@ -1,11 +1,12 @@
<?php
namespace Icicle\Tests\Concurrent\Threading;
use Icicle\Concurrent\Sync\Semaphore as SyncSemaphore;
use Icicle\Concurrent\Threading\{Semaphore, Thread};
use Icicle\Coroutine;
use Icicle\Loop;
use Icicle\Tests\Concurrent\Sync\AbstractSemaphoreTest;
namespace Amp\Tests\Concurrent\Threading;
use Amp\Concurrent\Sync\Semaphore as SyncSemaphore;
use Amp\Concurrent\Threading\{Semaphore, Thread};
use Amp\Coroutine;
use Amp\Loop;
use Amp\Tests\Concurrent\Sync\AbstractSemaphoreTest;
/**
* @group threading

View File

@ -1,10 +1,11 @@
<?php
namespace Icicle\Tests\Concurrent\Threading;
use Icicle\Concurrent\Threading\Thread;
use Icicle\Coroutine;
use Icicle\Loop;
use Icicle\Tests\Concurrent\AbstractContextTest;
namespace Amp\Tests\Concurrent\Threading;
use Amp\Concurrent\Threading\Thread;
use Amp\Coroutine;
use Amp\Loop;
use Amp\Tests\Concurrent\AbstractContextTest;
/**
* @group threading

View File

@ -1,10 +1,11 @@
<?php
namespace Icicle\Tests\Concurrent\Worker;
use Icicle\Awaitable;
use Icicle\Coroutine;
use Icicle\Loop;
use Icicle\Tests\Concurrent\TestCase;
namespace Amp\Tests\Concurrent\Worker;
use Amp\Awaitable;
use Amp\Coroutine;
use Amp\Loop;
use Amp\Tests\Concurrent\TestCase;
abstract class AbstractPoolTest extends TestCase
{
@ -12,7 +13,7 @@ abstract class AbstractPoolTest extends TestCase
* @param int $min
* @param int $max
*
* @return \Icicle\Concurrent\Worker\Pool
* @return \Amp\Concurrent\Worker\Pool
*/
abstract protected function createPool($min = null, $max = null);

View File

@ -1,15 +1,16 @@
<?php
namespace Icicle\Tests\Concurrent\Worker;
use Icicle\Awaitable;
use Icicle\Coroutine;
use Icicle\Loop;
use Icicle\Tests\Concurrent\TestCase;
namespace Amp\Tests\Concurrent\Worker;
use Amp\Awaitable;
use Amp\Coroutine;
use Amp\Loop;
use Amp\Tests\Concurrent\TestCase;
abstract class AbstractWorkerTest extends TestCase
{
/**
* @return \Icicle\Concurrent\Worker\Worker
* @return \Amp\Concurrent\Worker\Worker
*/
abstract protected function createWorker();

View File

@ -1,7 +1,8 @@
<?php
namespace Icicle\Tests\Concurrent\Worker;
use Icicle\Concurrent\Worker\{DefaultPool, WorkerFactory, WorkerFork};
namespace Amp\Tests\Concurrent\Worker;
use Amp\Concurrent\Worker\{DefaultPool, WorkerFactory, WorkerFork};
/**
* @group forking

View File

@ -1,10 +1,11 @@
<?php
namespace Icicle\Tests\Concurrent\Worker;
use Icicle\Concurrent\Worker;
use Icicle\Concurrent\Worker\{Environment, Pool, Task, WorkerFactory};
use Icicle\Coroutine\Coroutine;
use Icicle\Tests\Concurrent\TestCase;
namespace Amp\Tests\Concurrent\Worker;
use Amp\Concurrent\Worker;
use Amp\Concurrent\Worker\{Environment, Pool, Task, WorkerFactory};
use Amp\Coroutine;
use Amp\Tests\Concurrent\TestCase;
class FunctionsTest extends TestCase
{

View File

@ -1,7 +1,8 @@
<?php
namespace Icicle\Tests\Concurrent\Worker;
use Icicle\Concurrent\Worker\{DefaultPool, WorkerFactory, WorkerProcess};
namespace Amp\Tests\Concurrent\Worker;
use Amp\Concurrent\Worker\{DefaultPool, WorkerFactory, WorkerProcess};
/**
* @group process

Some files were not shown because too many files have changed in this diff Show More