Пример #1
0
 /**
  * Connect to an AMQP broker.
  *
  * @recoil-coroutine
  *
  * @param ConnectionOptions $options,... Options that configure the connection.
  *
  * @return Connection          The AMQP connection.
  * @throws ConnectionException The connection could not be established.
  */
 public function connect(ConnectionOptions $options, ConnectionOptions ...$more) : Generator
 {
     $options = func_get_args();
     try {
         return (yield Recoil::any(...array_map([$this->connector, 'connect'], $options)));
     } catch (CompositeException $e) {
         throw ClusterConnectionException::couldNotConnect($options, $e->exceptions());
     }
 }
Пример #2
0
 /**
  * [COROUTINE] Close this stream.
  *
  * Closing a stream indicates that no more data will be read from the
  * stream.
  */
 public function close()
 {
     if ($this->strand) {
         $this->strand->resumeWithException(new StreamClosedException());
         $this->strand = null;
     }
     $this->stream->close();
     $this->buffer = '';
     (yield Recoil::noop());
 }
Пример #3
0
 /**
  * @recoil-coroutine
  */
 public function start() : Generator
 {
     // start a strand that deals with the main connection logic ...
     $strand = (yield Recoil::execute($this->readLoop()));
     // if heartbeats are enabled, start an additional strand that checks
     // the heartbeat state at the negotiated interval ...
     if ($this->tune->heartbeat !== HandshakeManager::HEARTBEAT_DISABLED) {
         (yield Recoil::link($strand, (yield Recoil::execute($this->heartbeatLoop()))));
     }
 }
Пример #4
0
 /**
  * [COROUTINE] Close this channel.
  *
  * Closing a channel indicates that no more values will be read from or
  * written to the channel. Any future read/write operations will fail.
  */
 public function close()
 {
     $this->closed = true;
     while (!$this->writeStrands->isEmpty()) {
         $this->writeStrands->pop()->resumeWithException(new ChannelClosedException());
     }
     while (!$this->readStrands->isEmpty()) {
         $this->readStrands->pop()->resumeWithException(new ChannelClosedException());
     }
     (yield Recoil::noop());
 }
Пример #5
0
 /**
  * [COROUTINE] Close this stream.
  *
  * Closing a stream indicates that no more data will be read from the
  * stream.
  */
 public function close()
 {
     if ($this->strand) {
         $this->strand->kernel()->eventLoop()->removeReadStream($this->stream);
         $this->strand->resumeWithException(new StreamClosedException());
         $this->strand = null;
     }
     if (is_resource($this->stream)) {
         fclose($this->stream);
     }
     (yield Recoil::noop());
 }
Пример #6
0
 /**
  * @recoil-coroutine
  */
 private function connectSocket(ConnectionOptions $options) : Generator
 {
     $errorCode = null;
     $errorMessage = null;
     $stream = @stream_socket_client(\sprintf('tcp://%s:%s', $options->host(), $options->port()), $errorCode, $errorMessage, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
     if ($stream === false) {
         throw SingleConnectionException::couldNotConnect($options, $errorCode . ': ' . $errorMessage);
     }
     // Wait for the socket to open or fail ...
     stream_set_blocking($stream, false);
     list(, $writable) = (yield Recoil::select([$stream], [$stream]));
     if (empty($writable)) {
         throw SingleConnectionException::couldNotConnect($options, 'connection refused');
     }
     return $stream;
 }
Пример #7
0
 /**
  * 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;
     }
 }
Пример #8
0
 /**
  * [COROUTINE] Read a value from this channel.
  *
  * Execution of the current strand is suspended until a value is available.
  *
  * If the channel is already closed, or is closed while a read operation is
  * pending a ChannelClosedException is thrown.
  *
  * Read operations must be exclusive only if the underlying stream requires
  * exclusive reads.
  *
  * @return mixed                  The value read from the channel.
  * @throws ChannelClosedException if the channel has been closed.
  * @throws ChannelLockedException if concurrent reads are unsupported.
  */
 public function read()
 {
     while (!$this->unserializer->hasValue()) {
         try {
             $buffer = (yield $this->stream->read($this->bufferSize));
         } catch (StreamClosedException $e) {
             throw new ChannelClosedException($e);
         } catch (StreamLockedException $e) {
             throw new ChannelLockedException($e);
         }
         $this->unserializer->feed($buffer);
         if ($this->stream->isClosed()) {
             $this->unserializer->finalize();
         }
     }
     (yield Recoil::return_($this->unserializer->unserialize()));
     // @codeCoverageIgnoreStart
 }
Пример #9
0
 /**
  * Adapt a value into a coroutine.
  *
  * @param StrandInterface $strand The currently executing strand.
  * @param mixed           $value  The value to adapt.
  *
  * @return CoroutineInterface
  * @throws InvalidArgumentException if now valid adaptation can be made.
  */
 public function adapt(StrandInterface $strand, $value)
 {
     while ($value instanceof CoroutineProviderInterface) {
         $value = $value->coroutine($strand);
     }
     if ($value instanceof CoroutineInterface) {
         return $value;
     } elseif ($value instanceof Generator) {
         return new GeneratorCoroutine($value);
     } elseif ($value instanceof PromiseInterface) {
         return new PromiseCoroutine($value);
     } elseif (is_array($value)) {
         return Recoil::all($value);
     } elseif (null === $value) {
         return Recoil::cooperate();
     }
     throw new InvalidArgumentException('Unable to adapt ' . Repr::repr($value) . ' into a coroutine.');
 }
Пример #10
0
 /**
  * [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();
         }));
     }
 }
Пример #11
0
 /**
  * Write a frame.
  *
  * @recoil-coroutine
  *
  * @param OutgoingFrame $frame The frame to write.
  *
  * @throws ChannelClosedException   The channel has been closed.
  * @throws InvalidArgumentException The type of $value is unsupported.
  */
 public function write($frame) : Generator
 {
     if ($this->stream === null) {
         throw ChannelClosedException::channelClosed();
     }
     $buffer = $this->serializer->serialize($frame);
     assert(Debug::dumpOutgoingFrame('#' . (int) $this->stream, $frame));
     assert(Debug::dumpOutgoingHex('#' . (int) $this->stream, $buffer));
     $this->hasSentFrame = true;
     (yield Recoil::write($this->stream, $buffer));
 }
Пример #12
0
 /**
  * @recoil-coroutine
  */
 public function onCloseByBroker(ChannelCloseFrame $frame) : Generator
 {
     $this->connectionManager->removeChannel($this->channelId);
     $exception = AmqpException::create($frame->replyText, $frame->replyCode);
     // The closure was triggered by a previously sent frame. Find the
     // offending strand and resume it with an exception specific to this
     // closure ...
     if ($frame->classId && $frame->methodId) {
         $sentMethod = $frame->classId << 16 | $frame->methodId;
         ($callers =& $this->callersBySentMethod[$sentMethod]) ?? null;
         if ($callers !== null) {
             foreach ($callers as $id => $waitMethod) {
                 $strand = $this->callers[$waitMethod][$id];
                 unset($this->callers[$waitMethod][$id], $this->callersBySentMethod[$sentMethod][$id], $this->callersByWaitMethod[$waitMethod][$id]);
                 (yield Recoil::throw($strand, $exception));
                 break;
             }
         }
     }
     // Acknowledge the closure ...
     $frame = new ChannelCloseOkFrame();
     $frame->frameChannelId = $this->channelId;
     (yield $this->transport->write($frame));
     // Procee to the regular on-close logic. To all remaining callers and
     // listeners this is an unexpected closure ...
     (yield $this->onClose(ChannelException::closedUnexpectedly($this->channelId, $exception)));
 }