PHP asynchronous programming with Swoole: Part 1

This first part will provide some basics on working with Swoole and it is meant to work as an introduction to the code samples we will include in the second part.

Slowly but steadily, mostly in the last 5-6 years, PHP is making steps to the direction of asynchronous programming. Although ReactPHP was showing the way in the first years, the last 2-3 years Swoole has taken the lead with the documentation being its weakest point.

Fibers, a.k.a coroutines.

Let’s start with some introduction to the concept of fibers. I assume here that you are familiar with the concepts or “process” and “thread”. If not, it would be better if you would do a little research on these and then come back.

Fibers are well defined execution paths within the same process, like subroutines. Fibers are not an O.S construction like processes and threads. They belong to the user space and implement co-operative multitasking, rather than the pre-emptive multitasking used by the O.S kernel. So, since O.S is not aware of fibers’ existence, a fiber can not be forcefully pre-empted by the OS for the sake of another fiber. The kernel may interrupt the process for a while (and so the fiber) but, when the kernel returns the execution to that process, the same fiber will continue executing. A fiber must voluntarily yield its execution to allow another one to run. Fibers always start and stop/yield in predefined places. When the programmer decides to put a block of code in a fiber, this code also contains (at specific points) commands that yield the execution of this block. On one hand, programmers are guaranteed that their code will not be abruptly interrupted and its data structures accessed by another fiber. On the other hand, the burden of implementing multitasking falls to the developer. Anyway, fibers must play nice and yield now and then to allow concurrency – we can’t expect the O.S to do that for us.

Generally, a fiber belongs to a (parent) thread, like a thread belongs to a (parent) process. Fibers are a concurrency concept, but are not truly parallel. They are not meant to take advantage of multiple CPUs or cores but to better utilize the resources of a thread/process by moving various tasks into separate fibers (separate execution paths) and processing one task when another has to wait for I/O.

Just to remove any confusion, coroutines and fibers generally refer to the same thing. The former when a programming language construct and the latter when a system construct.

If you want a deeper understanding on how asynchronous programming is implemented in PHP, you can have a look in this older post, which contains a basic description of how event loops work in ReactPHP. To go even deeper, you need to study PHP socket programming (maybe, have a look at “The PHP Socket Programming Handbook” by Christoph Hochstrasser) and I/O event notification facilities provided by Linux (e.g epoll).

Swoole installation

Let’s see one way to install PHP 7.4 alongside the Swoole extension to Ubuntu.

apt-get update

apt-get install software-properties-common
apt-get install build-essential curl
add-apt-repository ppa:ondrej/php
apt-get update
apt -y install php7.4
apt -y install php7.4-dev
apt -y install php7.4-mbstring libcurl4-openssl-dev

// For Composer
apt-get install zip unzip php7.4-curl

// Install Composer following official instructions
https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx

// Update Composer
composer self-update

php -v

apt -y install php-pear
pecl install swoole

Enable Swoole PHP extension

// Locate php.ini file  (e.g /etc/php/7.4/cli/php.ini )
php -i | grep php.ini

Check Swoole extension configuration:

php --ri swoole

Check all running PHP processes

ps aux

Kill all PHP processes
(in case you want to modify php.ini)

kill $(ps aux | grep 'php' | awk '{print $2}')

Add Swoole support to your IDE

Swoole IDE Helper: https://github.com/wudi/swoole-ide-helper

Defining a coroutine context

The code samples of asynchronous calls that we will provide in the second part should be included in some coroutine context. Providing a coroutine context can be done in 2 main ways. Either by a stand-alone script (outside the context of a web server) that would contain a top-level coroutine wrapper:

// myscript.php

require_once 'vendor/autoload.php';

go(function () {
    // Your asynchronous code goes here
});

or through the request handling function of a Swoole web server:

// my_swoole_server.php

require_once 'vendor/autoload.php';

use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;

// Create the http server object
$http_server = new Swoole\Http\Server("127.0.0.1", 9503);

// Define the function that handles each request 
$onRequest = function (SwooleRequest $request, SwooleResponse $response) use ($http_server) {
    // Your asynchronous code goes here
};

// Configure your sevrer 
$http_server->set([
    'daemonize' => 1,
    'log_level' => 1,
    'log_file'  => '/var/log/swoole.log'
]);

// Register the event of request
$http_server->on('request', $onRequest);

// Start the server
$http_server->start();


Exceptions

The swoole doesn’t support the set_exception_handler function. If there is the logic of throwing exception in your code, it must add the try/catch in the very beginning of callback function.

Otherwise, Swoole will catch the exception, log it in the server log file and server execution will continue. For example, if it is a HTTP server, a 500 response will automatically returned by Swoole HTTP server.

Some resources

Swoole Chinese Wiki: https://wiki.swoole.com/wiki/index
(This is the original source of information. It is quite readable using google translate and contains some useful information that cannot be found in the english Wiki)

Swoole English Wiki: https://www.swoole.co.uk/

Swoole code: https://github.com/swoole/swoole-src

Swoole examples: https://github.com/deminy/swoole-by-examples