2016-12-30 02:16:04 +01:00
|
|
|
<?php
|
2015-08-24 17:40:36 +02:00
|
|
|
|
2016-08-23 23:47:40 +02:00
|
|
|
namespace Amp\Parallel\Threading\Internal;
|
2016-08-18 18:04:48 +02:00
|
|
|
|
|
|
|
use Amp\Coroutine;
|
2016-08-23 23:47:40 +02:00
|
|
|
use Amp\Parallel\{ ChannelException, SerializationException };
|
2016-08-31 01:27:14 +02:00
|
|
|
use Amp\Parallel\Sync\{ Channel, ChannelledSocket, Internal\ExitFailure, Internal\ExitSuccess };
|
2017-01-09 18:11:25 +01:00
|
|
|
use AsyncInterop\{ Loop, Promise };
|
2015-08-24 17:40:36 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* An internal thread that executes a given function concurrently.
|
|
|
|
*
|
|
|
|
* @internal
|
|
|
|
*/
|
2016-08-18 18:04:48 +02:00
|
|
|
class Thread extends \Thread {
|
|
|
|
const KILL_CHECK_FREQUENCY = 250;
|
2017-01-16 20:45:32 +01:00
|
|
|
const AUTOLOAD_FILENAME = "/autoload.php";
|
2017-01-16 20:30:16 +01:00
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
private static $autoloadPath;
|
2015-09-05 19:52:56 +02:00
|
|
|
|
2016-08-26 17:10:03 +02:00
|
|
|
/** @var callable The function to execute in the thread. */
|
2015-08-24 17:40:36 +02:00
|
|
|
private $function;
|
|
|
|
|
2016-08-26 17:10:03 +02:00
|
|
|
/** @var mixed[] Arguments to pass to the function. */
|
2015-08-24 17:40:36 +02:00
|
|
|
private $args;
|
|
|
|
|
2016-08-26 17:10:03 +02:00
|
|
|
/** @var resource */
|
2015-08-24 17:40:36 +02:00
|
|
|
private $socket;
|
|
|
|
|
2016-08-26 17:10:03 +02:00
|
|
|
/** @var bool */
|
2015-09-05 19:52:56 +02:00
|
|
|
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.
|
|
|
|
*/
|
2016-08-18 18:04:48 +02:00
|
|
|
public function __construct($socket, callable $function, array $args = []) {
|
2015-08-24 17:40:36 +02:00
|
|
|
$this->function = $function;
|
|
|
|
$this->args = $args;
|
|
|
|
$this->socket = $socket;
|
2017-01-16 20:30:16 +01:00
|
|
|
|
|
|
|
if (self::$autoloadPath === null) { // Determine path to composer autoload.php
|
|
|
|
foreach (\get_included_files() as $path) {
|
2017-01-16 20:45:32 +01:00
|
|
|
if (\substr($path, -\strlen(self::AUTOLOAD_FILENAME)) === self::AUTOLOAD_FILENAME) {
|
2017-01-16 20:30:16 +01:00
|
|
|
self::$autoloadPath = $path;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-16 20:45:32 +01:00
|
|
|
throw new \Error(\sprintf("Could not locate %s", self::AUTOLOAD_FILENAME));
|
2017-01-16 20:30:16 +01:00
|
|
|
}
|
2015-08-24 17:40:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
*/
|
2016-08-18 18:04:48 +02:00
|
|
|
public function run() {
|
2015-08-24 17:40:36 +02:00
|
|
|
/* 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.
|
|
|
|
*/
|
2017-01-13 11:00:35 +01:00
|
|
|
|
2017-01-16 20:30:16 +01:00
|
|
|
// Protect scope by using an unbound closure (protects static access as well).
|
|
|
|
$autoloadPath = self::$autoloadPath;
|
|
|
|
(static function () use ($autoloadPath) { require $autoloadPath; })->bindTo(null, null)();
|
2017-01-13 11:00:35 +01:00
|
|
|
|
2015-08-24 17:40:36 +02:00
|
|
|
// At this point, the thread environment has been prepared so begin using the thread.
|
2016-01-27 07:54:51 +01:00
|
|
|
|
|
|
|
try {
|
2016-12-30 02:17:26 +01:00
|
|
|
Loop::execute(\Amp\wrap(function () {
|
2016-08-31 01:27:14 +02:00
|
|
|
$channel = new ChannelledSocket($this->socket, $this->socket, false);
|
2017-01-13 11:00:35 +01:00
|
|
|
|
2016-12-30 02:17:26 +01:00
|
|
|
$watcher = Loop::repeat(self::KILL_CHECK_FREQUENCY, function () {
|
2016-08-18 18:04:48 +02:00
|
|
|
if ($this->killed) {
|
2016-12-30 02:17:26 +01:00
|
|
|
Loop::stop();
|
2016-08-18 18:04:48 +02:00
|
|
|
}
|
|
|
|
});
|
2017-01-13 11:00:35 +01:00
|
|
|
|
2016-12-30 02:17:26 +01:00
|
|
|
Loop::unreference($watcher);
|
2017-01-13 11:00:35 +01:00
|
|
|
|
2016-08-29 06:47:15 +02:00
|
|
|
return $this->execute($channel);
|
2016-12-30 02:17:26 +01:00
|
|
|
}));
|
2016-01-27 08:03:37 +01:00
|
|
|
} catch (\Throwable $exception) {
|
2016-08-18 18:04:48 +02:00
|
|
|
return 1;
|
2016-01-27 07:54:51 +01:00
|
|
|
}
|
2017-01-13 11:00:35 +01:00
|
|
|
|
2016-08-18 18:04:48 +02:00
|
|
|
return 0;
|
2015-09-05 19:52:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets a local variable to true so the running event loop can check for a kill signal.
|
|
|
|
*/
|
2016-08-18 18:04:48 +02:00
|
|
|
public function kill() {
|
2016-01-23 17:52:18 +01:00
|
|
|
return $this->killed = true;
|
2015-08-24 17:40:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @coroutine
|
|
|
|
*
|
2016-08-23 23:47:40 +02:00
|
|
|
* @param \Amp\Parallel\Sync\Channel $channel
|
2015-08-24 17:40:36 +02:00
|
|
|
*
|
|
|
|
* @return \Generator
|
|
|
|
*
|
2015-09-04 01:10:19 +02:00
|
|
|
* @codeCoverageIgnore Only executed in thread.
|
2015-08-24 17:40:36 +02:00
|
|
|
*/
|
2016-08-18 18:04:48 +02:00
|
|
|
private function execute(Channel $channel): \Generator {
|
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
|
|
|
}
|
2017-01-13 11:00:35 +01:00
|
|
|
|
2016-08-18 18:04:48 +02:00
|
|
|
$result = $function(...$this->args);
|
2017-01-13 11:00:35 +01:00
|
|
|
|
2016-08-18 18:04:48 +02:00
|
|
|
if ($result instanceof \Generator) {
|
|
|
|
$result = new Coroutine($result);
|
|
|
|
}
|
2017-01-13 11:00:35 +01:00
|
|
|
|
2016-11-15 00:43:44 +01:00
|
|
|
if ($result instanceof Promise) {
|
2016-08-18 18:04:48 +02:00
|
|
|
$result = yield $result;
|
|
|
|
}
|
2015-08-24 17:40:36 +02:00
|
|
|
|
2016-08-18 18:04:48 +02:00
|
|
|
$result = new ExitSuccess($result);
|
2016-01-23 07:00:56 +01:00
|
|
|
} catch (\Throwable $exception) {
|
2015-08-24 17:40:36 +02:00
|
|
|
$result = new ExitFailure($exception);
|
|
|
|
}
|
|
|
|
|
2015-12-12 07:34:41 +01:00
|
|
|
// Attempt to return the result.
|
|
|
|
try {
|
2016-01-23 18:20:58 +01:00
|
|
|
try {
|
2016-08-18 18:04:48 +02:00
|
|
|
return yield $channel->send($result);
|
2016-01-23 18:20:58 +01:00
|
|
|
} catch (SerializationException $exception) {
|
|
|
|
// Serializing the result failed. Send the reason why.
|
2016-08-18 18:04:48 +02:00
|
|
|
return yield $channel->send(new ExitFailure($exception));
|
2016-01-23 18:20:58 +01:00
|
|
|
}
|
|
|
|
} catch (ChannelException $exception) {
|
2016-01-23 17:57:10 +01:00
|
|
|
// The result was not sendable! The parent context must have died or killed the context.
|
2016-01-23 18:29:22 +01:00
|
|
|
return 0;
|
2015-12-12 07:34:41 +01:00
|
|
|
}
|
2015-08-24 17:40:36 +02:00
|
|
|
}
|
|
|
|
}
|