/** * [COROUTINE] Write data to this stream. * * Execution of the current strand is suspended until the data is sent. * * Write operations must be exclusive. If concurrent writes are attempted a * StreamLockedException is thrown. * * @param string $buffer The data to write to the stream. * @param integer|null $length The maximum number of bytes to write. * * @return integer The number of bytes written. * @throws StreamClosedException if the stream is already closed. * @throws StreamLockedException if concurrent writes are unsupported. * @throws StreamWriteException if an error occurs while writing to the stream. */ public function write($buffer, $length = null) { if ($this->strand) { throw new StreamLockedException(); } elseif ($this->isClosed()) { throw new StreamClosedException(); } (yield Recoil::suspend(function ($strand) { $this->strand = $strand; $strand->kernel()->eventLoop()->addWriteStream($this->stream, function ($stream, $eventLoop) use($strand) { $eventLoop->removeWriteStream($this->stream); $this->strand->resumeWithValue(null); }); })); $this->strand = null; $exception = null; set_error_handler(function ($code, $message, $file, $line) use(&$exception) { $exception = new ErrorException($message, 0, $code, $file, $line); }); $bytesWritten = fwrite($this->stream, $buffer, $length ?: strlen($buffer)); restore_error_handler(); if (false === $bytesWritten) { throw new StreamWriteException($exception); } (yield Recoil::return_($bytesWritten)); // @codeCoverageIgnoreStart }
/** * [COROUTINE] Read data from the stream. * * Execution of the current strand is suspended until data is available or * the end of the data stream is reached. * * Read operations must be exclusive. If concurrent reads are attempted a * StreamLockedException is thrown. * * @param integer $length The maximum number of bytes to read. * * @return string The data read from the stream. * @throws StreamClosedException if the stream is already closed. * @throws StreamLockedException if concurrent reads are unsupported. * @throws StreamReadException if an error occurs while reading from the stream. */ public function read($length) { if ($this->strand) { throw new StreamLockedException(); } elseif ($this->isClosed()) { throw new StreamClosedException(); } (yield Recoil::suspend(function ($strand) { $this->strand = $strand; $strand->kernel()->eventLoop()->addReadStream($this->stream, function ($stream, $eventLoop) { $eventLoop->removeReadStream($stream); $this->strand->resumeWithValue(null); }); })); $this->strand = null; $exception = null; set_error_handler(function ($code, $message, $file, $line) use(&$exception) { $exception = new ErrorException($message, 0, $code, $file, $line); }); $buffer = fread($this->stream, $length); restore_error_handler(); if (is_resource($this->stream) && feof($this->stream)) { fclose($this->stream); } if (false === $buffer) { throw new StreamReadException($exception); } (yield Recoil::return_($buffer)); // @codeCoverageIgnoreStart }
/** * [COROUTINE] Write a value to this channel. * * Execution of the current strand is suspended until the value has been * consumed. * * If the channel is already closed, or is closed while a write operation is * pending a ChannelClosedException is thrown. * * @param mixed $value The value to write to the channel. * * @throws ChannelClosedException if the channel has been closed. */ public function write($value) { if ($this->isClosed()) { throw new ChannelClosedException(); } if ($this->readStrands->isEmpty()) { (yield Recoil::suspend([$this->writeStrands, 'push'])); } $readStrand = $this->readStrands->dequeue(); $readStrand->resumeWithValue($value); }
/** * Publish a message. * * @recoil-coroutine * * @param OutgoingMessage $message The message to publish. * * The returned promise is resolved when the publisher has sent the message * over the network. This does not necessarily mean that the message was * successfully routed to a queue or delivered to a consumer, or even that * it reached the broker. * * Additional messages may be published before the promise is resolved, in * which case they will be queued in memory. * * @throws ConnectionException The connection is closed. */ public function publish(OutgoingMessage $message) : Generator { if ($this->isClosed) { throw ChannelClosedException::channelClosed(); } // There is already a message being publish, suspend this strand // until we're ready to publish this message ... if ($this->currentMessage !== null) { (yield Recoil::suspend(function ($strand) { $this->strandQueue[$strand->id()] = $strand; }, function ($strand) { unset($this->strandQueue[$strand->id()]); })); } $this->currentMessage = $message; if ($this->channelManager === null) { $this->channelManager = (yield $this->connectionManager->acquireChannel()); } (yield $this->channelManager->send(BasicPublishFrame::create($this->exchangeName, $this->fixedRoutingKey ?: $message->routingKey(), $this->options->mandatory, $this->options->immediate))); $frame = new BasicHeaderFrame(); $frame->contentLength = $message->contentLength(); $message->properties()->to091HeaderFrame($frame); (yield $this->channelManager->send($frame)); if ($frame->contentLength > 0) { if ($message instanceof BufferedOutgoingMessage) { (yield $this->channelManager->sendContent($message->content())); } else { assert(false, 'not implemented'); // $stream = $this->currentMessage->contentStream(); // $stream->on('data', [$this, 'onStreamData']); // $stream->on('close', [$this, 'onStreamClose']); // $stream->on('error', [$this, 'onStreamError']); // $stream->resume(); } } $this->currentMessage = null; if (!empty($this->strandQueue)) { foreach ($this->strandQueue as $id => $strand) { unset($this->strandQueue[$id]); $strand->send(); break; } } elseif ($this->channel === null) { $this->channelManager->release(); $this->channelManager = null; } }
/** * [COROUTINE] Read data from the stream. * * Execution of the current strand is suspended until data is available or * the end of the data stream is reached. * * Read operations must be exclusive. If concurrent reads are attempted a * StreamLockedException is thrown. * * @param integer $length The maximum number of bytes to read. * * @return string The data read from the stream. * @throws StreamClosedException if the stream is already closed. * @throws StreamLockedException if concurrent reads are unsupported. * @throws StreamReadException if an error occurs while reading from the stream. */ public function read($length) { if ($this->strand) { throw new StreamLockedException(); } elseif ($this->isClosed()) { throw new StreamClosedException(); } if (!$this->buffer) { (yield Recoil::suspend(function ($strand) { $this->strand = $strand; $this->stream->resume(); })); $this->strand = null; } if (strlen($this->buffer) > $length) { $buffer = substr($this->buffer, 0, $length); $this->buffer = substr($this->buffer, $length); } else { $buffer = $this->buffer; $this->buffer = ''; } (yield Recoil::return_($buffer)); // @codeCoverageIgnoreStart }
/** * [COROUTINE] Close this stream. * * Closing a stream indicates that no more data will be written to the * stream. */ public function close() { if ($this->strand) { $this->strand->resumeWithException(new StreamClosedException()); $this->strand = null; $this->stream->close(); } else { (yield Recoil::suspend(function ($strand) { $this->stream->once('close', function () use($strand) { $strand->resumeWithValue(null); }); $this->stream->end(); })); } }
/** * Send a frame to the broker and wait for a response. * * @recoil-coroutine * * The frame's channel is automatically set to this channel ID. * * @param OutgoingFrame $frame The frame to send. * @param integer $method The method ID of the frame to wait for (*::METHOD_ID constant). * * @return IncomingFrame The method response. */ public function call(OutgoingFrame $frame, int $method) : Generator { if ($this->state !== ChannelState::OPEN) { throw ChannelException::notOpen($this->channelId); } $frame->frameChannelId = $this->channelId; (yield $this->transport->write($frame)); return (yield Recoil::suspend(function ($strand) use($method, $frame) { $id = $strand->id(); $this->callers[$method][$id] = $strand; $this->callersBySentMethod[$frame::METHOD_ID][$id] = $method; $this->callersByWaitMethod[$method][$id] = $frame::METHOD_ID; }, function ($strand) use($method, $frame) { $id = $strand->id(); unset($this->callers[$method][$id], $this->callersBySentMethod[$frame::METHOD_ID][$id], $this->callersByWaitMethod[$method][$id]); })); }