2015-07-14 00:30:59 +02:00
|
|
|
<?php
|
|
|
|
namespace Icicle\Concurrent\Threading;
|
|
|
|
|
2015-08-22 23:27:44 +02:00
|
|
|
use Icicle\Concurrent\ChannelInterface;
|
|
|
|
use Icicle\Concurrent\Exception\InvalidArgumentError;
|
|
|
|
use Icicle\Concurrent\Exception\SynchronizationError;
|
2015-08-05 09:48:43 +02:00
|
|
|
use Icicle\Concurrent\Sync\Channel;
|
2015-08-22 23:27:44 +02:00
|
|
|
use Icicle\Concurrent\Sync\ExitStatusInterface;
|
|
|
|
use Icicle\Concurrent\Sync\Lock;
|
|
|
|
use Icicle\Coroutine;
|
2015-07-27 00:53:00 +02:00
|
|
|
|
2015-07-15 19:36:32 +02:00
|
|
|
/**
|
2015-08-22 23:27:44 +02:00
|
|
|
* Implements an execution context using native multi-threading.
|
2015-08-11 00:38:58 +02:00
|
|
|
*
|
2015-08-22 23:27:44 +02:00
|
|
|
* The thread context is not itself threaded. A local instance of the context is
|
|
|
|
* maintained both in the context that creates the thread and in the thread
|
|
|
|
* itself.
|
2015-07-15 19:36:32 +02:00
|
|
|
*/
|
2015-08-22 23:27:44 +02:00
|
|
|
class Thread implements ChannelInterface
|
2015-07-14 00:30:59 +02:00
|
|
|
{
|
2015-07-27 00:53:00 +02:00
|
|
|
/**
|
2015-08-22 23:27:44 +02:00
|
|
|
* @var \Icicle\Concurrent\Threading\InternalThread An internal thread instance.
|
2015-07-27 00:53:00 +02:00
|
|
|
*/
|
2015-08-22 23:27:44 +02:00
|
|
|
private $thread;
|
2015-07-27 00:53:00 +02:00
|
|
|
|
2015-08-07 06:25:04 +02:00
|
|
|
/**
|
2015-08-22 23:27:44 +02:00
|
|
|
* @var \Icicle\Concurrent\Sync\Channel A channel for communicating with the thread.
|
2015-08-07 06:25:04 +02:00
|
|
|
*/
|
2015-08-22 23:27:44 +02:00
|
|
|
private $channel;
|
2015-08-07 06:25:04 +02:00
|
|
|
|
2015-08-07 01:59:25 +02:00
|
|
|
/**
|
2015-08-22 23:27:44 +02:00
|
|
|
* Spawns a new thread and runs it.
|
|
|
|
*
|
|
|
|
* @param callable $function A callable to invoke in the thread.
|
|
|
|
*
|
|
|
|
* @return Thread The thread object that was spawned.
|
2015-08-07 01:59:25 +02:00
|
|
|
*/
|
2015-08-22 23:27:44 +02:00
|
|
|
public static function spawn(callable $function /* , ...$args */)
|
|
|
|
{
|
2015-08-24 17:47:36 +02:00
|
|
|
$class = new \ReflectionClass(__CLASS__);
|
|
|
|
$thread = $class->newInstanceArgs(func_get_args());
|
2015-08-22 23:27:44 +02:00
|
|
|
$thread->start();
|
|
|
|
return $thread;
|
|
|
|
}
|
2015-08-05 09:48:43 +02:00
|
|
|
|
2015-08-18 17:12:06 +02:00
|
|
|
/**
|
2015-08-22 23:27:44 +02:00
|
|
|
* Creates a new thread context from a thread.
|
|
|
|
*
|
|
|
|
* @param callable $function
|
2015-08-18 17:12:06 +02:00
|
|
|
*/
|
2015-08-22 23:27:44 +02:00
|
|
|
public function __construct(callable $function /* , ...$args */)
|
|
|
|
{
|
|
|
|
$args = array_slice(func_get_args(), 1);
|
|
|
|
|
|
|
|
list($channel, $socket) = Channel::createSocketPair();
|
|
|
|
|
|
|
|
$this->channel = new Channel($channel);
|
|
|
|
$this->thread = new InternalThread($socket, $function, $args);
|
|
|
|
}
|
2015-08-18 17:12:06 +02:00
|
|
|
|
2015-07-27 00:53:00 +02:00
|
|
|
/**
|
2015-08-22 23:27:44 +02:00
|
|
|
* Checks if the context is running.
|
2015-07-27 00:53:00 +02:00
|
|
|
*
|
2015-08-22 23:27:44 +02:00
|
|
|
* @return bool True if the context is running, otherwise false.
|
2015-07-27 00:53:00 +02:00
|
|
|
*/
|
2015-08-22 23:27:44 +02:00
|
|
|
public function isRunning()
|
2015-07-15 00:15:10 +02:00
|
|
|
{
|
2015-08-22 23:27:44 +02:00
|
|
|
return $this->thread->isRunning();
|
2015-08-07 01:59:25 +02:00
|
|
|
}
|
|
|
|
|
2015-08-05 09:48:43 +02:00
|
|
|
/**
|
2015-08-22 23:27:44 +02:00
|
|
|
* Starts the context execution.
|
2015-08-05 09:48:43 +02:00
|
|
|
*/
|
2015-08-22 23:27:44 +02:00
|
|
|
public function start()
|
2015-07-14 00:30:59 +02:00
|
|
|
{
|
2015-08-22 23:27:44 +02:00
|
|
|
if ($this->isRunning()) {
|
|
|
|
throw new SynchronizationError('The thread has already been started.');
|
2015-08-05 09:48:43 +02:00
|
|
|
}
|
|
|
|
|
2015-08-22 23:27:44 +02:00
|
|
|
$this->thread->start(PTHREADS_INHERIT_ALL);
|
|
|
|
}
|
2015-08-11 00:38:58 +02:00
|
|
|
|
2015-08-22 23:27:44 +02:00
|
|
|
/**
|
|
|
|
* Immediately kills the context.
|
|
|
|
*/
|
|
|
|
public function kill()
|
|
|
|
{
|
|
|
|
$this->channel->close();
|
|
|
|
$this->thread->kill();
|
2015-08-05 09:48:43 +02:00
|
|
|
}
|
|
|
|
|
2015-08-18 17:12:06 +02:00
|
|
|
/**
|
2015-08-22 23:27:44 +02:00
|
|
|
* @coroutine
|
|
|
|
*
|
|
|
|
* Gets a promise that resolves when the context ends and joins with the
|
|
|
|
* parent context.
|
2015-08-18 17:12:06 +02:00
|
|
|
*
|
2015-08-22 23:27:44 +02:00
|
|
|
* @return \Generator
|
|
|
|
*
|
|
|
|
* @resolve mixed Resolved with the return or resolution value of the context once it has completed execution.
|
2015-08-18 17:12:06 +02:00
|
|
|
*/
|
2015-08-22 23:27:44 +02:00
|
|
|
public function join()
|
2015-08-18 17:12:06 +02:00
|
|
|
{
|
2015-08-22 23:27:44 +02:00
|
|
|
if (!$this->isRunning()) {
|
|
|
|
throw new SynchronizationError('The thread has not been started or has already finished.');
|
2015-08-18 17:12:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2015-08-22 23:27:44 +02:00
|
|
|
$response = (yield $this->channel->receive());
|
|
|
|
|
|
|
|
if (!$response instanceof ExitStatusInterface) {
|
|
|
|
throw new SynchronizationError('Did not receive an exit status from thread.');
|
2015-08-18 17:12:06 +02:00
|
|
|
}
|
2015-08-22 23:27:44 +02:00
|
|
|
|
|
|
|
yield $response->getResult();
|
2015-08-18 17:12:06 +02:00
|
|
|
} finally {
|
2015-08-22 23:27:44 +02:00
|
|
|
$this->channel->close();
|
|
|
|
$this->thread->join();
|
2015-08-18 17:12:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-08-22 23:27:44 +02:00
|
|
|
* {@inheritdoc}
|
2015-08-18 17:12:06 +02:00
|
|
|
*/
|
2015-08-22 23:27:44 +02:00
|
|
|
public function receive()
|
2015-08-18 17:12:06 +02:00
|
|
|
{
|
2015-08-22 23:27:44 +02:00
|
|
|
if (!$this->isRunning()) {
|
|
|
|
throw new SynchronizationError('The thread has not been started or has already finished.');
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = (yield $this->channel->receive());
|
|
|
|
|
|
|
|
if ($data instanceof ExitStatusInterface) {
|
|
|
|
$data = $data->getResult();
|
|
|
|
throw new SynchronizationError(sprintf(
|
|
|
|
'Thread unexpectedly exited with result of type: %s',
|
|
|
|
is_object($data) ? get_class($data) : gettype($data)
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
yield $data;
|
2015-08-18 17:12:06 +02:00
|
|
|
}
|
|
|
|
|
2015-08-07 01:59:25 +02:00
|
|
|
/**
|
2015-08-22 23:27:44 +02:00
|
|
|
* {@inheritdoc}
|
2015-08-07 01:59:25 +02:00
|
|
|
*/
|
2015-08-22 23:27:44 +02:00
|
|
|
public function send($data)
|
2015-08-05 09:48:43 +02:00
|
|
|
{
|
2015-08-22 23:27:44 +02:00
|
|
|
if (!$this->isRunning()) {
|
|
|
|
throw new SynchronizationError('The thread has not been started or has already finished.');
|
|
|
|
}
|
2015-08-07 07:07:53 +02:00
|
|
|
|
2015-08-22 23:27:44 +02:00
|
|
|
if ($data instanceof ExitStatusInterface) {
|
|
|
|
throw new InvalidArgumentError('Cannot send exit status objects.');
|
2015-07-27 00:53:00 +02:00
|
|
|
}
|
2015-07-14 00:30:59 +02:00
|
|
|
|
2015-08-22 23:27:44 +02:00
|
|
|
return $this->channel->send($data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*/
|
|
|
|
public function acquire()
|
|
|
|
{
|
|
|
|
while (!$this->thread->tsl()) {
|
|
|
|
yield Coroutine\sleep(0.01);
|
2015-08-19 02:12:58 +02:00
|
|
|
}
|
2015-08-22 23:27:44 +02:00
|
|
|
|
|
|
|
yield new Lock(function () {
|
|
|
|
$this->thread->release();
|
|
|
|
});
|
2015-08-05 09:48:43 +02:00
|
|
|
}
|
2015-07-14 00:30:59 +02:00
|
|
|
}
|