2015-08-24 17:40:36 +02:00
|
|
|
<?php
|
2015-08-29 08:40:10 +02:00
|
|
|
namespace Icicle\Concurrent\Threading\Internal;
|
2015-08-24 17:40:36 +02:00
|
|
|
|
|
|
|
use Icicle\Concurrent\Sync\Channel;
|
2015-12-06 07:32:06 +01:00
|
|
|
use Icicle\Concurrent\Sync\ChannelledStream;
|
2015-08-29 08:40:10 +02:00
|
|
|
use Icicle\Concurrent\Sync\Internal\ExitFailure;
|
|
|
|
use Icicle\Concurrent\Sync\Internal\ExitSuccess;
|
2015-08-24 17:40:36 +02:00
|
|
|
use Icicle\Coroutine\Coroutine;
|
|
|
|
use Icicle\Loop;
|
2015-10-17 01:20:20 +02:00
|
|
|
use Icicle\Stream\Pipe\DuplexPipe;
|
2015-08-24 17:40:36 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* An internal thread that executes a given function concurrently.
|
|
|
|
*
|
|
|
|
* @internal
|
|
|
|
*/
|
2015-08-29 08:40:10 +02:00
|
|
|
class Thread extends \Thread
|
2015-08-24 17:40:36 +02:00
|
|
|
{
|
2015-09-05 19:52:56 +02:00
|
|
|
const KILL_CHECK_FREQUENCY = 0.25;
|
|
|
|
|
2015-08-24 17:40:36 +02:00
|
|
|
/**
|
|
|
|
* @var callable The function to execute in the thread.
|
|
|
|
*/
|
|
|
|
private $function;
|
|
|
|
|
|
|
|
/**
|
2015-09-04 01:10:19 +02:00
|
|
|
* @var mixed[] Arguments to pass to the function.
|
2015-08-24 17:40:36 +02:00
|
|
|
*/
|
|
|
|
private $args;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var resource
|
|
|
|
*/
|
|
|
|
private $socket;
|
|
|
|
|
2015-09-05 19:52:56 +02:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
private $killed = false;
|
|
|
|
|
2015-08-24 17:40:36 +02:00
|
|
|
/**
|
|
|
|
* Creates a new thread object.
|
|
|
|
*
|
|
|
|
* @param resource $socket IPC communication socket.
|
|
|
|
* @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 = [])
|
|
|
|
{
|
|
|
|
$this->function = $function;
|
|
|
|
$this->args = $args;
|
|
|
|
$this->socket = $socket;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Runs the thread code and the initialized function.
|
2015-09-04 01:10:19 +02:00
|
|
|
*
|
|
|
|
* @codeCoverageIgnore Only executed in thread.
|
2015-08-24 17:40:36 +02:00
|
|
|
*/
|
|
|
|
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();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-17 01:20:20 +02:00
|
|
|
Loop\loop($loop = Loop\create(false)); // Disable signals in thread.
|
2015-09-05 19:52:56 +02:00
|
|
|
|
2015-08-24 17:40:36 +02:00
|
|
|
// At this point, the thread environment has been prepared so begin using the thread.
|
2015-12-30 05:29:01 +01:00
|
|
|
$channel = new ChannelledStream(new DuplexPipe($this->socket, false));
|
2015-08-24 17:40:36 +02:00
|
|
|
|
|
|
|
$coroutine = new Coroutine($this->execute($channel));
|
|
|
|
$coroutine->done();
|
|
|
|
|
2015-09-06 21:59:24 +02:00
|
|
|
$timer = $loop->timer(self::KILL_CHECK_FREQUENCY, true, function () use ($loop) {
|
2015-09-05 19:52:56 +02:00
|
|
|
if ($this->killed) {
|
|
|
|
$loop->stop();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
$timer->unreference();
|
|
|
|
|
|
|
|
$loop->run();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets a local variable to true so the running event loop can check for a kill signal.
|
|
|
|
*/
|
|
|
|
public function kill()
|
|
|
|
{
|
|
|
|
$this->killed = true;
|
2015-09-06 21:59:24 +02:00
|
|
|
return parent::kill();
|
2015-08-24 17:40:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @coroutine
|
|
|
|
*
|
2015-12-05 06:50:32 +01:00
|
|
|
* @param \Icicle\Concurrent\Sync\Channel $channel
|
2015-08-24 17:40:36 +02:00
|
|
|
*
|
|
|
|
* @return \Generator
|
|
|
|
*
|
|
|
|
* @resolve int
|
2015-09-04 01:10:19 +02:00
|
|
|
*
|
|
|
|
* @codeCoverageIgnore Only executed in thread.
|
2015-08-24 17:40:36 +02:00
|
|
|
*/
|
2015-12-05 06:50:32 +01:00
|
|
|
private function execute(Channel $channel)
|
2015-08-24 17:40:36 +02:00
|
|
|
{
|
|
|
|
try {
|
2015-08-27 21:49:41 +02:00
|
|
|
if ($this->function instanceof \Closure) {
|
2015-10-18 08:54:09 +02:00
|
|
|
$function = $this->function->bindTo($channel, Channel::class);
|
2015-08-27 21:49:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($function)) {
|
|
|
|
$function = $this->function;
|
2015-08-24 17:40:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$result = new ExitSuccess(yield call_user_func_array($function, $this->args));
|
|
|
|
} catch (\Exception $exception) {
|
|
|
|
$result = new ExitFailure($exception);
|
|
|
|
}
|
|
|
|
|
2015-12-12 07:34:41 +01:00
|
|
|
// Attempt to return the result.
|
|
|
|
try {
|
|
|
|
yield $channel->send($result);
|
|
|
|
} catch (\Exception $exception) {
|
|
|
|
// The result was not sendable! Try sending the reason why instead.
|
|
|
|
yield $channel->send(new ExitFailure($exception));
|
|
|
|
}
|
2015-08-24 17:40:36 +02:00
|
|
|
}
|
|
|
|
}
|