Initial Commit
This commit is contained in:
211
dependencies/amphp/byte-stream/lib/ResourceInputStream.php
vendored
Normal file
211
dependencies/amphp/byte-stream/lib/ResourceInputStream.php
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\ByteStream;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Deferred;
|
||||
use WP_Ultimo\Dependencies\Amp\Loop;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
/**
|
||||
* Input stream abstraction for PHP's stream resources.
|
||||
*/
|
||||
final class ResourceInputStream implements InputStream
|
||||
{
|
||||
const DEFAULT_CHUNK_SIZE = 8192;
|
||||
/** @var resource|null */
|
||||
private $resource;
|
||||
/** @var string */
|
||||
private $watcher;
|
||||
/** @var Deferred|null */
|
||||
private $deferred;
|
||||
/** @var bool */
|
||||
private $readable = \true;
|
||||
/** @var int */
|
||||
private $chunkSize;
|
||||
/** @var bool */
|
||||
private $useSingleRead;
|
||||
/** @var callable */
|
||||
private $immediateCallable;
|
||||
/** @var string|null */
|
||||
private $immediateWatcher;
|
||||
/**
|
||||
* @param resource $stream Stream resource.
|
||||
* @param int $chunkSize Chunk size per read operation.
|
||||
*
|
||||
* @throws \Error If an invalid stream or parameter has been passed.
|
||||
*/
|
||||
public function __construct($stream, int $chunkSize = self::DEFAULT_CHUNK_SIZE)
|
||||
{
|
||||
if (!\is_resource($stream) || \get_resource_type($stream) !== 'stream') {
|
||||
throw new \Error("Expected a valid stream");
|
||||
}
|
||||
$meta = \stream_get_meta_data($stream);
|
||||
$useSingleRead = $meta["stream_type"] === "udp_socket" || $meta["stream_type"] === "STDIO";
|
||||
$this->useSingleRead = $useSingleRead;
|
||||
if (\strpos($meta["mode"], "r") === \false && \strpos($meta["mode"], "+") === \false) {
|
||||
throw new \Error("Expected a readable stream");
|
||||
}
|
||||
\stream_set_blocking($stream, \false);
|
||||
\stream_set_read_buffer($stream, 0);
|
||||
$this->resource =& $stream;
|
||||
$this->chunkSize =& $chunkSize;
|
||||
$deferred =& $this->deferred;
|
||||
$readable =& $this->readable;
|
||||
$this->watcher = Loop::onReadable($this->resource, static function ($watcher) use(&$deferred, &$readable, &$stream, &$chunkSize, $useSingleRead) {
|
||||
if ($useSingleRead) {
|
||||
$data = @\fread($stream, $chunkSize);
|
||||
} else {
|
||||
$data = @\stream_get_contents($stream, $chunkSize);
|
||||
}
|
||||
\assert($data !== \false, "Trying to read from a previously fclose()'d resource. Do NOT manually fclose() resources the loop still has a reference to.");
|
||||
// Error suppression, because pthreads does crazy things with resources,
|
||||
// which might be closed during two operations.
|
||||
// See https://github.com/amphp/byte-stream/issues/32
|
||||
if ($data === '' && @\feof($stream)) {
|
||||
$readable = \false;
|
||||
$stream = null;
|
||||
$data = null;
|
||||
// Stream closed, resolve read with null.
|
||||
Loop::cancel($watcher);
|
||||
} else {
|
||||
Loop::disable($watcher);
|
||||
}
|
||||
$temp = $deferred;
|
||||
$deferred = null;
|
||||
\assert($temp instanceof Deferred);
|
||||
$temp->resolve($data);
|
||||
});
|
||||
$this->immediateCallable = static function ($watcherId, $data) use(&$deferred) {
|
||||
$temp = $deferred;
|
||||
$deferred = null;
|
||||
\assert($temp instanceof Deferred);
|
||||
$temp->resolve($data);
|
||||
};
|
||||
Loop::disable($this->watcher);
|
||||
}
|
||||
/** @inheritdoc */
|
||||
public function read() : Promise
|
||||
{
|
||||
if ($this->deferred !== null) {
|
||||
throw new PendingReadError();
|
||||
}
|
||||
if (!$this->readable) {
|
||||
return new Success();
|
||||
// Resolve with null on closed stream.
|
||||
}
|
||||
\assert($this->resource !== null);
|
||||
// Attempt a direct read, because Windows suffers from slow I/O on STDIN otherwise.
|
||||
if ($this->useSingleRead) {
|
||||
$data = @\fread($this->resource, $this->chunkSize);
|
||||
} else {
|
||||
$data = @\stream_get_contents($this->resource, $this->chunkSize);
|
||||
}
|
||||
\assert($data !== \false, "Trying to read from a previously fclose()'d resource. Do NOT manually fclose() resources the loop still has a reference to.");
|
||||
if ($data === '') {
|
||||
// Error suppression, because pthreads does crazy things with resources,
|
||||
// which might be closed during two operations.
|
||||
// See https://github.com/amphp/byte-stream/issues/32
|
||||
if (@\feof($this->resource)) {
|
||||
$this->readable = \false;
|
||||
$this->resource = null;
|
||||
Loop::cancel($this->watcher);
|
||||
return new Success();
|
||||
// Stream closed, resolve read with null.
|
||||
}
|
||||
$this->deferred = new Deferred();
|
||||
Loop::enable($this->watcher);
|
||||
return $this->deferred->promise();
|
||||
}
|
||||
// Prevent an immediate read → write loop from blocking everything
|
||||
// See e.g. examples/benchmark-throughput.php
|
||||
$this->deferred = new Deferred();
|
||||
$this->immediateWatcher = Loop::defer($this->immediateCallable, $data);
|
||||
return $this->deferred->promise();
|
||||
}
|
||||
/**
|
||||
* Closes the stream forcefully. Multiple `close()` calls are ignored.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if (\is_resource($this->resource)) {
|
||||
// Error suppression, as resource might already be closed
|
||||
$meta = @\stream_get_meta_data($this->resource);
|
||||
if ($meta && \strpos($meta["mode"], "+") !== \false) {
|
||||
@\stream_socket_shutdown($this->resource, \STREAM_SHUT_RD);
|
||||
} else {
|
||||
/** @psalm-suppress InvalidPropertyAssignmentValue */
|
||||
@\fclose($this->resource);
|
||||
}
|
||||
}
|
||||
$this->free();
|
||||
}
|
||||
/**
|
||||
* Nulls reference to resource, marks stream unreadable, and succeeds any pending read with null.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function free()
|
||||
{
|
||||
$this->readable = \false;
|
||||
$this->resource = null;
|
||||
if ($this->deferred !== null) {
|
||||
$deferred = $this->deferred;
|
||||
$this->deferred = null;
|
||||
$deferred->resolve();
|
||||
}
|
||||
Loop::cancel($this->watcher);
|
||||
if ($this->immediateWatcher !== null) {
|
||||
Loop::cancel($this->immediateWatcher);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return resource|null The stream resource or null if the stream has closed.
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
return $this->resource;
|
||||
}
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setChunkSize(int $chunkSize)
|
||||
{
|
||||
$this->chunkSize = $chunkSize;
|
||||
}
|
||||
/**
|
||||
* References the read watcher, so the loop keeps running in case there's an active read.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @see Loop::reference()
|
||||
*/
|
||||
public function reference()
|
||||
{
|
||||
if (!$this->resource) {
|
||||
throw new \Error("Resource has already been freed");
|
||||
}
|
||||
Loop::reference($this->watcher);
|
||||
}
|
||||
/**
|
||||
* Unreferences the read watcher, so the loop doesn't keep running even if there are active reads.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @see Loop::unreference()
|
||||
*/
|
||||
public function unreference()
|
||||
{
|
||||
if (!$this->resource) {
|
||||
throw new \Error("Resource has already been freed");
|
||||
}
|
||||
Loop::unreference($this->watcher);
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->resource !== null) {
|
||||
$this->free();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user