PHP in the Async-land

The World is Asynchronous
27
Jul

PHP in the Async-land

When working on this article, I was thinking about real-life examples to explain asynchronous processing from scratch. In the end, I understood that there are too many examples and I can’t pick the most relevant one. Seriously, you barely can imagine a process in nature that follows a sequence or an organised queue. And asynchronous doesn’t mean chaotic.

For example, a beehive is a single unit, living as a well-managed system, despite every bee acting independently, busy with its own task. A human body is another example, where everybody’s organs are acting with its own goal and process. However, everything is perfectly coordinated with repeating heartbeats, ticking constantly, and pushing the blood through all the system members.

In fact, it is much harder to imagine something really synchronous in nature. Can you imagine the bees, waiting in queue for their turn to enter the hive, to put some honey into it? Ridiculous!

‘Why,’ said the Dodo, ‘the best way to explain it is to do it.’ (And, as you might like to try the thing yourself, some winter day, I will tell you how the Dodo managed it.) – Lewis Carroll, Alice in Wonderland

Down the Hole

Diving deeper into the topic, PHP is a general-purpose scripting language, especially suited for web development. In contrast to, for example, Java, PHP has no built-in server. Well, actually, it has one, but it’s an experimental feature, not intended for production usage. If you want to execute a PHP application, you either execute it as a single script run via command-line interface (CLI) or run it as an ongoing server process, for example, using FPM (Fast Process Manager), which can manage child PHP processes and ensure that it will always be available (at least one available) to run your code.

In the case of CLI, everything is very easy – a single process is being executed to process a single request. PHP compiles code dynamically, on-demand. This means PHP will make a start-up, initialise the engine, and included modules. After it’s done, PHP will compile and execute your code. At the end of processing, it will shut down.

With FPM all will be the same, but the system will try to be prepared for the request coming from the server. FPM will make sure that the running PHP process already exists in the pool, ready to receive requests. Depending on the process management type – static, dynamic, or on-demand, it will define how many child processes have to be executed to process incoming requests from the webserver. However, each of these processes is executed to process a single request at once. So every other request is landed in the queue and is waiting for its turn at a mad tea party.

And it doesn’t mean that the working process is using any resources of the CPU at every moment of waiting time. It might be that some external request was sent, and the process is just waiting for the response, to proceed further with a running request. It would be cool if we could use the resources smarter and go on with another task during the waiting time, and continue the current one, when waiting time releases.

Have you guessed the riddle yet?

Don’t give it up! Let me guess, some of you were already facing similar situations, working with data import or export with an external system, and we’re thinking about how it would be to start a new PHP process or thread directly from your current run. There are some existing extensions available with PHP, i.e., pthreads or parallel – both, actually, require a build of PHP with Zend Thread Safety module enabled. (https://www.php.net/manual/en/intro.pthreads.php, https://www.php.net/manual/en/parallel.setup.php). I might write another article later on, digging deeper into multithreading. However, now we are looking for a clear, easy, and controlled way to run the code in a non-blocking manner, without actually needing to use multithreading, or think about multiprocessing.

And keep in mind, better performance and better end-user satisfaction are the main goals here, as we want to use our resources more effectively. Sometimes, we need to run PHP code asynchronously, even if it was not originally supposed to be launched in such a way.

Learn more about International PHP Conference 2021

First of all, we need to understand, what exactly does asynchronous processing mean? It’s a kind of design for computing tasks, executing them without any dependencies between each other. Each task is different and the sequence of execution doesn’t guarantee the same order of result delivery (Picture 1).

Picture 1

When we are running the webserver, we have the same behavior of serving web pages to the browser users. To make this happen like in the picture, we’d need to have at least four PHP processes executed by FPM. If we look in frames of a single PHP process, each task is queued and executed one after another (Picture 2).

Picture 2

But is it possible to have one single PHP process executing multiple tasks in parallel, like in Picture 1? If Task A makes some external request, for example, to the data store and waits some time for the result, we can leave the reference open and execute Task B during the waiting time. When task B has the same external request, a program can check if the result of the request in task A is already ready and return it to the user, then continue with task B.

The best way to explain it is to program it

Implementation in PHP might be tricky, but possible. For example, a request to the data store might be implemented not as a real database request, but placing the task into the queue, checking it further by given reference, if there is a result already available. Real data store requests might be done by some background worker or cron job (Picture 3).

Picture 3

We might need to update the user waiting for the result with some abstract knowledge of what to expect when the result is delivered. This can be done through the non-fulfilled handler of the object which the user will receive before the result is ready. It might help to start rendering on the front end already. This is a very common solution used on the front end. And this is when the concept of Promise would help us.

A popular example of promise in real life is ordering at a famous fast-food restaurant using a self-service terminal. You select your favorite hamburger and then systems give you a ticket with a number, which describes exactly what you will receive after your food is ready.

If you’re a Javascript developer, you must be familiar with this concept.

Basically, promise is a proxy for a result that might be still unknown, when the promise is created. It can normally have one of the following states: pending, which is an initial state, fulfilled, when an operation is completed successfully or rejected if the operation failed. If you are using Typescript, you can even provide an interface or type of the promise value when it would be fulfilled. But if you are using PHP, currently it’s not available out of the box.

In Listing 1, I programmed the concept of promise very basically. It implements an interface with 3 methods – to resolve, reject Promise, or to get result value, obviously, after it was fulfilled.

interface PromiseInterface
{
  public function resolve(mixed $result);
  public function reject();
  public function getResult(): mixed;
}

final class Promise implements PromiseInterface
{
  const STATE_PENDING = "pending";
  const STATE_FULFILLED = "fulfilled";
  const STATE_REJECTED = "rejected";

  public function construct(

  ) {
    $this->state = self::STATE_PENDING;
  }

  public function resolve(mixed $result): void
  {
    $this->state = self::STATE_FULFILLED;
    $this->result = $result;
  }

  public function reject(): void
  {
    $this->state = self::STATE_REJECTED;
    unset($this->result);
  }

  public function getResult(): mixed

Luckily, this trivial implementation can stay a prototype only forever, because the ReactPHP framework already provides a great library, implementing the common Javascript promises concept (https://github.com/reactphp/promise).

It’s a bit more complex and, additionally to basic promises, supporting promiser object Deferred, which represents a computation unit, creating a promise.

Additionally, the promiser object has common functions to resolve or reject the deferred promise. The promise object, besides, supports chained promises with resolution and rejection forwarding. The ReactPHP library provides good documentation and usage examples, so there’s no reason to go deeper now.

Curiouser and curiouser!

Let’s focus on the missing elements needed to implement the solution, described in Picture 3. Remembering that in this picture, it’s just a single PHP process running, the next challenge is to execute the second task B during the waiting time for a response from the data store. This is where we are ready to follow the next important concept of asynchronous execution – Event Loop.

Picture 4

This concept is well known in the NodeJS world – the event loop is one of the most important aspects to understand there. The event loop is continuously running, checking if there’s any function to execute. In the next Picture 4 event loop concept applied to the previously mentioned solution.

Learn more about International PHP Conference 2021

There is a task pool, which can handle functions and promises pool, handling references and corresponding promises to be resolved from the data store. The event loop is constantly running, taking tasks from the tasks pool and storing created promises. In the same loop, it is checking for results to resolve stored promises. If I needed to implement an event loop in PHP, I’d start with something simple as described in Listing 2:

class EventLoop
{
  public function construct(
  private iterable $tasksPool,
  ) {}


  public function addTask(callable $task): void
  {
    $this->tasksPool[] = $task;
  }

  public function start(): self
  {
    $this->tick();
    return $this->start();
  }
}

Thanks again to ReactPHP, such basic implementation is not needed, we don’t need to invent another bicycle. There’s a library for event loop in ReactPHP: https://github.com/reactphp/event-loop.

Same as in the example in Listing 2, event loop runs in a single thread, however, using ticks and timers to plan and execute all the tasks and events asynchronously. Same as in Javascript, ReactPHP implements a concurrency model based completely on the event loop. To use event loop in the application, a single instance should be created and used to add timers and ticks. At the end of the application code, the event loop should be set to run.

ReactPHP supports several loop implementations, some of them require additional PHP extensions to be installed. However, a stream_select() one is used as a fallback if there are no specific extensions available. Selection is defined in the React\EventLoop\Factory::create() method, which should be used to create an instance of the loop. It might be an issue using StreamSelectLoop in case of a really high load, however, to try the concept, it’s completely appropriate to choose.

Let’s look into the implementation of the concept, applied for the use case, on Listing 3.

interface DataStoreInterface {
  public function asyncQuery(string $needle): bool;
  public function checkResult(string $needle): mixed;

}

interface EndUserSocketInterface {
  public function deliverResult(mixed $result): void;
}

class App
{
  private LoopInterface $loop;

  public function construct(
  private array $searchTasks,
  private DataStoreInterface $dataStore,
  private EndUserSocketInterface $socket,
  ) {}

  public function invoke() {
    $this->loop = \React\EventLoop\Factory::create();
    $this->init();
    $this->loop->run();
  }

  $app = new App(['test1', 'test2'], $dataStore, $socket);
  $app();

As preconditions, there are test dummy interfaces DataStoreInterface, representing an example of the asynchronous data store and EndUserSocketInterface, responsible for delivery of the result to the end-user.

Implementation is done as a class App, which expects an array of search tasks, instances of data store and socket to be provided. It used the magic method invoke() to execute the loop run. Loop is created there by the Factory and is set up in the init() method, where asynchronous code is planned using the futureTick function from the loop.

Code inside will execute only when the loop is started by running $this->loop->run(). Future tick is an event that happens immediately. However it’s important to understand, according to the asynchronous nature of the loop, there’s no guarantee when exactly and in which sequence all the ticks would be executed.

In the anonymous callback function, which will be executed with a tick in the loop, the Deferred object is instantiated and the promise is configured with a callback onFulfilled. This callback function will be called when the promise is resolved. Here, it’s also possible to configure callbacks for onRejected, where error handling can be implemented, or onProgress to update progress tracking. Currently, we skip this to simplify the example. In the only feedback for onFulfilled event, result, resolved in the promise, is pushed to the end customer through the EndUserSocketInterface.

Explain Yourself!

To understand the example properly, let’s agree on how our dummy DataStore is working. It has two methods: asyncQuery() and checkResult(). When a query is sent to the data store, it doesn’t immediately provide a result. Instead, it registers the query, closes the connection, and executes the background process, caching the result for the query, which can be retrieved with another call of the method checkResult(). This is considered to be a completely custom dummy data store implementation, just to show a possible use case of applying the concept.

To fulfill the promise with the result, another event is set in the loop – a periodic timer. It will repeatedly run and execute checkResult function in the data store until the result is successfully returned. Timer is cancelled with $this->loop->cancelTimer($timer) to stop further continuous processing of the periodic event. When the result is ready, the promise is resolved with $deferred->resolve(), which executes the callback function, set up previously.

Like it’s said, the best way to explain it is to do it. Now what was supposed to be done is done! And I hope it helped to clarify the general approach of running PHP asynchronously.

There are several popular PHP frameworks and libraries currently available for asynchronous processing. Among them, together with ReactPHP (https://reactphp.org/), the most popular are Swoole (https://www.swoole.co.uk) which is delivered as PHP module, and Amphp (https://amphp.org) with wider usage of coroutines concept – a way of splitting an operation or a process into chunks with some execution in each chunk.

The most exciting thing is that a similar concept is about to be implemented in native PHP8.1, called Fiber (https://wiki.php.net/rfc/fibers). Fiber is like a coroutine – it is a final class that operates like a car – it starts, runs, suspends, and resumes when it’s required. PHP stays synchronous inside, so Fibers are running cooperatively.

However, it will be the first implementation of concurrent cooperative multitasking in PHP and I think this would be an awesome start of a completely new journey for PHP in the Async-world!

Stay tuned!

Behind the Tracks of IPC

PHP Core & Coding
Best practices & applications

General Web Development
Broader web development topics

DevOps & Continuous Delivery
Learn about DevOps and transform your development pipeline

Software Architecture
All about PHP frameworks, concepts & environments

Web Security
All about web security

Software Quality
More about software testing tools & strategies

Agile & Company Culture
Getting agile right is so important

Content Management Systems
Sessions on content management systems

#slideless (pure coding)
See how technology really works

PHP Frameworks
All about PHP Frameworks

Docker, Kubernetes, Cloud
Cloud-based & native apps