swoole-src Documentation

Coroutine-based concurrency library for PHP

18,908 stars C++ C++ 1 file · ~11,633 tokens #concurrency#coroutines#event#php#swoole#websocket
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy swoole-src on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name swoole-src -p 1521:1521 --restart always phpswoole/golang-h2demo
Configured Services & Ports
⚙️ swoole
image: phpswoole/php:${PHP_VERSION}
⚙️ mysql
image: mysql:latest
⚙️ pgsql
image: postgres:14
⚙️ oracle
image: gvenzl/oracle-free:slim
🔌 Ports: 1521:1521
⚙️ redis
image: redis:latest
⚙️ httpbin
image: arnaudlacour/httpbin
⚙️ tinyproxy
image: kalaksi/tinyproxy
⚙️ golang-h2demo
image: phpswoole/golang-h2demo
⚙️ socks5
image: serjs/go-socks5-proxy
🔌 Ports: 1080:1080
⚙️ ftp
image: teezily/ftpd
⚙️ firebirdsql
image: firebirdsql/firebird
🔌 Ports: 3050:3050
docker-compose.yml
yaml (docker-compose.yml)
version: '3.4'
services:
  swoole:
    container_name: "swoole"
    image: "phpswoole/php:${PHP_VERSION}"
    volumes:
      - "${SWOOLE_BUILD_DIR}:/swoole-src:rw"
    working_dir: /swoole-src
    ulimits:
      core: -1
    privileged: true
    depends_on:
      - mysql
      - redis
      - pgsql
      - oracle
    dns:
      - 8.8.8.8
      - 1.1.1.1
    environment:
      SWOOLE_BRANCH: "${SWOOLE_BRANCH}"
      TEST_SSH2_HOSTNAME: localhost
      TEST_SSH2_PORT: 22
      TEST_SSH2_USER: swoole_ssh2_test_user
      TEST_SSH2_PASS: swoole_ssh2_test_pass
      TEST_SSH2_AUTH: password
    command: tail -f /etc/group
  mysql:
    container_name: "mysql"
    image: "mysql:latest"
    volumes:
      - ./data/mysql:/var/lib/mysql:rw
      - ./data/run/mysqld:/var/run/mysqld:rw
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test
      MYSQL_USER: swoole
      MYSQL_PASSWORD: swoole
  pgsql:
    image: postgres:14
    container_name: "pgsql"
    environment:
      POSTGRES_USER: root
      POSTGRES_DB: test
      POSTGRES_PASSWORD: root
  oracle:
    image: gvenzl/oracle-free:slim
    container_name: "oracle"
    environment:
      ORACLE_PASSWORD: oracle
    ports:
      - "1521:1521"
  redis:
    container_name: "redis"
    image: "redis:latest"
    volumes:
      - ./data/redis:/var/lib/redis:rw
      - ./data/run/redis:/var/run/redis:rw
    sysctls:
        net.core.somaxconn: 65535
  httpbin:
    container_name: "httpbin"
    image: "arnaudlacour/httpbin"
  tinyproxy:
    container_name: "tinyproxy"
    image: "kalaksi/tinyproxy"
  golang-h2demo:
    container_name: "golang-h2demo"
    image: "phpswoole/golang-h2demo"
  socks5:
    container_name: "socks5"
    image: "serjs/go-socks5-proxy"
    ports:
      - "1080:1080"
  ftp:
    container_name: "ftp"
    image: "teezily/ftpd"
    environment:
      FTP_USER: admin
      FTP_PASSWORD: admin

  firebirdsql:
    container_name: "firebirdsql"
    image: "firebirdsql/firebird"
    environment:
      - FIREBIRD_ROOT_PASSWORD=root
      - FIREBIRD_USER=test
      - FIREBIRD_PASSWORD=test
      - FIREBIRD_DATABASE=test.fdb
      - FIREBIRD_DATABASE_DEFAULT_CHARSET=UTF8
    volumes:
      - ./data:/var/lib/firebird/data
    ports:
      - "3050:3050"

File: README.md

text
Swoole is an event-driven, asynchronous, coroutine-based concurrency library with high performance for PHP.











⚙️ Quick Start

Run Swoole program by Docker

bash
docker run --rm phpswoole/swoole "php --ri swoole"

For details on how to use it, see: How to Use This Image.

Documentation

https://wiki.swoole.com/

HTTP Service

php
$http = new Swoole\Http\Server('127.0.0.1', 9501);
$http->set(['hook_flags' => SWOOLE_HOOK_ALL]);

$http->on('request', function ($request, $response) {
    $result = [];
    Co::join([
        go(function () use (&$result) {
            $result['google'] = file_get_contents("https://www.google.com/");
        }),
        go(function () use (&$result) {
            $result['taobao'] = file_get_contents("https://www.taobao.com/");
        })
    ]);
    $response->end(json_encode($result));
});

$http->start();

Concurrency

php
Co\run(function() {
    Co\go(function() {
        while(1) {
            sleep(1);
            $fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30);
            echo fread($fp, 8192), PHP_EOL;
        }
    });

    Co\go(function() {
        $fp = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
        while(1) {
            $conn = stream_socket_accept($fp);
            fwrite($conn, 'The local time is ' . date('n/j/Y g:i a'));
        }
    });

    Co\go(function() {
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        while(true) {
            $redis->subscribe(['test'], function ($instance, $channelName, $message) {
                echo 'New redis message: '.$channelName, "==>", $message, PHP_EOL;
            });
        }
    });

    Co\go(function() {
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $count = 0;
        while(true) {
            sleep(2);
            $redis->publish('test','hello, world, count='.$count++);
        }
    });
});

Runtime Hook

Swoole hooks the blocking io function of PHP at the bottom layer and automatically converts it to a non-blocking function, so that these functions can be called concurrently in coroutines.

Supported extension/functions

  • ext-curl (Support symfony and guzzle)
  • ext-redis
  • ext-mysqli
  • ext-pdo_mysql
  • ext-pdo_pgsql
  • ext-pdo_sqlite
  • ext-pdo_oracle
  • ext-pdo_odbc
  • stream functions (e.g. stream_socket_client/stream_socket_server), Supports TCP/UDP/UDG/Unix/SSL/TLS/FileSystem API/Pipe
  • ext-sockets
  • ext-soap
  • sleep/usleep/time_sleep_until
  • proc_open
  • gethostbyname/shell_exec/exec
  • fread/fopen/fsockopen/fwrite/flock

🛠 Develop & Discussion

💎 Awesome Swoole

Project Awesome Swoole maintains a curated list of awesome things related to Swoole, including

  • Swoole-based frameworks and libraries.
  • Packages to integrate Swoole with popular PHP frameworks, including Laravel, Symfony, Slim, and Yii.
  • Books, videos, and other learning materials about Swoole.
  • Debugging, profiling, and testing tools for developing Swoole-based applications.
  • Coroutine-friendly packages and libraries.
  • Other Swoole related projects and resources.

✨ Event-based

The network layer in Swoole is event-based and takes full advantage of the underlying epoll/kqueue implementation, making it really easy to serve millions of requests.

Swoole 4.x uses a brand new engine kernel and now it has a full-time developer team, so we are entering an unprecedented period in PHP history which offers a unique possibility for rapid evolution in performance.

⚡ Coroutine

Swoole 4.x or later supports the built-in coroutine with high availability, and you can use fully synchronized code to implement asynchronous performance. PHP code without any additional keywords, the underlying automatic coroutine-scheduling.

Developers can understand coroutines as ultra-lightweight threads, and you can easily create thousands of coroutines in a single process.

MySQL

Concurrency 10K requests to read data from MySQL takes only 0.2s!

php
$s = microtime(true);
Co\run(function() {
    for ($c = 100; $c--;) {
        go(function () {
            $mysql = new Swoole\Coroutine\MySQL;
            $mysql->connect([
                'host' => '127.0.0.1',
                'user' => 'root',
                'password' => 'root',
                'database' => 'test'
            ]);
            $statement = $mysql->prepare('SELECT * FROM `user`');
            for ($n = 100; $n--;) {
                $result = $statement->execute();
                assert(count($result) > 0);
            }
        });
    }
});
echo 'use ' . (microtime(true) - $s) . ' s';

Mixed server

You can create multiple services on the single event loop: TCP, HTTP, Websocket and HTTP2, and easily handle thousands of requests.

php
function tcp_pack(string $data): string
{
    return pack('N', strlen($data)) . $data;
}
function tcp_unpack(string $data): string
{
    return substr($data, 4, unpack('N', substr($data, 0, 4))[1]);
}
$tcp_options = [
    'open_length_check' => true,
    'package_length_type' => 'N',
    'package_length_offset' => 0,
    'package_body_offset' => 4
];
php
$server = new Swoole\WebSocket\Server('127.0.0.1', 9501, SWOOLE_BASE);
$server->set(['open_http2_protocol' => true]);
// http && http2
$server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) {
    $response->end('Hello ' . $request->rawcontent());
});
// websocket
$server->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) {
    $server->push($frame->fd, 'Hello ' . $frame->data);
});
// tcp
$tcp_server = $server->listen('127.0.0.1', 9502, SWOOLE_TCP);
$tcp_server->set($tcp_options);
$tcp_server->on('receive', function (Swoole\Server $server, int $fd, int $reactor_id, string $data) {
    $server->send($fd, tcp_pack('Hello ' . tcp_unpack($data)));
});
$server->start();

Coroutine clients

Whether you DNS query or send requests or receive responses, all of these are scheduled by coroutine automatically.

php
go(function () {
    // http
    $http_client = new Swoole\Coroutine\Http\Client('127.0.0.1', 9501);
    assert($http_client->post('/', 'Swoole Http'));
    var_dump($http_client->body);
    // websocket
    $http_client->upgrade('/');
    $http_client->push('Swoole Websocket');
    var_dump($http_client->recv()->data);
});
go(function () {
    // http2
    $http2_client = new Swoole\Coroutine\Http2\Client('localhost', 9501);
    $http2_client->connect();
    $http2_request = new Swoole\Http2\Request;
    $http2_request->method = 'POST';
    $http2_request->data = 'Swoole Http2';
    $http2_client->send($http2_request);
    $http2_response = $http2_client->recv();
    var_dump($http2_response->data);
});
go(function () use ($tcp_options) {
    // tcp
    $tcp_client = new Swoole\Coroutine\Client(SWOOLE_TCP);
    $tcp_client->set($tcp_options);
    $tcp_client->connect('127.0.0.1', 9502);
    $tcp_client->send(tcp_pack('Swoole Tcp'));
    var_dump(tcp_unpack($tcp_client->recv()));
});

Channel

Channel is the only way for exchanging data between coroutines, the development combination of the Coroutine + Channel is the famous CSP programming model.

In Swoole development, Channel is usually used for implementing connection pool or scheduling coroutine concurrent.

The simplest example of a connection pool

In the following example, we have a thousand concurrently requests to redis. Normally, this has exceeded the maximum number of Redis connections setting and will throw a connection exception, but the connection pool based on Channel can perfectly schedule requests. We don't have to worry about connection overload.

php
class RedisPool
{
    /**@var \Swoole\Coroutine\Channel */
    protected $pool;

    /**
     * RedisPool constructor.
     * @param int $size max connections
     */
    public function __construct(int $size = 100)
    {
        $this->pool = new \Swoole\Coroutine\Channel($size);
        for ($i = 0; $i < $size; $i++) {
            $redis = new \Swoole\Coroutine\Redis();
            $res = $redis->connect('127.0.0.1', 6379);
            if ($res == false) {
                throw new \RuntimeException("failed to connect redis server.");
            } else {
                $this->put($redis);
            }
        }
    }

    public function get(): \Swoole\Coroutine\Redis
    {
        return $this->pool->pop();
    }

    public function put(\Swoole\Coroutine\Redis $redis)
    {
        $this->pool->push($redis);
    }

    public function close(): void
    {
        $this->pool->close();
        $this->pool = null;
    }
}

go(function () {
    $pool = new RedisPool();
    // max concurrency num is more than max connections
    // but it's no problem, channel will help you with scheduling
    for ($c = 0; $c < 1000; $c++) {
        go(function () use ($pool, $c) {
            for ($n = 0; $n < 100; $n++) {
                $redis = $pool->get();
                assert($redis->set("awesome-{$c}-{$n}", 'swoole'));
                assert($redis->get("awesome-{$c}-{$n}") === 'swoole');
                assert($redis->delete("awesome-{$c}-{$n}"));
                $pool->put($redis);
            }
        });
    }
});

Producer and consumers

Some Swoole's clients implement the defer mode for concurrency, but you can still implement it flexible with a combination of coroutines and channels.

php
go(function () {
    // User: I need you to bring me some information back.
    // Channel: OK! I will be responsible for scheduling.
    $channel = new Swoole\Coroutine\Channel;
    go(function () use ($channel) {
        // Coroutine A: Ok! I will show you the github addr info
        $addr_info = Co::getaddrinfo('github.com');
        $channel->push(['A', json_encode($addr_info, JSON_PRETTY_PRINT)]);
    });
    go(function () use ($channel) {
        // Coroutine B: Ok! I will show you what your code look like
        $mirror = Co::readFile(__FILE__);
        $channel->push(['B', $mirror]);
    });
    go(function () use ($channel) {
        // Coroutine C: Ok! I will show you the date
        $channel->push(['C', date(DATE_W3C)]);
    });
    for ($i = 3; $i--;) {
        list($id, $data) = $channel->pop();
        echo "From {$id}:\n {$data}\n";
    }
    // User: Amazing, I got every information at earliest time!
});

Timer

php
$id = Swoole\Timer::tick(100, function () {
    echo "⚙️ Do something...\n";
});
Swoole\Timer::after(500, function () use ($id) {
    Swoole\Timer::clear($id);
    echo "⏰ Done\n";
});
Swoole\Timer::after(1000, function () use ($id) {
    if (!Swoole\Timer::exists($id)) {
        echo "✅ All right!\n";
    }
});

The way of coroutine

php
go(function () {
    $i = 0;
    while (true) {
        Co::sleep(0.1);
        echo "📝 Do something...\n";
        if (++$i === 5) {
            echo "🛎 Done\n";
            break;
        }
    }
    echo "🎉 All right!\n";
});

🔥 Amazing runtime hooks

As of Swoole v4.1.0, we added the ability to transform synchronous PHP network libraries into co-routine libraries using a single line of code.

Simply call the Swoole\Runtime::enableCoroutine() method at the top of your script. In the sample below we connect to php-redis and concurrently read 10k requests in 0.1s:

php
Swoole\Runtime::enableCoroutine();
$s = microtime(true);
Co\run(function() {
    for ($c = 100; $c--;) {
        go(function () {
            ($redis = new Redis)->connect('127.0.0.1', 6379);
            for ($n = 100; $n--;) {
                assert($redis->get('awesome') === 'swoole');
            }
        });
    }
});
echo 'use ' . (microtime(true) - $s) . ' s';

By calling this method, the Swoole kernel replaces ZendVM stream function pointers. If you use php_stream based extensions, all socket operations can be dynamically converted to be asynchronous IO scheduled by coroutine at runtime!

How many things you can do in 1s?

Sleep 10K times, read, write, check and delete files 10K times, use PDO and MySQLi to communicate with the database 10K times, create a TCP server and multiple clients to communicate with each other 10K times, create a UDP server and multiple clients to communicate with each other 10K times... Everything works well in one process!

Just see what the Swoole brings, just imagine...

text
/* Detailed source-code truncated for AI context efficiency. */

⌛️ Installation

As with any open source project, Swoole always provides the most reliable stability and the most powerful features in the latest released version. Please ensure as much as possible that you are using the latest version.

Compiling requirements

  • Linux, OS X or Cygwin, WSL
  • PHP 8.2.0 or later (The higher the version, the better the performance.)
  • GCC 4.8 or later

1. Install via PECL (beginners)

shell
pecl install swoole

Please download the source packages from Releases or clone a specific version. Don't use master branch as it may be in development.

To clone the source code from git specify a tag:

shell
git clone --branch v6.0.0 --single-branch https://github.com/swoole/swoole-src.git && \
cd swoole-src

Compile and install at the source folder:

shell
phpize && \
./configure && \
make && make install

Enable extension in PHP

After compiling and installing to the system successfully, you have to add a new line extension=swoole.so to php.ini to enable Swoole extension.

Extra compiler configurations

for example: ./configure --enable-openssl --enable-sockets

  • --enable-openssl or --with-openssl-dir=DIR
  • --enable-sockets
  • --enable-mysqlnd (need mysqlnd, it just for supporting $mysql->escape method)
  • --enable-swoole-curl

Upgrade

⚠️ If you upgrade from source, don't forget to make clean before you upgrade your swoole

  1. pecl upgrade swoole
  2. cd swoole-src && git pull && make clean && make && sudo make install
  3. if you change your PHP version, please re-run phpize clean && phpize then try to compile

Major change since version 4.3.0

Async clients and API are moved to a separate PHP extension swoole_async since version 4.3.0, install swoole_async:

shell
git clone https://github.com/swoole/ext-async.git
cd ext-async
phpize
./configure
make -j 4
sudo make install

Enable it by adding a new line extension=swoole_async.so to php.ini.

🍭 Benchmark

  • On the open source Techempower Web Framework benchmarks Swoole used MySQL database benchmark to rank first, and all performance tests ranked in the first echelon.
  • You can just run Benchmark Script to quickly test the maximum QPS of Swoole-HTTP-Server on your machine.

🔰️ Security issues

Security issues should be reported privately, via email, to the Swoole develop team [email protected]. You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message.

🖊️ Contribution

Your contribution to Swoole development is very welcome!

You may contribute in the following ways:

❤️ Contributors

This project exists thanks to all the people who contribute. [Contributors].

🎙️ Official Evangelist

Demin has been playing with PHP since 2000, focusing on building high-performance, secure web services. He is an occasional conference speaker on PHP and Swoole, and has been working for companies in the states like eBay, Visa and Glu Mobile for years. You may find Demin on Twitter or GitHub.

📃 License

Apache License Version 2.0 see http://www.apache.org/licenses/LICENSE-2.0.html


File: docs/CODE-STYLE.md

Code Style

  • Google C++ Style
  • Indent adjusted to 4 characters
  • Line width adjusted to 120 characters

File: docs/ISSUE.md

Bug reports

Instruction

If you think you have found a bug in Swoole, please report it.
The Swoole developers probably don't know about it,
and unless you report it, chances are it won't be fixed.
You can report bugs at https://github.com/swoole/swoole-src/issues.
Please do not send bug reports in the mailing list or personal letters.
The issue page is also suitable to submit feature requests.

Please read the How to report a bug document before submitting any bug reports.

New issue

First, while creating an issue, the system will give the following template:

markdown
Please answer these questions before submitting your issue. Thanks!
1. What did you do? If possible, provide a simple script for reproducing the error.
2. What did you expect to see?
3. What did you see instead?
4. What version of Swoole are you using (`php --ri swoole`)?
5. What is your machine environment used (including the version of kernel & php & gcc)?

The most important thing is to provide a simple script for reproducing the error, otherwise, you must provide as much information as possible.

In addition to using gdb analysis, you can use the valgrind tool to check if the program is working properly.

shell
USE_ZEND_ALLOC=0 valgrind --log-file=/tmp/valgrind.log php your_file.php
  • After the program is executed to the wrong location, ctrl+c is interrupted, and upload the /tmp/valgrind.log file.

Address Sanitizer

In certain cases, crash issues only occur in complex production environments, not consistently reproducible, and with a very low probability of occurrence. The use of Valgrind significantly impacts performance, whereas Address Sanitizer allows for memory detection without affecting performance.

We have created a debug version of Swoole, which can be compiled with the --enable-debug and --enable-address-sanitizer parameters to enable Address Sanitizer. The usage is as follows:

shell
docker pull phpswoole/swoole:dev-debug
docker run -it --rm phpswoole/swoole:dev-debug /bin/bash
php your_file.php

To use ASAN, be sure to disable Address Space Layout Randomization (ASLR):

shell
echo 0 > /proc/sys/kernel/randomize_va_space

This docker image can be used directly in a production environment, and Address Sanitizer will print error messages and call stack information when memory errors occur. This information can assist developers in quickly pinpointing issues.

CoreDump

Besides, In a special case, you can use debugging tools to help developers locate problems

shell
WARNING	Worker::report_error(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=11

When a segmentation error occurs with Swoole, You can use the gdb tool and use bt command.

Using gdb to track the core file need to add the --enable-debug parameter when compiling swoole.

Enable core dump

shell
ulimit -c unlimited

Use gdb to view the core dump information. The core file is usually in the current directory. If the operating system does the processing, put the core dump file in another directory, please replace it with the corresponding path.

text
gdb php core
gdb php /tmp/core.4596

Enter bt under gdb to view the call stack information.

text
(gdb) bt

Use the f command in gdb to view the code segment corresponding to the ID.

text
(gdb)f 1
(gdb)f 0

If there is no function call stack information, it may be that the compiler has removed the debug information. Please manually modify the Makefile file in the swoole source directory and modify CFLAGS to

shell
CFLAGS = -Wall -pthread -g -O0

File: docs/sponsors.md


[成为 Swoole 的赞助者](#title1)


Swoole 是采用 Apache 许可的开源项目,是完全免费的。

维护这样一个庞大的生态系统和为项目开发新功能所需的巨大努力,只有在我们的赞助者慷慨的财务支持下才得以持续。
捐赠募集到的资金将全部用于开源项目的维护和发展。包括但不限于:

  • 向核心贡献者支付报酬
  • 服务器租赁费用
  • 网站和文档的维护
  • 举办线下活动
  • 为社区活跃者提供奖励

您可以扫码添加微信客服或通过邮件 [email protected] 与我们联系,沟通赞助事宜


[赞助渠道](#title2)


来自世界各地的企业可以通过赞助来支持 Swoole 项目的开发。
根据赞助商赞助金额的不同分为顶级赞助商、金牌赞助商,在获得捐赠的第一年内我们将为赞助商做以下品牌展示:

顶级赞助商

  • 官网、文档、问答社区首屏顶部显著位置展示
  • GitHub 仓库首页顶部赞助商标识
  • 官方微信公众号、QQ 群、微博、知乎等官方社交平台赞助商宣传内容推送

金牌赞助商

  • 官网、文档、问答社区首屏侧边位置展示

顶级赞助商

text
[](https://crmeb.com/)

金牌赞助商

text
[](https://www.apipost.cn/)


[](https://cloud.tencent.com/)


[](https://www.w7.cc/)


[](https://www.zentao.net/)


[](https://zh.51talk.com/)


[](https://www.easycorp.cn/)


[](https://www.liuxingedu.com/)


[](https://guojiang.club/)

银牌赞助商

text
[](https://shopxo.net/)


[](https://segmentfault.com/)



[](https://codecasts.com/)

File: docs/SUPPORTED.md

Supported Versions

Branch PHP Version Initialization Active Support Until Security Support Until
v6.1.x 8.1 - 8.4 2025-10-24 2026-10-24 2027-04-24
v6.2.x 8.2 - 8.5 2026-04-07 2027-04-07 2027-10-07
v6.3.x (unreleased) 8.2 - 8.5 Unreleased TBD TBD
  • Active support: A release that is being actively supported. Reported bugs and security issues are fixed and regular point releases are made.
  • Security fixes only: A release that is supported for critical security issues only. Releases are only made on an as-needed basis.
  • Standard support period: A non-LTS branch receives one year of active support followed by six months of security-only support, for a total of 1.5 years.
  • LTS support period: A branch explicitly designated as LTS receives a total of three years of support. The active and security-only dates are listed when the LTS release is announced.

PHP Version Support

  1. Each branch (MINOR version) supports a fixed range of PHP versions. The RELEASE VERSIONS for that branch will not increase support for higher PHP versions.
  2. The upper limit is four PHP versions; any additional versions will not be supported. For example, version 6.0 only supports PHP 8.1 to 8.4.
  3. No support for any DEV or RC stage of PHP

The pace of PHP version updates is rapid, with each version introducing numerous underlying changes. The developers of Swoole have had to invest significant time and effort to support new releases, and we lack sufficient resources to keep up with PHP updates. Therefore, there will be a delay of one MINOR version before supporting new PHP versions.

Unsupported Branches

These releases that are no longer supported. Users of this release should upgrade as soon as possible, as they may be exposed to unpatched security vulnerabilities.

Branch PHP Version Duration
1.x.x 5.4 - 7.2 2012-07-01 ~ 2018-05-14
2.x.x 7.0 - 7.3 2016-12-30 ~ 2018-05-23
4.0.x ~ 4.3.x 7.0 - 7.4 2018-06-14 ~ 2019-12-31
4.4.x 7.1 - 7.4 2019-04-15 ~ 2022-07-31
4.5.x,4.6.x, 4.7.x 7.1 - 7.4 2019-12-20 ~ 2021-12-31
4.8.x 7.3 - 8.2 2021-10-14 ~ 2024-06-30
5.0.x 7.4 - 8.3 2022-01-20 ~ 2023-07-20
5.1.x 8.0 - 8.3 2023-11-29 ~ 2025-04-29
6.0.x 8.1 - 8.4 2024-12-31 ~ 2026-06-30

File: docs/TESTS.md

Swoole Tests

Core Tests

Used to test the core code in the src/ directory, only as a C++ library, not related to php.
The core tests depends on the googletest framework, and googletest needs to be installed.
The core test cases must be written with C++.

GCC/G++ version 8.0 or higher is required, with full support for C++17.

**Build googletest**

Since swoole core unit testing rely on googletest, we need compile googletest at first.

shell
wget https://github.com/google/googletest/releases/download/v1.15.2/googletest-1.15.2.tar.gz
tar zxf googletest-1.15.2.tar.gz
cd googletest-1.15.2
mkdir ./build && cd ./build
cmake ..
make -j
sudo make install

Build libswoole.so

shell
cd swoole-src
cmake .
make -j$(nproc) lib-swoole

Build core-tests

shell
make -j$(nproc) core-tests

Run core-tests

shell
# run all dependency services
cd core-tests
docker compose up -d
# run all tests
./bin/core-tests
# run some test cases
./bin/core-tests --gtest_filter=server.*
# list all tests
./bin/core_tests --gtest_list_tests

PHP Tests

Used to test the php extension code in the ext-src/ directory. The swoole php test depends on php environment.
The php-dev related components must be installed.

The php test cases must be written with PHP.

Build ext-swoole

shell
cd swoole-src
phpize
./configure ${options}
make -j$(nproc)
make install

Need to configure php.ini, add extension=swoole.so to enable ext-swoole.

Run tests

shell
./scripts/route.sh

The automated test scripts in this directory can not only run on Github Action CI. Powered by docker container technology, it can run on any systems. You only need to run the route.sh script to create containers of multiple PHP environments then it will run Swoole's build tests and unit tests on multiple systems automatically.

With special branch

shell
SWOOLE_BRANCH=alpine ./scripts/route.sh

Enter the container

You can cancel the unit test by CTRL+C

shell
docker exec -it -e LINES=$(tput lines) -e COLUMNS=$(tput cols) swoole /bin/bash

File: docs/WORKFLOW-PARAMETERS.md

Workflow Parameters

Adding parameters in the Git commit log can control the workflow.

--filter

This parameter specifies which workflows to run, instead of running all of them.
The command format is: --filter=[flow1][flow2][flow...] ,
and it supports setting multiple workflows, with names matching the filename of the yml file.

--valgrind

Setting this parameter will cause the test program to be run with valgrind.

shell
git commit -m "commit message --filter=[core][unit] --valgrind"

--asan

Setting this parameter will cause the test program to be run with ASAN.


File: thirdparty/hiredis/README.md

This Readme reflects the latest changed in the master branch. See v0.13.3 for the Readme and documentation for the latest release.

HIREDIS

Hiredis is a minimalistic C client library for the Redis database.

It is minimalistic because it just adds minimal support for the protocol, but
at the same time it uses a high level printf-alike API in order to make it
much higher level than otherwise suggested by its minimal code base and the
lack of explicit bindings for every Redis command.

Apart from supporting sending commands and receiving replies, it comes with
a reply parser that is decoupled from the I/O layer. It
is a stream parser designed for easy reusability, which can for instance be used
in higher level language bindings for efficient reply parsing.

Hiredis only supports the binary-safe Redis protocol, so you can use it with any
Redis version >= 1.2.0.

The library comes with multiple APIs. There is the
synchronous API, the asynchronous API and the reply parsing API.

Upgrading to `1.0.0`

Version 1.0.0 marks a stable release of hiredis.
It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory.
It also bundles the updated sds library, to sync up with upstream and Redis.
For most applications a recompile against the new hiredis should be enough.
For code changes see the Changelog.

Upgrading from `<0.9.0`

Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
code using hiredis should not be a big pain. The key thing to keep in mind when
upgrading is that hiredis >= 0.9.0 uses a redisContext* to keep state, in contrast to
the stateless 0.0.1 that only has a file descriptor to work with.

Synchronous API

To consume the synchronous API, there are only a few function calls that need to be introduced:

c
redisContext *redisConnect(const char *ip, int port);
void *redisCommand(redisContext *c, const char *format, ...);
void freeReplyObject(void *reply);

Connecting

The function redisConnect is used to create a so-called redisContext. The
context is where Hiredis holds state for a connection. The redisContext
struct has an integer err field that is non-zero when the connection is in
an error state. The field errstr will contain a string with a description of
the error. More information on errors can be found in the Errors section.
After trying to connect to Redis using redisConnect you should
check the err field to see if establishing the connection was successful:

c
redisContext *c = redisConnect("127.0.0.1", 6379);
if (c == NULL || c->err) {
    if (c) {
        printf("Error: %s\n", c->errstr);
        // handle error
    } else {
        printf("Can't allocate redis context\n");
    }
}

Note: A redisContext is not thread-safe.

Sending commands

There are several ways to issue commands to Redis. The first that will be introduced is
redisCommand. This function takes a format similar to printf. In the simplest form,
it is used like this:

c
reply = redisCommand(context, "SET foo bar");

The specifier %s interpolates a string in the command, and uses strlen to
determine the length of the string:

c
reply = redisCommand(context, "SET foo %s", value);

When you need to pass binary safe strings in a command, the %b specifier can be
used. Together with a pointer to the string, it requires a size_t length argument
of the string:

c
reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);

Internally, Hiredis splits the command in different arguments and will
convert it to the protocol used to communicate with Redis.
One or more spaces separates arguments, so you can use the specifiers
anywhere in an argument:

c
reply = redisCommand(context, "SET key:%s %s", myid, value);

Using replies

The return value of redisCommand holds a reply when the command was
successfully executed. When an error occurs, the return value is NULL and
the err field in the context will be set (see section on Errors).
Once an error is returned the context cannot be reused and you should set up
a new connection.

The standard replies that redisCommand are of the type redisReply. The
type field in the redisReply should be used to test what kind of reply
was received:

  • REDIS_REPLY_STATUS:

    • The command replied with a status reply. The status string can be accessed using reply->str.
      The length of this string can be accessed using reply->len.
  • REDIS_REPLY_ERROR:

    • The command replied with an error. The error string can be accessed identical to REDIS_REPLY_STATUS.
  • REDIS_REPLY_INTEGER:

    • The command replied with an integer. The integer value can be accessed using the
      reply->integer field of type long long.
  • REDIS_REPLY_NIL:

    • The command replied with a nil object. There is no data to access.
  • REDIS_REPLY_STRING:

    • A bulk (string) reply. The value of the reply can be accessed using reply->str.
      The length of this string can be accessed using reply->len.
  • REDIS_REPLY_ARRAY:

    • A multi bulk reply. The number of elements in the multi bulk reply is stored in
      reply->elements. Every element in the multi bulk reply is a redisReply object as well
      and can be accessed via reply->element[..index..].
      Redis may reply with nested arrays but this is fully supported.

Replies should be freed using the freeReplyObject() function.
Note that this function will take care of freeing sub-reply objects
contained in arrays and nested arrays, so there is no need for the user to
free the sub replies (it is actually harmful and will corrupt the memory).

Important: the current version of hiredis (0.10.0) frees replies when the
asynchronous API is used. This means you should not call freeReplyObject when
you use this API. The reply is cleaned up by hiredis after the callback
returns. This behavior will probably change in future releases, so make sure to
keep an eye on the changelog when upgrading (see issue #39).

Cleaning up

To disconnect and free the context the following function can be used:

c
void redisFree(redisContext *c);

This function immediately closes the socket and then frees the allocations done in
creating the context.

Sending commands (cont'd)

Together with redisCommand, the function redisCommandArgv can be used to issue commands.
It has the following prototype:

c
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);

It takes the number of arguments argc, an array of strings argv and the lengths of the
arguments argvlen. For convenience, argvlen may be set to NULL and the function will
use strlen(3) on every argument to determine its length. Obviously, when any of the arguments
need to be binary safe, the entire array of lengths argvlen should be provided.

The return value has the same semantic as redisCommand.

Pipelining

To explain how Hiredis supports pipelining in a blocking connection, there needs to be
understanding of the internal execution flow.

When any of the functions in the redisCommand family is called, Hiredis first formats the
command according to the Redis protocol. The formatted command is then put in the output buffer
of the context. This output buffer is dynamic, so it can hold any number of commands.
After the command is put in the output buffer, redisGetReply is called. This function has the
following two execution paths:

  1. The input buffer is non-empty:
    • Try to parse a single reply from the input buffer and return it
    • If no reply could be parsed, continue at 2
  2. The input buffer is empty:
    • Write the entire output buffer to the socket
    • Read from the socket until a single reply could be parsed

The function redisGetReply is exported as part of the Hiredis API and can be used when a reply
is expected on the socket. To pipeline commands, the only things that needs to be done is
filling up the output buffer. For this cause, two commands can be used that are identical
to the redisCommand family, apart from not returning a reply:

c
void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);

After calling either function one or more times, redisGetReply can be used to receive the
subsequent replies. The return value for this function is either REDIS_OK or REDIS_ERR, where
the latter means an error occurred while reading a reply. Just as with the other commands,
the err field in the context can be used to find out what the cause of this error is.

The following examples shows a simple pipeline (resulting in only a single call to write(2) and
a single call to read(2)):

c
redisReply *reply;
redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo");
redisGetReply(context,&reply); // reply for SET
freeReplyObject(reply);
redisGetReply(context,&reply); // reply for GET
freeReplyObject(reply);

This API can also be used to implement a blocking subscriber:

c
reply = redisCommand(context,"SUBSCRIBE foo");
freeReplyObject(reply);
while(redisGetReply(context,&reply) == REDIS_OK) {
    // consume message
    freeReplyObject(reply);
}

Errors

When a function call is not successful, depending on the function either NULL or REDIS_ERR is
returned. The err field inside the context will be non-zero and set to one of the
following constants:

  • REDIS_ERR_IO:
    There was an I/O error while creating the connection, trying to write
    to the socket or read from the socket. If you included errno.h in your
    application, you can use the global errno variable to find out what is
    wrong.

  • REDIS_ERR_EOF:
    The server closed the connection which resulted in an empty read.

  • REDIS_ERR_PROTOCOL:
    There was an error while parsing the protocol.

  • REDIS_ERR_OTHER:
    Any other error. Currently, it is only used when a specified hostname to connect
    to cannot be resolved.

In every case, the errstr field in the context will be set to hold a string representation
of the error.

Asynchronous API

Hiredis comes with an asynchronous API that works easily with any event library.
Examples are bundled that show using Hiredis with libev
and libevent.

Connecting

The function redisAsyncConnect can be used to establish a non-blocking connection to
Redis. It returns a pointer to the newly created redisAsyncContext struct. The err field
should be checked after creation to see if there were errors creating the connection.
Because the connection that will be created is non-blocking, the kernel is not able to
instantly return if the specified host and port is able to accept a connection.

Note: A redisAsyncContext is not thread-safe.

c
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
    printf("Error: %s\n", c->errstr);
    // handle error
}

The asynchronous context can hold a disconnect callback function that is called when the
connection is disconnected (either because of an error or per user request). This function should
have the following prototype:

c
void(const redisAsyncContext *c, int status);

On a disconnect, the status argument is set to REDIS_OK when disconnection was initiated by the
user, or REDIS_ERR when the disconnection was caused by an error. When it is REDIS_ERR, the err
field in the context can be accessed to find out the cause of the error.

The context object is always freed after the disconnect callback fired. When a reconnect is needed,
the disconnect callback is a good point to do so.

Setting the disconnect callback can only be done once per context. For subsequent calls it will
return REDIS_ERR. The function to set the disconnect callback has the following prototype:

c
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);

Sending commands and their callbacks

In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
Therefore, unlike the synchronous API, there is only a single way to send commands.
Because commands are sent to Redis asynchronously, issuing a command requires a callback function
that is called when the reply is received. Reply callbacks should have the following prototype:

c
void(redisAsyncContext *c, void *reply, void *privdata);

The privdata argument can be used to curry arbitrary data to the callback from the point where
the command is initially queued for execution.

The functions that can be used to issue commands in an asynchronous context are:

c
int redisAsyncCommand(
  redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
  const char *format, ...);
int redisAsyncCommandArgv(
  redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
  int argc, const char **argv, const size_t *argvlen);

Both functions work like their blocking counterparts. The return value is REDIS_OK when the command
was successfully added to the output buffer and REDIS_ERR otherwise. Example: when the connection
is being disconnected per user-request, no new commands may be added to the output buffer and REDIS_ERR is
returned on calls to the redisAsyncCommand family.

If the reply for a command with a NULL callback is read, it is immediately freed. When the callback
for a command is non-NULL, the memory is freed immediately following the callback: the reply is only
valid for the duration of the callback.

All pending callbacks are called with a NULL reply when the context encountered an error.

Disconnecting

An asynchronous connection can be terminated using:

c
void redisAsyncDisconnect(redisAsyncContext *ac);

When this function is called, the connection is not immediately terminated. Instead, new
commands are no longer accepted and the connection is only terminated when all pending commands
have been written to the socket, their respective replies have been read and their respective
callbacks have been executed. After this, the disconnection callback is executed with the
REDIS_OK status and the context object is freed.

Hooking it up to event library *X*

There are a few hooks that need to be set on the context object after it is created.
See the adapters/ directory for bindings to libev and libevent.

Reply parsing API

Hiredis comes with a reply parsing API that makes it easy for writing higher
level language bindings.

The reply parsing API consists of the following functions:

c
redisReader *redisReaderCreate(void);
void redisReaderFree(redisReader *reader);
int redisReaderFeed(redisReader *reader, const char *buf, size_t len);
int redisReaderGetReply(redisReader *reader, void **reply);

The same set of functions are used internally by hiredis when creating a
normal Redis context, the above API just exposes it to the user for a direct
usage.

Usage

The function redisReaderCreate creates a redisReader structure that holds a
buffer with unparsed data and state for the protocol parser.

Incoming data -- most likely from a socket -- can be placed in the internal
buffer of the redisReader using redisReaderFeed. This function will make a
copy of the buffer pointed to by buf for len bytes. This data is parsed
when redisReaderGetReply is called. This function returns an integer status
and a reply object (as described above) via void **reply. The returned status
can be either REDIS_OK or REDIS_ERR, where the latter means something went
wrong (either a protocol error, or an out of memory error).

The parser limits the level of nesting for multi bulk payloads to 7. If the
multi bulk nesting level is higher than this, the parser returns an error.

Customizing replies

The function redisReaderGetReply creates redisReply and makes the function
argument reply point to the created redisReply variable. For instance, if
the response of type REDIS_REPLY_STATUS then the str field of redisReply
will hold the status as a vanilla C string. However, the functions that are
responsible for creating instances of the redisReply can be customized by
setting the fn field on the redisReader struct. This should be done
immediately after creating the redisReader.

For example, hiredis-rb
uses customized reply object functions to create Ruby objects.

Reader max buffer

Both when using the Reader API directly or when using it indirectly via a
normal Redis context, the redisReader structure uses a buffer in order to
accumulate data from the server.
Usually this buffer is destroyed when it is empty and is larger than 16
KiB in order to avoid wasting memory in unused buffers

However when working with very big payloads destroying the buffer may slow
down performances considerably, so it is possible to modify the max size of
an idle buffer changing the value of the maxbuf field of the reader structure
to the desired value. The special value of 0 means that there is no maximum
value for an idle buffer, so the buffer will never get freed.

For instance if you have a normal Redis context you can set the maximum idle
buffer to zero (unlimited) just with:

c
context->reader->maxbuf = 0;

This should be done only in order to maximize performances when working with
large payloads. The context should be set back to REDIS_READER_MAX_BUF again
as soon as possible in order to prevent allocation of useless memory.

AUTHORS

Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
Jan-Erik Rediger (janerik at fnordig dot com)