mirror of
https://github.com/amphp/parallel.git
synced 2025-01-16 20:28:21 +01:00
Port to Amp
This commit is contained in:
parent
54810f1d67
commit
da84a772cf
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,4 +1,4 @@
|
||||
tests export-ignore
|
||||
test export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.travis.yml export-ignore
|
||||
|
28
CHANGELOG.md
28
CHANGELOG.md
@ -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`.
|
||||
|
@ -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).
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
5
lib/ContextException.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Concurrent;
|
||||
|
||||
class ContextException extends \Exception {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
interface Error extends \Icicle\Exception\Error, Throwable {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
interface Exception extends \Icicle\Exception\Exception, Throwable {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
class ForkException extends \Exception implements Exception {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
class LockAlreadyReleasedError extends \Error implements Error {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
class MutexException extends \Exception implements Exception {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
class ProcessException extends \Exception implements Exception {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
class SemaphoreException extends \Exception implements Exception {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
class SharedMemoryException extends \Exception implements Exception {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
class StatusError extends \Error implements Exception {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
class SynchronizationError extends \Error implements Error {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
class ThreadException extends \Exception implements Exception {}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
interface Throwable extends \Icicle\Exception\Throwable {}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
5
lib/LockAlreadyReleasedError.php
Normal file
5
lib/LockAlreadyReleasedError.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Concurrent;
|
||||
|
||||
class LockAlreadyReleasedError extends \Error {}
|
5
lib/MutexException.php
Normal file
5
lib/MutexException.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Concurrent;
|
||||
|
||||
class MutexException extends \Exception {}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.');
|
||||
}
|
||||
|
5
lib/SemaphoreException.php
Normal file
5
lib/SemaphoreException.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Concurrent;
|
||||
|
||||
class SemaphoreException extends \Exception {}
|
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent\Exception;
|
||||
|
||||
namespace Amp\Concurrent;
|
||||
|
||||
class SerializationException extends ChannelException {}
|
5
lib/SharedMemoryException.php
Normal file
5
lib/SharedMemoryException.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Concurrent;
|
||||
|
||||
class SharedMemoryException extends \Exception {}
|
5
lib/StatusError.php
Normal file
5
lib/StatusError.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Concurrent;
|
||||
|
||||
class StatusError extends \Error {}
|
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
namespace Icicle\Concurrent;
|
||||
|
||||
namespace Amp\Concurrent;
|
||||
|
||||
interface Strand extends Context, Sync\Channel {}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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.');
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.');
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
5
lib/SynchronizationError.php
Normal file
5
lib/SynchronizationError.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Concurrent;
|
||||
|
||||
class SynchronizationError extends \Error {}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
{
|
@ -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
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
namespace Icicle\Tests\Concurrent\Stub;
|
||||
|
||||
namespace Amp\Tests\Concurrent\Stub;
|
||||
|
||||
class CallbackStub
|
||||
{
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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()
|
||||
{
|
@ -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
|
||||
{
|
@ -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()
|
||||
{
|
@ -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
|
@ -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()
|
||||
{
|
@ -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.
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
@ -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
|
||||
{
|
@ -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
Loading…
x
Reference in New Issue
Block a user