1
0
mirror of https://github.com/danog/parallel.git synced 2024-12-02 17:52:14 +01:00
parallel/lib/Sync/ChannelledStream.php

136 lines
4.0 KiB
PHP
Raw Normal View History

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-05-18 09:51:31 +02:00
use Amp\ByteStream\InputStream;
use Amp\ByteStream\OutputStream;
use Amp\ByteStream\Parser;
use Amp\ByteStream\StreamException;
use Amp\Coroutine;
use Amp\Promise;
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 {
const HEADER_LENGTH = 5;
2015-12-05 06:50:32 +01:00
/** @var \Amp\ByteStream\InputStream */
2015-12-05 06:50:32 +01:00
private $read;
/** @var \Amp\ByteStream\OutputStream */
2015-12-05 06:50:32 +01:00
private $write;
/** @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
*
* @param \Amp\ByteStream\InputStream $read
* @param \Amp\ByteStream\OutputStream $write
2015-12-05 06:50:32 +01:00
*/
public function __construct(InputStream $read, OutputStream $write) {
2015-12-05 06:50:32 +01:00
$this->read = $read;
2017-03-25 07:19:46 +01:00
$this->write = $write;
$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
));
}
}));
}
/**
* @param \SplQueue $queue
* @param callable $errorHandler
*
* @return \Generator
*
* @throws \Amp\Parallel\Sync\ChannelException
* @throws \Amp\Parallel\Sync\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);
} 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 {
while ($this->received->isEmpty()) {
if (($chunk = yield $this->read->read()) === null) {
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
try {
yield $this->parser->write($chunk);
} catch (StreamException $exception) {
throw new ChannelException("Reading from the channel failed. Did the context die?", $exception);
}
2015-12-05 06:50:32 +01:00
}
return $this->received->shift();
2015-12-05 06:50:32 +01:00
}
}