Async Expressive? Try Swoole!

When we were finalizing features for Expressive 3,
we had a number of users testing using asynchronous PHP web servers. As a
result, we made a number of changes in the last few iterations to ensure that
Expressive will work well under these paradigms.

Specifically, we made changes to how response prototypes are injected into
services.

Response prototypes?

What’s the problem?

In an async system, one advantage is that you can bootstrap the application
once, and then respond to requests until the server is shutdown.

However, this can become problematic with services that compose a response
prototype in order to produce a response (e.g., authentication middleware that
may need to produce an "unauthenticated" response; middleware that will produce
a "not found" response; middleware that will produce a "method not allowed"
response; etc.). We have standardized on providing response prototypes via
dependency injection, using a service named after the interface they implement:
PsrHttpMessageResponseInterface.

If a particular service accepts a response instance that’s injected during
initial service creation, that same instance will be used for any subsequent
requests that require it. And that’s where the issue comes in.

When running PHP under traditional conditions — php-fpm, the Apache SAPI,
etc. — all requests are isolated; the environment is both created and torn
down for each and every request. As such, passing an instance is perfectly safe;
there’s very little chance, if any, that any other service will be working with
the same instance.

With an async server, however, the same instance will be used on each and every
request. Generally, manipulations of PSR-7
message instances will create new instances, as the interfaces they implement
are specified as immutable. Unfortunately, due to technical limitations of the
PHP language, we were unable to make the body of response messages
immutable. This means that if one process writes to that body, then a
subsequent process — or even those executing in parallel! — will
receive the same changes. This can lead to, in the best case scenario,
duplicated content, and, in the worst, provide incorrect content or perform
information leaking!

To combat these situations, we modified the PsrHttpMessageResponseInterface
service we register with the dependency injection container: it now returns not
an instance of the interface, but a factory capable of producing an instance.
Services should compose this factory, and then call on it each time they need to
produce a response. This fixes the async problem, as it ensures a new instance
is used each time, instead of the same instance.

(Additionally, this change helps us prepare for the upcoming PSR-17, which
describes factories for PSR-7 artifacts; this solution will be compatible with
that specification once complete.)

Why async?

If asynchronous systems operate so differently, why bother?

There’s many reasons, but the one that generally gets the attention of
developers is performance.

We performed benchmarks of Expressive 2 and Expressive 3 under both Apache and
nginx, and found version 3 received around a 10% improvement.

We then tested using Swoole. Swoole is a PHP
extension that provides built-in async, multi-threaded input/output (I/O)
modules; it’s essentially the I/O aspects of node.js — which allow you to
create network servers and perform database and filesystem operations —
but for PHP.

A contributor, Westin Shafer, has written a module
for Expressive 3 that provides an application wrapper for Swoole

that is exposed via a CLI command. We ran our same benchmarks against this, and
the results were astonishing: applications ran consistently 4 times faster
under this asynchronous framework, and used fewer resources!

While performance is a great reason to explore async, there are other reasons as
well. For instance, if you do not need the return value of an I/O call (e.g., a
database transaction or cache operation), you can fire it off asynchronously,
and finish out the response without waiting for it. This can lead to reduced
waiting times for clients, further improving your performance.

We have had fun testing Swoole, and think it has tremendous possibilities when
it comes to creating microservices in PHP. The combination of Expressive and
Swoole is remarkably simple to setup and run, making it a killer combination!

Notes on setting up Swoole

The wshafer/swoole-expressive package requires a version 2 release of the
Swoole extension.

However, there’s a slight bug in the PECL installer whereby it picks up the
most recent release as the "latest", even if a version with greater stability
exists. As of the time of writing, version 1.10.2 of Swoole was released after
version 2.1.1, causing it to be installed instead of the more 2.X version.

You can force installation of a version by appending the version you want when
invoking the pecl command:

$ pecl install swoole-2.1.1

The version must be fully qualified for it to install correctly; no partials
(such as swoole-2 or swoole-2.1 will work.

Source: Zend feed