amphp / amp

A non-blocking concurrency framework for PHP applications. 🐘

Home Page:https://amphp.org/amp

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

When to not to use `async`?

kafkiansky opened this issue · comments

Hello! I'm refactoring the client for RabbitMQ, moving it from amphp v2 to amphp v3, and am a bit confused about when not to use the async function. For example, we have a connection to rabbit over amphp/socket.

final class Connection
{
    public function open(): void
    {
        ....
        async(function (): void {
           while (null !== $chunk = $this->socket->read()) {
               $frame = $this->parser->parse($chunk);
               
               $future = $this->futures[$frame::class];
               $future->complete($frame);
           }
        });
    }
}

Its main task is to write frames to the socket and expect them from there. Once a complete frame has been collected, we find the Future waiting for that frame and complete it.

Here's an example of how Future is created that subscribes to frames:

/**
  * @template T of Protocol\AbstractFrame
  * @psalm-param class-string<T> $frame
  * @psalm-return Future<T>
 */
private function future(string $frame): Future
{
    /** @psalm-var DeferredFuture<T> $deferred */
    $deferred = new DeferredFuture();

    $this->connection->subscribe(
         $this->id,
         $frame,
         static function (Protocol\AbstractFrame $frame) use ($deferred) {
             /** @psalm-var T $frame */
             $deferred->complete($frame);

             return true;
         }
   );

    return $deferred->getFuture();
}

When we declare a queue, we write a special frame to the socket and expect a special frame from it.

    public function queueDeclare
    (
        string $queue = '',
        bool $passive = false,
        bool $durable = false,
        bool $exclusive = false,
        bool $autoDelete = false,
        bool $noWait = false,
        array $arguments = []
    ): ?Queue {
        $flags = [$passive, $durable, $exclusive, $autoDelete, $noWait];

        $this->connection->method($this->id, (new Buffer)
            ->appendUint16(50)
            ->appendUint16(10)
            ->appendInt16(0)
            ->appendString($queue)
            ->appendBits($flags)
            ->appendTable($arguments)
        );

        if ($noWait) {
            return null;
        }

        /** @var Protocol\QueueDeclareOkFrame $frame */
        $frame = $this->future(Protocol\QueueDeclareOkFrame::class)->await();

        return new Queue($frame->queue, $frame->messageCount, $frame->consumerCount);
    }

So the question is: should we wrap these two actions in the async function, given that the Connection::method method uses amphp/socket and our future method is also non-blocking or does that not make sense?

In other words, is it necessary to write code in this way?

public function queueDeclare
    (
        string $queue = '',
        bool $passive = false,
        bool $durable = false,
        bool $exclusive = false,
        bool $autoDelete = false,
        bool $noWait = false,
        array $arguments = []
    ): Future {
        return async(function () use (...): ?Queue {
            $flags = [$passive, $durable, $exclusive, $autoDelete, $noWait];

            $this->connection->method($this->id, (new Buffer)
                ->appendUint16(50)
                ->appendUint16(10)
                ->appendInt16(0)
                ->appendString($queue)
                ->appendBits($flags)
                ->appendTable($arguments)
            );

            if ($noWait) {
                return null;
            }

            /** @var Protocol\QueueDeclareOkFrame $frame */
            $frame = $this->future(Protocol\QueueDeclareOkFrame::class)->await();

            return new Queue($frame->queue, $frame->messageCount, $frame->consumerCount);
        });
    }