2016-12-30 02:16:04 +01:00
|
|
|
<?php
|
2015-12-05 06:50:32 +01:00
|
|
|
|
2016-08-23 23:47:40 +02:00
|
|
|
namespace Amp\Parallel\Sync;
|
2016-08-18 18:04:48 +02:00
|
|
|
|
2017-03-16 23:03:59 +01:00
|
|
|
use Amp\{ Coroutine, Promise };
|
2017-04-16 17:12:42 +02:00
|
|
|
use Amp\ByteStream\{ Parser, ReadableStream, StreamException, WritableStream };
|
2016-08-23 23:47:40 +02:00
|
|
|
use Amp\Parallel\{ ChannelException, SerializationException };
|
2015-12-05 06:50:32 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* An asynchronous channel for sending data between threads and processes.
|
|
|
|
*
|
|
|
|
* Supports full duplex read and write.
|
|
|
|
*/
|
2016-08-18 18:04:48 +02:00
|
|
|
class ChannelledStream implements Channel {
|
2015-12-06 07:32:06 +01:00
|
|
|
const HEADER_LENGTH = 5;
|
2015-12-05 06:50:32 +01:00
|
|
|
|
2017-03-24 01:25:35 +01:00
|
|
|
/** @var \Amp\ByteStream\ReadableStream */
|
2015-12-05 06:50:32 +01:00
|
|
|
private $read;
|
|
|
|
|
2017-03-24 01:25:35 +01:00
|
|
|
/** @var \Amp\ByteStream\WritableStream */
|
2015-12-05 06:50:32 +01:00
|
|
|
private $write;
|
|
|
|
|
2017-04-16 17:12:42 +02:00
|
|
|
/** @var \SplQueue */
|
|
|
|
private $received;
|
|
|
|
|
|
|
|
/** @var \Amp\ByteStream\Parser */
|
|
|
|
private $parser;
|
2015-12-05 06:50:32 +01:00
|
|
|
|
|
|
|
/**
|
2017-03-25 07:19:46 +01:00
|
|
|
* Creates a new channel from the given stream objects. Note that $read and $write can be the same object.
|
2015-12-05 06:50:32 +01:00
|
|
|
*
|
2017-03-24 01:25:35 +01:00
|
|
|
* @param \Amp\ByteStream\ReadableStream $read
|
2017-03-25 07:19:46 +01:00
|
|
|
* @param \Amp\ByteStream\WritableStream $write
|
2015-12-05 06:50:32 +01:00
|
|
|
*/
|
2017-03-25 07:19:46 +01:00
|
|
|
public function __construct(ReadableStream $read, WritableStream $write) {
|
2015-12-05 06:50:32 +01:00
|
|
|
$this->read = $read;
|
2017-03-25 07:19:46 +01:00
|
|
|
$this->write = $write;
|
2017-04-16 17:12:42 +02:00
|
|
|
$this->received = new \SplQueue;
|
|
|
|
$this->parser = new Parser(self::parser($this->received, static function ($errno, $errstr, $errfile, $errline) {
|
2016-09-02 01:10:52 +02:00
|
|
|
if ($errno & \error_reporting()) {
|
|
|
|
throw new ChannelException(\sprintf(
|
|
|
|
'Received corrupted data. Errno: %d; %s in file %s on line %d',
|
|
|
|
$errno,
|
|
|
|
$errstr,
|
|
|
|
$errfile,
|
|
|
|
$errline
|
|
|
|
));
|
|
|
|
}
|
2017-04-16 17:12:42 +02:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param \SplQueue $queue
|
|
|
|
* @param callable $errorHandler
|
|
|
|
*
|
|
|
|
* @return \Generator
|
|
|
|
*
|
|
|
|
* @throws \Amp\Parallel\ChannelException
|
|
|
|
* @throws \Amp\Parallel\SerializationException
|
|
|
|
*/
|
|
|
|
private static function parser(\SplQueue $queue, callable $errorHandler): \Generator {
|
|
|
|
while (true) {
|
|
|
|
$data = \unpack("Cprefix/Llength", yield self::HEADER_LENGTH);
|
|
|
|
|
|
|
|
if ($data["prefix"] !== 0) {
|
|
|
|
throw new ChannelException("Invalid header received");
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = yield $data["length"];
|
|
|
|
|
|
|
|
\set_error_handler($errorHandler);
|
|
|
|
|
|
|
|
// Attempt to unserialize the received data.
|
|
|
|
try {
|
|
|
|
$data = \unserialize($data);
|
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
throw new SerializationException("Exception thrown when unserializing data", $exception);
|
|
|
|
} finally {
|
|
|
|
\restore_error_handler();
|
|
|
|
}
|
|
|
|
|
|
|
|
$queue->push($data);
|
|
|
|
}
|
2015-12-05 06:50:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*/
|
2016-11-15 00:43:44 +01:00
|
|
|
public function send($data): Promise {
|
2016-08-18 18:04:48 +02:00
|
|
|
return new Coroutine($this->doSend($data));
|
|
|
|
}
|
|
|
|
|
2016-11-15 00:43:44 +01:00
|
|
|
private function doSend($data): \Generator {
|
2015-12-05 06:50:32 +01:00
|
|
|
// Serialize the data to send into the channel.
|
|
|
|
try {
|
2016-09-02 01:10:52 +02:00
|
|
|
$data = \serialize($data);
|
2016-01-23 07:00:56 +01:00
|
|
|
} catch (\Throwable $exception) {
|
2016-01-23 18:20:58 +01:00
|
|
|
throw new SerializationException(
|
2016-08-18 18:04:48 +02:00
|
|
|
"The given data cannot be sent because it is not serializable.", $exception
|
2015-12-05 06:50:32 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2016-09-02 01:10:52 +02:00
|
|
|
return yield $this->write->write(\pack("CL", 0, \strlen($data)) . $data);
|
2017-04-16 17:12:42 +02:00
|
|
|
} catch (StreamException $exception) {
|
2016-08-18 18:04:48 +02:00
|
|
|
throw new ChannelException("Sending on the channel failed. Did the context die?", $exception);
|
2015-12-05 06:50:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*/
|
2016-11-15 00:43:44 +01:00
|
|
|
public function receive(): Promise {
|
2016-08-18 18:04:48 +02:00
|
|
|
return new Coroutine($this->doReceive());
|
|
|
|
}
|
|
|
|
|
2016-11-15 00:43:44 +01:00
|
|
|
private function doReceive(): \Generator {
|
2017-04-16 17:12:42 +02:00
|
|
|
while ($this->received->isEmpty()) {
|
|
|
|
if (!yield $this->read->advance()) {
|
|
|
|
throw new ChannelException("The channel closed. Did the context die?");
|
2015-12-05 07:54:15 +01:00
|
|
|
}
|
2015-12-05 06:50:32 +01:00
|
|
|
|
2017-04-16 17:12:42 +02:00
|
|
|
try {
|
|
|
|
yield $this->parser->write($this->read->getChunk());
|
|
|
|
} catch (StreamException $exception) {
|
|
|
|
throw new ChannelException("Reading from the channel failed. Did the context die?", $exception);
|
|
|
|
}
|
2015-12-05 06:50:32 +01:00
|
|
|
}
|
|
|
|
|
2017-04-16 17:12:42 +02:00
|
|
|
return $this->received->shift();
|
2015-12-05 06:50:32 +01:00
|
|
|
}
|
|
|
|
}
|