github swoole/swoole-src v6.1.0-rc2

pre-release19 hours ago

✨ New Features

  • Added the $throw_exception parameter to Swoole\Coroutine::cancel(), enabling exceptions to be thrown when canceling a coroutine.
  • Swoole\Coroutine\Http\Client and Swoole\Coroutine\Http\Server now support receiving WebSocket fragmented messages.
  • Added disconnect and ping methods to Swoole\Coroutine\Http\Client and Swoole\Coroutine\Http\Server.
  • Refactored Swoole\Coroutine\Lock, Swoole\Lock, and Swoole\Thread\Lock, retaining only the __construct, lock, and unlock methods, making usage more aligned with php flock.

🐛 Bug Fixes

  • Fixed an issue where Swoole\Websocket\Server might still trigger the onMessage callback after a handshake failure or connection closure.
  • Fixed execution errors in Swoole\Coroutine\Http\Server caused by mismatched return types and function signatures.
  • Fixed the issue where Swoole\Coroutine\Http\Server did not support enabling HTTP/2.
  • Fixed the timeout precision conversion issue in Swoole\Lock::lockwait().
  • Fixed the ReactorEpoll::add(): failed to add event warning when using the coroutine-based curl module, caused by PHP 8 compatibility adjustments and incomplete cleanup of keepalive connections.

🛠️ Internal Optimizations

  • Upgraded the Swoole library version.
  • Disabled the -Wdate-time warning to resolve compilation errors when using Zig/Clang.
  • Removed PHP 8.0 related compatibility code.
  • Added the SWOOLE_ODBC_LIBS variable in config.m4.
  • Ensured that timespec.tv_nsec values are less than 1,000,000,000 to comply with POSIX standards.

⚠️ Notes

  • This version is an RC (Release Candidate) and should only be used in testing environments. Do not deploy it in production.
  • Starting from Swoole 6.1, Swoole\Coroutine\Http\Client and Swoole\Coroutine\Http\Server will automatically handle WebSocket control frames unless open_websocket_ping_frame, open_websocket_pong_frame, or open_websocket_close_frame are explicitly set.
  • Swoole\Coroutine::cancel() cannot cancel file coroutine operations. Forcibly canceling may cause segmentation faults.
  • If --enable-iouring is enabled, a warning will be thrown if the kernel version or liburing version does not meet the requirements.
  • The Swoole\Websocket\Server::push method will not automatically close the connection after sending a close frame. To close the connection, use the Swoole\Websocket\Server::disconnect() method.
  • When processing control frames, the Swoole protocol actively sets the FIN flag to 1 and ensures compression is not enabled (i.e., the RSV1 bit is 0). User settings for this will be ignored.
  • When handling compression and sending of consecutive frames, Swoole delegates the responsibility of compression and data chunking to the application layer. Users must manually implement the relevant logic. Refer to the example below for details.

💡 API Changes

New Lock API

Function Prototypes

namespace Swoole\Coroutine {
    class Lock {
        public function __construct(bool $shared = false) {}
        public function lock(int $operation = LOCK_EX): bool {}
        public function unlock(): bool {}
    }
}

namespace Swoole {
    class Lock {
        public function __construct(int $type = SWOOLE_MUTEX) {}
        public function lock(int $operation = LOCK_EX, float $timeout = -1): bool {}
        public function unlock(): bool {}
    }
}

namespace Swoole\Thread {
    class Lock {
        public function __construct(int $type = SWOOLE_MUTEX) {}
        public function lock(int $operation = LOCK_EX, float $timeout = -1): bool {}
        public function unlock(): bool {}
    }
}

Usage Example

$lock = new Swoole\Lock;
$lock->lock(LOCK_EX | LOCK_NB, 0.5);
$lock->unlock();

Canceling Coroutines

Co\run(function () {
    $cid = Co\go(function () {
        try {
            while (true) {
                System::sleep(0.1);
                echo "co 2 running\n";
            }
            var_dump('end');
        } catch (Swoole\Coroutine\CanceledException $e) {
            var_dump('cancelled');
        }
    });

    System::sleep(0.3);
    Co::cancel($cid, true);

    System::sleep(0.2);
    echo "co 1 end\n";
});

WebSocket Compression and Sending Consecutive Frames

Asynchronous Server

use Swoole\WebSocket\Server;
use Swoole\WebSocket\Frame;

$server = Server('127.0.0.1', 9502);
$server->set(['websocket_compression' => true]);
$server->on('message', function (Server $server, Frame $frame) {
    $data1 = bin2hex(random_bytes(10 * 1024));
    $data2 = bin2hex(random_bytes(20 * 2048));
    $data3 = bin2hex(random_bytes(40 * 4096));
    $context = deflate_init(ZLIB_ENCODING_RAW); // Stream compression
    $server->push($frame->fd, deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
    $server->push($frame->fd, deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
    $server->push($frame->fd, deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
});
$server->start();

Coroutine Server

use function Swoole\Coroutine\run;
use Swoole\Coroutine\Http\Server;

run(function() {
    $server = new Server("127.0.0.1", 9502);
    $server->set(['websocket_compression' => true]);
    $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) {
        $response->upgrade();
        $data1 = bin2hex(random_bytes(10 * 1024));
        $data2 = bin2hex(random_bytes(20 * 2048));
        $data3 = bin2hex(random_bytes(40 * 4096));
        $context = deflate_init(ZLIB_ENCODING_RAW);
        $response->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
        $response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
        $response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
    });
});

Coroutine Client

use function Swoole\Coroutine\run;
use Swoole\Coroutine\Http\Client;

run(function() {
    $client = new Client('127.0.0.1', 9502);
    $client->set(['websocket_compression' => true]);
    $ret = $client->upgrade('/');
    $context = deflate_init(ZLIB_ENCODING_RAW);
    $client->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
    $client->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
    $client->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
});

✨ 新功能

  • Swoole\Coroutine::cancel() 新增 $throw_exception 参数,支持在取消协程时抛出异常。
  • Swoole\Coroutine\Http\ClientSwoole\Coroutine\Http\Server 现已支持接收 WebSocket 分块消息。
  • Swoole\Coroutine\Http\ClientSwoole\Coroutine\Http\Server 新增 disconnectping 方法。
  • 重构Swoole\Coroutine\LockSwoole\Lock和,Swoole\Thread\Lock,只保留__constructlockunlock方法,使用上更加贴近php flock

🐛 问题修复

  • 修复 Swoole\Websocket\Server 在握手失败或连接关闭后,仍可能触发 onMessage 回调的问题。
  • 修复 Swoole\Coroutine\Http\Server 因函数返回值类型与签名不一致导致的执行错误。
  • 修复 Swoole\Coroutine\Http\Server 不支持启用 HTTP/2 的问题。
  • 修复 Swoole\Lock::lockwait() 超时时间精度转换问题。
  • 修复因PHP 8兼容性调整及keepalive连接清理不彻底,导致使用协程化 curl 模块时出现 ReactorEpoll::add(): failed to add event 警告的问题。

🛠️ 内部优化

  • 升级 Swoole library 版本。
  • 禁用 -Wdate-time 警告,解决使用 Zig/Clang 编译时的错误。
  • 移除 PHP 8.0 相关兼容代码。
  • config.m4 中新增 SWOOLE_ODBC_LIBS 变量。
  • 确保 timespec.tv_nsec 取值小于 1,000,000,000,以符合 POSIX 标准规范。

⚠️ 注意事项

  • 本版本为 RC(候选发布)版本,请仅在测试环境中使用,请勿在生产环境部署。
  • 自 Swoole 6.1 起,Swoole\Coroutine\Http\ClientSwoole\Coroutine\Http\Server 将自动处理 WebSocket 控制帧,除非显式设置 open_websocket_ping_frameopen_websocket_pong_frameopen_websocket_close_frame
  • Swoole\Coroutine::cancel() 无法取消文件协程化操作,强行取消可能导致段错误。
  • 若启用 --enable-iouring,在检测到内核版本或 liburing 版本不满足要求时,将抛出警告信息。
  • Swoole\Websocket\Server::push 方法在发送关闭帧后不会自动关闭连接,如需关闭连接,请使用 Swoole\Websocket\Server::disconnect() 方法。
  • Swoole协议在处理控制帧时,会主动设置 FIN 标志位为 1,并确保不启用压缩(即 RSV1 位为 0),用户对此的设置将被忽略。
  • 当处理连续帧的压缩和发送时,Swoole将压缩与数据分块的职责移交至应用层,用户需手动实现相关逻辑。具体请看最下面的例子。

💡 API 变更

新的锁 API

函数原型

namespace Swoole\Coroutine {
    class Lock {
        public function __construct(bool $shared = false) {}
        public function lock(int $operation = LOCK_EX): bool {}
        public function unlock(): bool {}
    }
}

namespace Swoole {
    class Lock {
        public function __construct(int $type = SWOOLE_MUTEX) {}
        public function lock(int $operation = LOCK_EX, float $timeout = -1): bool {}
        public function unlock(): bool {}
    }
}

namespace Swoole\Thread {
    class Lock {
        public function __construct(int $type = SWOOLE_MUTEX) {}
        public function lock(int $operation = LOCK_EX, float $timeout = -1): bool {}
        public function unlock(): bool {}
    }
}

使用示例

$lock = new Swoole\Lock;
$lock->lock(LOCK_EX | LOCK_NB, 0.5);
$lock->unlock();

取消协程

Co\run(function () {
    $cid = Co\go(function () {
        try {
            while (true) {
                System::sleep(0.1);
                echo "co 2 running\n";
            }
            var_dump('end');
        } catch (Swoole\Coroutine\CanceledException $e) {
            var_dump('cancelled');
        }
    });

    System::sleep(0.3);
    Co::cancel($cid, true);

    System::sleep(0.2);
    echo "co 1 end\n";
});

WebSocket 压缩和发送连续帧

异步服务端

use Swoole\WebSocket\Server;
use Swoole\WebSocket\Frame;

$server = Server('127.0.0.1', 9502);
$server->set(['websocket_compression' => true]);
$server->on('message', function (Server $server, Frame $frame) {
    $data1 = bin2hex(random_bytes(10 * 1024));
    $data2 = bin2hex(random_bytes(20 * 2048));
    $data3 = bin2hex(random_bytes(40 * 4096));
    $context = deflate_init(ZLIB_ENCODING_RAW); // 流式压缩
    $server->push($frame->fd, deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
    $server->push($frame->fd, deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
    $server->push($frame->fd, deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
});
$server->start();

协程服务端

use function Swoole\Coroutine\run;
use Swoole\Coroutine\Http\Server;

run(function() {
    $server = new Server("127.0.0.1", 9502);
    $server->set(['websocket_compression' => true]);
    $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) {
        $response->upgrade();
        $data1 = bin2hex(random_bytes(10 * 1024));
        $data2 = bin2hex(random_bytes(20 * 2048));
        $data3 = bin2hex(random_bytes(40 * 4096));
        $context = deflate_init(ZLIB_ENCODING_RAW);
        $response->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
        $response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
        $response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
    });
});

协程客户端

use function Swoole\Coroutine\run;
use Swoole\Coroutine\Http\Client;

run(function() {
    $client = new Client('127.0.0.1', 9502);
    $client->set(['websocket_compression' => true]);
    $ret = $client->upgrade('/');
    $context = deflate_init(ZLIB_ENCODING_RAW);
    $client->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1);
    $client->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0);
    $client->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN);
});

Don't miss a new swoole-src release

NewReleases is sending notifications on new releases.