github swoole/swoole-src v6.2.4

2 hours ago

Swoole Core

  • Build fix for platforms without futex: Changed sw_usleep to the standard usleep to fix compilation failure on that platform.
  • WebSocket decoding validation: The decoder rejects CLOSE/PING/PONG control frames with RSV1 set before unmasking, preventing compressed control frames from entering the receive path.
  • WebSocket encoding validation: The encoder rejects fragmented control frames or control frames with payloads exceeding 125 bytes, and makes ping() return false and set SWOOLE_ERROR_WEBSOCKET_PACK_FAILED when encoding fails.
  • Thread stdio fd leak: After a thread exits, manually close the STDIN/STDOUT/STDERR file descriptors that were not released due to PHP_STREAM_FLAG_NO_CLOSE.
  • Reactor unregistered fd lookup: When looking up an unregistered fd, return null instead of inserting an empty entry into the socket map, and change the accessor to const.
  • SOCKS5 target host length validation: The DNS tunnel now validates that the target host length does not exceed 255 bytes, and returns an error as a handshake failure when it is too long.
  • Thread scheduling policy preservation: When no policy is specified in Thread::setPriority(), first read the current scheduling policy before setting the priority, fixing the issue of using the invalid -1 policy.
  • Buffered stream read fix: Under PHP 8.3, when the stream already has buffered data, use the non-waiting receive path so that hooked streams directly return buffered data without waiting for new events.
  • Atomic wait on systems without futex: Implement an atomic wait registry based on a shared-memory hash table, replace the mutex with a spinlock, and add timeout, generation notification, and entry reuse mechanisms.
  • Stream write timeout reporting: When fwrite encounters ETIMEDOUT, no longer print an error notice, preserve the timed_out metadata and do not set eof; only set eof for genuine write errors.
  • Stream read timeout error preservation: When fread encounters ETIMEDOUT, return failure (false) instead of 0 so callers can identify the timeout through metadata, and do not mistakenly set eof.
  • Poll dispatch socket identity preservation: The Poll reactor additionally stores the Socket pointer corresponding to each pollfd during set_events, and directly uses that pointer during dispatch, avoiding events being dispatched to the wrong socket after the fd is reused in a handler.
  • Process pool message bus reuse: start() no longer recreates the message bus when it already exists, allowing the same process pool to be shut down and started again and still deliver messages normally.
  • BASE event pool resource release: A multi-worker BASE server destroys the event process pool after the manager ends and reuses the message boxes already allocated by create(), fixing resource leaks.
  • TCP listener rebindability: make_server_socket() enables SO_REUSEADDR only for TCP listeners, avoiding TIME_WAIT preventing immediate restart while not affecting Unix and UDP socket behavior.
  • ProcessPool truncated packet handling: The stream/task protocol requires both the length prefix and the packet body to be read exactly in full; a short read is treated as an incomplete packet and the connection is closed, and old data in the reused buffer is no longer passed to the callback as a valid payload.
  • Message queue key range validation: The Process, ProcessPool, and Server entry points validate the range before narrowing a PHP integer to key_t; keys outside the representable range of key_t are rejected, while unsigned 32-bit boundaries are still accepted.
  • Multipart upload temporary file error: When a temporary file cannot be created for a multipart upload, mark UPLOAD_ERR_NO_TMP_DIR in the file information, avoiding handing an empty upload to the handler as a successful file.
  • Support HTTP QUERY method: Add QUERY to the HTTP/1 method table and let URL-encoded form bodies go through the existing request parsing path, keeping parsing consistent between normal servers and coroutine servers.
  • Apply stream timeout to hooked write operations: When stream_set_timeout() sets a timeout, also update both the read and write coroutine socket deadlines, so that a blocking fwrite() no longer exceeds the configured stream timeout.
  • Coroutine HTTP client socket initialization: Initialize the internal socket zval to null at construction so that calling close() before connecting is safe and returns SWOOLE_ERROR_CLIENT_NO_CONNECTION.
  • Pass null for DNS when there is no service: getaddrinfo() passes a null pointer instead of an empty string when no service is specified, fixing the issue on musl (Alpine Linux) where EAI_SERVICE caused resolution to always fail.
  • Ignore external interrupts when preemption is disabled: Apply the scheduling deadline only when the preemptive scheduling thread is running, preventing signal interrupts triggered by other extensions from switching coroutines using stale context state when preemption is off.
  • CPU affinity ID validation: Each affinity entry is converted only once and used for validation, diagnostics, and CPU_SET; negative IDs and IDs exceeding CPU_SETSIZE are rejected, and Thread::setAffinity() treats any nonzero return value from pthread_setaffinity_np as failure.
  • Validate affinity IDs by CPU count: On top of the previous commit, change the upper limit to SW_CPU_NUM (also not exceeding CPU_SETSIZE), rejecting IDs outside the available CPU range before Linux silently trims the invalid mask.
  • Prevent workers from executing manager-only signal handling: In signal handling, first verify that the current process PID equals the manager PID, avoiding a newly forked worker briefly inheriting the manager's signal handler and then repeatedly broadcasting SIGWINCH to itself.
  • Preserve native coroutine socket errors: After a socket call fails, immediately read errno and store it in a local variable; shutdown() and check_liveness() no longer depend on the C runtime errno that may be overwritten.
  • Initialize PHP hook after switching hooks: When switching from the native hook to the PHP hook, add the missing function cache initialization so that subsequent calls go through the swoole_* PHP functions instead of an empty cache.
  • Redis server packet receive buffer handling: Check the length before reading the trailing CRLF, expand only when the buffer is full, and perform the expansion calculation without narrowing and within package_max_length, fixing fragmented request parsing.
  • Preserve package length limit for partial settings: When set() on a coroutine socket does not provide package_max_length, no longer reset it to the default value, preserving the current limit.
  • Crash when initiating PHP requests in non-Swoole threads: sw_is_main_thread() now uses the ZTS check, and all reads and writes of the task temporary directory are protected by sw_thread_lock, avoiding crashes in threads not created by Swoole.
  • Coroutine HTTP server request boundaries and 100 Continue: When the chunked request buffer is full, expand it to the limit and return 413 if exceeded; use llhttp completion events to precisely determine request end and preserve pipeline bytes, and send a 100 Continue once before waiting for the request body.
  • HTTP/1.0 ignores 100-continue: Send the 100 Continue interim response only for HTTP/1.1 (version 101) requests; HTTP/1.0 clients do not recognize informational responses, so wait directly for the request body.
  • Reject overly long HTTP Upgrade values: Accumulate the Upgrade header length across multiple parsing fragments, and return a parse error when it exceeds the thread temporary buffer capacity, avoiding out-of-bounds writes and heap corruption.
  • Server SSL context initialization: Always initialize the main context and register the SNI callback even when only SNI certificates are configured without a default certificate, and make SSLContext::create() return failure and release the incompletely initialized context when certificate loading fails.
  • Preserve request fields across parse calls: Standalone Request::parse() now performs llhttp parsing of appended ranges on retained request storage, and rebuilds URL and header field spans across fragments, avoiding fragments being treated as complete fields or referencing freed pointers.
  • Reject repeated calls to native resource constructors: The constructors of coroutine HTTP/HTTP2 clients, coroutine HTTP servers, Process Pool, Cookie, and Thread report an error on the second call, protecting the original native resources and object state from being replaced.
  • Server cpu_affinity_ignore handling: First validate that the option must be an array, then compute candidate CPUs based on the process's actual affinity mask, ignore invalid IDs, report an error when all available CPUs are excluded, and free that memory when the Server is destructed.
  • Terminate chunked responses after clearing trailers: Always append the trailing CRLF even if the public trailer property is empty or null, ensuring wire format integrity and connection reusability.
  • RSHUTDOWN cleanup of PHP timers: Use std::vector and a timer ID list instead of a pointer array allocated with emalloc, look up each one again before deleting it, avoiding timers remaining until swoole_clean() due to a fatal error when request heap memory is exhausted.
  • Register after-fork hook during MINIT: Move the registration of the after-fork hook from RINIT to MINIT, avoiding repeated mounting when requests are started and shut down repeatedly in thread mode.
  • curl multi preserves select readiness state: curl_multi_exec is driven by curl_multi_socket_action, and select is only responsible for waiting and recording readiness, covering polling, callback timing, removal, and reuse scenarios.
  • HTTP/1 trailer completion fix: Determine whether sending is needed using the actual length of the built trailer buffer, preserve the trailing CRLF even for trailers containing only null values, and make end() return false when trailer sending fails instead of being overwritten by a success result.
  • Preserve incomplete packets on receive timeout: Under the length protocol, no longer clear the read buffer on timeout and do not set offset to the packet length, allowing subsequent reads to continue assembling the complete packet from the interruption point; HTTP/2 and socket fragmentation tests cover this behavior.
  • Recognize PHP sockaddr length macros on BSD: Change the compilation condition for address traversal to HAVE_SOCKADDR_SA_LEN or HAVE_STRUCT_SOCKADDR_SA_LEN, fixing the check on BSD platforms.
  • Coroutine exception delivery in SPL directory iteration: Save EG(opline_before_exception) and the interrupt opline for each PHP coroutine, and restore the suspended user code through Zend's normal exception path on external cancellation; also support throw-style self-cancellation and reject non-coroutine main contexts.
  • Reject global HTTP response headers in coroutines: At coroutine runtime, replace http_get_last_response_headers() with a handler that indicates it is unsupported, and restore the native handler after runtime stops, avoiding request-level global headers polluting each other across coroutines.
  • CPU affinity for worker application configuration: Extract cpu_affinity_ignore validation into a separate helper function, and select CPU binding for event/task workers based on the actual allowed affinity mask before the worker startup callback; in THREAD mode bind the current pthread, and keep the master mask unchanged.
  • Out-of-bounds read on empty PDO pgsql error message: _pdo_pgsql_trim_message() first checks whether the length is 0 and directly returns an empty string, avoiding strlen - 1 underflowing to SIZE_MAX and then reading bytes before the buffer.
  • Do not lose signals during signal handling: The sigaction fallback path fills sa_mask for non-safe handlers to block other signals, so that signals arriving during handling remain pending and are delivered after handling returns, fixing the issue where the master's SIGTERM is lost during SIGCHLD handling.
  • Enforce EOF packet length limit: Immediately report SW_ERROR_PACKAGE_LENGTH_TOO_LARGE when the first complete EOF packet exceeds package_max_length, or when an unterminated buffer reaches/exceeds the limit; coroutine sockets, synchronous/asynchronous clients, split mode, and server protocols enforce this uniformly, preserving packets exactly equal to the limit and valid first packets in merged reads.
  • Validate WebSocket handshake response headers: The coroutine HTTP client validates Upgrade: websocket, Connection: Upgrade, and Sec-WebSocket-Accept in the 101 response (recomputed and compared using the request key); if invalid, the handshake fails and the connection is closed.
  • UNIX datagram address ownership: When recvfrom has no peer address, clear the cached sun_path; during destruction, first refresh its own bound address with getsockname before unlinking, avoiding mistakenly deleting the peer socket file when releasing the receiver or exposing the previous peer address to PHP.
  • write_func receives decompressed response body: Compressed responses are decompressed before being handed to write_func; the callback receives plaintext chunks and the internal body buffer is cleared; covers gzip/deflate/br and compressed SSE streams.
  • Safe buffer expansion for WebSocket compressed output: Z_SYNC_FLUSH output may exceed deflateBound(); change to extend first when bytes_written + max_length exceeds capacity, and accumulate length according to the write position, avoiding out-of-bounds and length loss; add regression tests for incompressible payloads and control frames.
  • Shared settings array isolation: Each set() performs SEPARATE_ARRAY on the setting property array before merging, avoiding in-place modification of a copy-on-write shared array, so that previously taken snapshots and caller-provided arrays remain unchanged; also remove the erroneous extra reference to the port object when inheriting settings across multiple ports.
  • Preserve reload initiated in worker callbacks: The wait loop no longer deletes reload tasks during the reload_init phase, and instead waits until the next initialization round fills in the worker snapshot before checking completion, avoiding reloads requested in callbacks being discarded prematurely.
  • Backport adaptation for pending reload and regression cases: When there is already a pending reload in the queue, use wait_process with WNOHANG to avoid blocking, and fix the pid <= 0 check; on the test side, reap child processes, drain accepted HTTP connections before closing listeners, and adapt the test runner's output limit handling.
  • Free captured HTTP/1 Content-Type: Move the freeing of content_type out of the branch that only matches compressed types, so that it is also freed when Content-Encoding is set and compression is disabled due to a mismatch, fixing a memory leak of about 1 KiB per request.

Swoole Library

  • Added \Swoole\Coroutine\Http2\MultiplexClient, an HTTP/2 client that multiplexes requests from many coroutines over one shared connection, reconnects on demand, and closes an idle connection after a configurable period (options heartbeat_check_interval and heartbeat_idle_time).
  • \Swoole\Database\RedisConfig::withAuth() now also accepts a [username, password] array for Redis ACL authentication. A class extending RedisConfig that overrides withAuth() or redeclares $auth has to widen its string type to string|array.
  • Support the CURLOPT_PREREQFUNCTION option in the coroutine curl handler; on PHP versions without the native constants, use \Swoole\Curl\CURLOPT_PREREQFUNCTION, \Swoole\Curl\CURL_PREREQFUNC_OK and \Swoole\Curl\CURL_PREREQFUNC_ABORT.
  • Support the http2_max_headers server option, with the constant `\Swoole\Constant::OPTION_HTTP2_MAX_HEADERS.
  • \Swoole\Database\MysqliProxy::inTransaction() and reset(), which report and forget the transaction state the proxy tracks. \Swoole\Database\MysqliPool::get() hands out a connection with that state reset.
  • \Swoole\RemoteObject\Exception::getRemoteClass() and getRemoteCode(), which give the class and the code of an exception thrown on the server.
  • \Swoole\Server\Helper uses the \Swoole\Constant::OPTION_* constants instead of string literals in its option tables and lookups.
  • The FastCGI Params record encodes and decodes its parameters in linear time, about 2.5 and 8 times faster than before; the bytes produced and the parameters read are the same.
  • \Swoole\ArrayObject::lastIndexOf() and remove() with $loop find their keys in one array_keys() call instead of walking the array in PHP, with the same results.
  • \Swoole\StringObject::endsWith() uses str_ends_with() and no longer scans the whole string, with the same results.
  • Report fatal \Swoole\Coroutine\Server accept failures through start() and errCode.
  • Fixed a memory leak in \Swoole\RemoteObject\Client, present since 6.2.0, where every client was kept alive for the lifetime of the process. \Swoole\RemoteObject\Client::getInstance() now returns null for a client that is gone, and a client can no longer be cloned.
  • Fixed a startup race in the default remote object server that let a coroutine see the server as ready while another one was still starting it.
  • A failed reconnect left a database statement proxy wrapping false.
  • \Swoole\RemoteObject\Client now throws a \Swoole\RemoteObject\Exception on a response that does not unserialize.
  • An empty FastCGI response is now reported as 502 Invalid FastCGI Response.
  • \Swoole\Coroutine\FastCGI\Client kept a connection whose response could not be parsed, and the next request sent over it read the rest of that response as its own.
  • A \Swoole\RemoteObject\Client used from several coroutines at once ended the process with a fatal "Socket has already been bound to another coroutine"; calls through one client are now serialized.
  • swoole_container_cpu_num() ignored the CPU quota of cgroup v2 containers.
  • The mysqli proxies did not reconnect after a lost connection under the default mysqli report mode of PHP 8.1+, where failures are thrown as \mysqli_sql_exception.
  • The mysqli proxies no longer reconnect inside a transaction, and report a lost connection in a method they do not retry, such as store_result(), where they returned false.
  • \Swoole\Database\MysqliStatementProxy did not re-bind the bind_result() variables correctly after a reconnect, and retried fetch() on a statement that was prepared again but never executed.
  • \Swoole\Database\MysqliStatementProxy did not recover from a second lost connection on the same statement.
  • \Swoole\Database\PDOStatementProxy turned a connection lost while reading rows into an empty result, and did not recover from a second lost connection on the same statement. It now retries execute() only, and reports a lost connection in any other method.
  • \Swoole\Database\PDOStatementProxy: after a connection lost inside a transaction started by hand, e.g. with exec('BEGIN'), execute() ran the statement again on a new connection, outside the transaction. The lost connection is now reported.
  • The database proxies recognise more lost-connection errors: SSL failures during a query, PostgreSQL's canceling statement due to conflict with recovery, and the PlanetScale PostgreSQL / pg_bouncer messages, in line with Laravel's list.
  • \Swoole\Database\RedisPool passed the connect() arguments in the wrong positions when only some of the connect timeout, retry interval and read timeout were configured.
  • \Swoole\Coroutine\Server never backed off when a coroutine could not be created for an accepted connection.
  • A FastCGI request body of "0" was dropped as empty.
  • An exception the remote object server caught became a TypeError on the client when its code was not an integer, as with a \PDOException, and so did the server's own errors such as an invalid API key. Both now arrive as a \Swoole\RemoteObject\Exception.
  • \Swoole\NameResolver\Nacos::getCluster() threw a TypeError, because Nacos reports the weight of a node as a float.
  • Several scripts under examples/ did not run: the remote object, Nacos, Consul, PDO and short name examples.
  • \Swoole\ConnectionPool::close() failed with an Error when called twice, and fill() on a closed pool made connections only to drop them.
  • \Swoole\Database\PDOProxy: query() and prepare() failed with a TypeError outside the exception error mode.
  • \Swoole\Database\PDOProxy: query(), exec() and beginTransaction() did not reconnect after a connection lost outside a transaction on PostgreSQL.
  • \Swoole\Database\PDOProxy stayed on a dead connection after a connection lost inside a transaction, and on PostgreSQL after a reconnect that failed. The next call now reconnects, and rollBack() of the transaction lost with the connection returns true.
  • \Swoole\ArrayObject and swoole_array_default_value() raised a deprecation on PHP 8.5 when given a null key, which still stands for the empty string.
  • The mysqli proxies raised a warning on prepare(query: ...), with the query passed by name, and could not prepare that statement again after a reconnect.
  • \Swoole\Database\PDOStatementProxy: after a reconnect, a statement used the value a variable had when bindParam() was called, not its present one, and no longer filled the variables of bindColumn().
  • \Swoole\Database\PDOStatementProxy::bindColumn() threw a TypeError when called without a type.
  • isset() and empty() on an offset of a \Swoole\RemoteObject always failed with a TypeError.
  • \Swoole\Database\PDOStatementProxy: after a reconnect, a parameter bound by both bindValue() and bindParam() could take the binding made first instead of the one made last.
  • Array access on a \Swoole\RemoteObject used the properties of the object on the server instead of its ArrayAccess implementation.
  • The remote object server passed null to a function called with a remote object it no longer has, where it now reports the object as not found.
  • The default remote object server did not start in a directory whose path has a space or a quote in it.
  • isset(), empty() and ?? on a property of a \Swoole\RemoteObject treated the property as unset without asking the server, and unset() did not reach the server.
  • Dropped support for PHP below 8.2 and Swoole below 6.2.

Swoole Core

  • 针对无 futex 平台的构建修复:将 sw_usleep 改为标准 usleep,修复该平台上的编译失败问题。
  • WebSocket 解码校验:解码器在解除掩码之前拒绝设置了 RSV1 的 CLOSE/PING/PONG 控制帧,防止压缩的控制帧进入接收路径。
  • WebSocket 编码校验:编码器拒绝分片的控制帧或载荷超过 125 字节的控制帧,并在编码失败时让 ping() 返回 false 并设置 SWOOLE_ERROR_WEBSOCKET_PACK_FAILED。
  • 线程 stdio fd 泄漏:线程退出后,手动关闭因 PHP_STREAM_FLAG_NO_CLOSE 而未释放的 STDIN/STDOUT/STDERR 文件描述符。
  • Reactor 未注册 fd 查找:查找未注册的 fd 时返回 null,而不是向 socket map 中插入空条目,并将访问器改为 const。
  • SOCKS5 目标主机长度校验:DNS 隧道现在校验目标主机长度不超过 255 字节,过长时以握手失败返回错误。
  • 线程调度策略保留:Thread::setPriority() 未指定策略时,先读取当前调度策略再设置优先级,修复使用无效的 -1 策略的问题。
  • 缓冲流读取修复:在 PHP 8.3 下,当流已有缓冲数据时,使用非等待接收路径,使被 hook 的流直接返回缓冲数据而无需等待新事件。
  • 无 futex 系统上的原子等待:实现基于共享内存哈希表的原子等待注册表,用自旋锁替代互斥锁,并增加超时、代际通知和条目复用机制。
  • 流写超时上报:fwrite 遇到 ETIMEDOUT 时不再打印错误通知,保留 timed_out 元数据且不设置 eof;仅对真正的写错误设置 eof。
  • 流读超时错误保留:fread 遇到 ETIMEDOUT 时返回失败(false)而非 0,使调用方可通过元数据识别超时,且不会误设 eof。
  • Poll 派发 socket 身份保留:Poll reactor 在 set_events 时额外存储每个 pollfd 对应的 Socket 指针,并在派发时直接使用该指针,避免 fd 在 handler 中被复用后事件被派发到错误的 socket。
  • 进程池消息总线复用:start() 不再在消息总线已存在时重新创建它,使同一进程池可以关闭后再次启动并仍能正常投递消息。
  • BASE 事件池资源释放:多 worker BASE 服务器在 manager 结束后销毁事件进程池,并复用 create() 已分配的消息盒,修复资源泄漏。
  • TCP 监听器可重新绑定:make_server_socket() 仅对 TCP 监听器启用 SO_REUSEADDR,避免 TIME_WAIT 阻止立即重启,同时不影响 Unix 和 UDP socket 行为。
  • ProcessPool 截断包处理:流/任务协议要求长度前缀和包体都完整读取;短读被视为不完整包并关闭连接,复用缓冲区中的旧数据不再作为有效载荷传给回调。
  • 消息队列 key 范围校验:Process、ProcessPool 和 Server 入口在将 PHP 整数收窄为 key_t 前校验范围;超出 key_t 可表示范围的 key 被拒绝,而无符号 32 位边界仍被接受。
  • Multipart 上传临时文件错误:无法为 multipart 上传创建临时文件时,在文件信息中标记 UPLOAD_ERR_NO_TMP_DIR,避免将空上传作为成功文件交给 handler。
  • 支持 HTTP QUERY 方法:将 QUERY 加入 HTTP/1 方法表,并让 URL 编码的表单体走现有请求解析路径,保持普通服务器与协程服务器解析一致。
  • 将流超时应用于被 hook 的写操作:stream_set_timeout() 设置超时时,同时更新读和写协程 socket 的 deadline,使阻塞的 fwrite() 不再超过配置的流超时。
  • 协程 HTTP 客户端 socket 初始化:构造时将内部 socket zval 初始化为 null,使在连接前调用 close() 安全并返回 SWOOLE_ERROR_CLIENT_NO_CONNECTION。
  • 无服务时 DNS 传 null:getaddrinfo() 在未指定服务时传 null 指针而非空字符串,修复 musl(Alpine Linux)上 EAI_SERVICE 导致解析总是失败的问题。
  • 禁用抢占时忽略外部中断:仅在抢占调度线程运行时应用调度 deadline,防止其他扩展触发的信号中断在抢占关闭时使用过期上下文状态切换协程。
  • CPU 亲和性 ID 校验:每个亲和性条目仅转换一次,并用于校验、诊断和 CPU_SET;负 ID 和超过 CPU_SETSIZE 的 ID 被拒绝,Thread::setAffinity() 将 pthread_setaffinity_np 的任何非零返回值视为失败。
  • 按 CPU 数量校验亲和性 ID:在上一条的基础上,将上限改为 SW_CPU_NUM(也不超过 CPU_SETSIZE),在 Linux 静默裁剪无效掩码之前拒绝可用 CPU 范围之外的 ID。
  • 防止 worker 执行仅限 manager 的信号处理:信号处理中先校验当前进程 PID 等于 manager PID,避免新 fork 的 worker 短暂继承 manager 的信号处理器后反复向自身广播 SIGWINCH。
  • 保留原生协程 socket 错误:socket 调用失败后立即读取 errno 并存入局部变量;shutdown() 和 check_liveness() 不再依赖可能被覆盖的 C 运行时 errno。
  • 切换 hook 后初始化 PHP hook:从原生 hook 切换到 PHP hook 时,补充缺失的函数缓存初始化,使后续调用走 swoole_* PHP 函数而非空缓存。
  • Redis 服务器包接收缓冲区处理:读取尾部 CRLF 前先检查长度,仅在缓冲区满时扩展,扩展计算不窄化且在 package_max_length 以内,修复分片请求解析。
  • 部分设置时保留包长度限制:协程 socket 上 set() 未提供 package_max_length 时,不再将其重置为默认值,保留当前限制。
  • 非 Swoole 线程中发起 PHP 请求时崩溃:sw_is_main_thread() 现在使用 ZTS 检查,任务临时目录的所有读写由 sw_thread_lock 保护,避免非 Swoole 创建的线程中崩溃。
  • 协程 HTTP 服务器请求边界与 100 Continue:分块请求缓冲区满时扩展到上限,超出则返回 413;使用 llhttp 完成事件精确判断请求结束并保留 pipeline 字节,在等待请求体前发送一次 100 Continue。
  • HTTP/1.0 忽略 100-continue:仅对 HTTP/1.1(version 101)请求发送 100 Continue 临时响应;HTTP/1.0 客户端不识别信息性响应,因此直接等待请求体。
  • 拒绝过长的 HTTP Upgrade 值:跨多个解析片段累计 Upgrade 头长度,超过线程临时缓冲区容量时返回解析错误,避免越界写和堆损坏。
  • 服务器 SSL 上下文初始化:即使仅配置 SNI 证书而无默认证书,也始终初始化主上下文并注册 SNI 回调;证书加载失败时让 SSLContext::create() 返回失败并释放未完全初始化的上下文。
  • 跨解析调用保留请求字段:独立的 Request::parse() 现在对保留的请求存储上追加的范围执行 llhttp 解析,并跨片段重建 URL 和头字段 span,避免片段被当作完整字段或引用已释放的指针。
  • 拒绝重复调用原生资源构造函数:协程 HTTP/HTTP2 客户端、协程 HTTP 服务器、进程池、Cookie 和 Thread 的构造函数在第二次调用时报错,保护原生资源和对象状态不被替换。
  • 服务器 cpu_affinity_ignore 处理:先校验该选项必须是数组,再基于进程实际亲和性掩码计算候选 CPU,忽略无效 ID,所有可用 CPU 都被排除时报错,并在 Server 析构时释放该内存。
  • 清除 trailer 后终止分块响应:即使公共 trailer 属性为空或 null,也始终追加尾部 CRLF,确保线格式完整性和连接可复用性。
  • RSHUTDOWN 清理 PHP 定时器:使用 std::vector 和定时器 ID 列表替代用 emalloc 分配的指针数组,删除前逐个重新查找,避免请求堆内存耗尽导致致命错误时定时器残留到 swoole_clean()。
  • 在 MINIT 阶段注册 after-fork hook:将 after-fork hook 的注册从 RINIT 移到 MINIT,避免线程模式下请求反复启动和关闭时重复挂载。
  • curl multi 保留 select 就绪状态:curl_multi_exec 由 curl_multi_socket_action 驱动,select 仅负责等待并记录就绪状态,覆盖轮询、回调时机、移除和复用场景。
  • HTTP/1 trailer 完成修复:使用构建的 trailer 缓冲区实际长度判断是否需要发送,即使 trailer 仅含 null 值也保留尾部 CRLF,并在 trailer 发送失败时让 end() 返回 false 而非被成功结果覆盖。
  • 接收超时时保留不完整包:长度协议下,超时不再清空读缓冲区,也不将 offset 设为包长度,使后续读取可从断点继续组装完整包;HTTP/2 和 socket 分片测试覆盖此行为。
  • 在 BSD 上识别 PHP sockaddr 长度宏:地址遍历的编译条件改为 HAVE_SOCKADDR_SA_LEN 或 HAVE_STRUCT_SOCKADDR_SA_LEN,修复 BSD 平台上的检查。
  • SPL 目录迭代中的协程异常投递:为每个 PHP 协程保存 EG(opline_before_exception) 和中断 opline,外部取消时通过 Zend 正常异常路径恢复挂起的用户代码;同时支持 throw 风格的自取消并拒绝非协程主上下文。
  • 拒绝协程中的全局 HTTP 响应头:协程运行时将 http_get_last_response_headers() 替换为表示不支持的 handler,运行时停止后恢复原生 handler,避免请求级全局头在协程间互相污染。
  • worker 应用配置的 CPU 亲和性:将 cpu_affinity_ignore 校验提取为独立辅助函数,在 worker 启动回调前基于实际允许的亲和性掩码为 event/task worker 选择 CPU 绑定;THREAD 模式下绑定当前 pthread,master 掩码保持不变。
  • 空 PDO pgsql 错误消息越界读:_pdo_pgsql_trim_message() 先检查长度是否为 0,直接返回空字符串,避免 strlen - 1 下溢为 SIZE_MAX 后读取缓冲区之前的字节。
  • 信号处理期间不丢失信号:sigaction 回退路径为非安全 handler 填充 sa_mask 以阻塞其他信号,使处理期间到达的信号保持 pending 并在处理返回后投递,修复 master 的 SIGTERM 在 SIGCHLD 处理期间丢失的问题。
  • 强制执行 EOF 包长度限制:首个完整 EOF 包超过 package_max_length,或未终止缓冲区达到/超过限制时,立即报告 SW_ERROR_PACKAGE_LENGTH_TOO_LARGE;协程 socket、同步/异步客户端、split 模式和服务器协议统一执行,精确保留等于限制的包以及合并读取中的有效首包。
  • 校验 WebSocket 握手响应头:协程 HTTP 客户端校验 101 响应中的 Upgrade: websocket、Connection: Upgrade 和 Sec-WebSocket-Accept(使用请求 key 重新计算并比较);无效则握手失败并关闭连接。
  • UNIX 数据报地址所有权:recvfrom 无对端地址时清除缓存的 sun_path;析构时先用 getsockname 刷新自身绑定地址再 unlink,避免释放接收方时误删对端 socket 文件或将之前的对端地址暴露给 PHP。
  • write_func 接收解压后的响应体:压缩响应在交给 write_func 前解压;回调接收明文块并清空内部 body 缓冲区;覆盖 gzip/deflate/br 和压缩 SSE 流。
  • WebSocket 压缩输出的安全缓冲区扩展:Z_SYNC_FLUSH 输出可能超过 deflateBound();当 bytes_written + max_length 超过容量时先改为 extend,并按写入位置累计 length,避免越界和长度丢失;增加不可压缩载荷和控制帧的回归测试。
  • 共享设置数组隔离:每次 set() 在合并前对 setting 属性数组执行 SEPARATE_ARRAY,避免原地修改写时复制的共享数组,使之前获取的快照和调用方提供的数组保持不变;同时移除跨多端口继承设置时对 port 对象的错误额外引用。
  • 保留 worker 回调中发起的 reload:等待循环不再在 reload_init 阶段删除 reload 任务,而是等到下一轮初始化填充 worker 快照后再检查完成,避免回调中请求的 reload 被过早丢弃。
  • pending reload 和回归用例的 backport 适配:队列中已有 pending reload 时,使用带 WNOHANG 的 wait_process 避免阻塞,并修复 pid <= 0 检查;测试侧回收子进程,关闭监听器前排空已接受的 HTTP 连接,并适配测试运行器的输出限制处理。
  • 释放捕获的 HTTP/1 Content-Type:将 content_type 的释放移出仅匹配压缩类型的分支,使其在设置了 Content-Encoding 但因不匹配而禁用压缩时也能释放,修复每请求约 1 KiB 的内存泄漏。

Swoole Library

  • 新增 \Swoole\Coroutine\Http2\MultiplexClient,一个 HTTP/2 客户端,在一条共享连接上多路复用来自多个协程的请求,按需重连,并在可配置时间后关闭空闲连接(选项 heartbeat_check_interval 和 heartbeat_idle_time)。
  • \Swoole\Database\RedisConfig::withAuth() 现在也接受 [username, password] 数组用于 Redis ACL 认证。扩展 RedisConfig 并重写 withAuth() 或重新声明 $auth 的类必须将其 string 类型放宽为 string|array。
  • 在协程 curl handler 中支持 CURLOPT_PREREQFUNCTION 选项;在没有原生常量的 PHP 版本上,使用 \Swoole\Curl\CURLOPT_PREREQFUNCTION、\Swoole\Curl\CURL_PREREQFUNC_OK 和 \Swoole\Curl\CURL_PREREQFUNC_ABORT。
  • 支持 http2_max_headers 服务器选项,常量 \Swoole\Constant::OPTION_HTTP2_MAX_HEADERS。
  • \Swoole\Database\MysqliProxy::inTransaction() 和 reset(),用于报告和遗忘代理跟踪的事务状态。\Swoole\Database\MysqliPool::get() 发放连接时会重置该状态。
  • \Swoole\RemoteObject\Exception::getRemoteClass() 和 getRemoteCode(),提供服务器端抛出的异常的类和代码。
  • \Swoole\Server\Helper 在其选项表和查找中使用 \Swoole\Constant::OPTION_* 常量而非字符串字面量。
  • FastCGI Params 记录以线性时间编码和解码其参数,比之前快约 2.5 倍和 8 倍;产生的字节和读取的参数相同。
  • \Swoole\ArrayObject::lastIndexOf() 和带 $loop 的 remove() 通过一次 array_keys() 调用查找键,而非在 PHP 中遍历数组,结果相同。
  • \Swoole\StringObject::endsWith() 使用 str_ends_with(),不再扫描整个字符串,结果相同。
  • 通过 start() 和 errCode 报告致命的 \Swoole\Coroutine\Server accept 失败。
  • 修复 \Swoole\RemoteObject\Client 中自 6.2.0 起存在的内存泄漏,此前每个客户端都会在进程生命周期内保持存活。\Swoole\RemoteObject\Client::getInstance() 现在对已消失的客户端返回 null,且客户端不再可克隆。
  • 修复默认远程对象服务器中的启动竞态,该竞态会让一个协程在另一个协程仍在启动服务器时看到服务器已就绪。
  • 重连失败后,数据库语句代理包装了 false。
  • \Swoole\RemoteObject\Client 现在对无法反序列化的响应抛出 \Swoole\RemoteObject\Exception。
  • 空 FastCGI 响应现在报告为 502 Invalid FastCGI Response。
  • \Swoole\Coroutine\FastCGI\Client 会保留响应无法解析的连接,下一个请求在该连接上发送时会把该响应的剩余部分当作自己的响应读取。
  • 同时从多个协程使用的 \Swoole\RemoteObject\Client 会以致命的 "Socket has already been bound to another coroutine" 结束进程;现在通过一个客户端的调用被串行化。
  • swoole_container_cpu_num() 忽略了 cgroup v2 容器的 CPU 配额。
  • 在 PHP 8.1+ 默认 mysqli 报告模式下(失败以 \mysqli_sql_exception 抛出),mysqli 代理在连接丢失后不会重连。
  • mysqli 代理不再在事务内重连,并在其不重试的方法(如 store_result())中报告连接丢失,此前返回 false。
  • \Swoole\Database\MysqliStatementProxy 在重连后未正确重新绑定 bind_result() 变量,并在重新准备但从未执行的语句上重试 fetch()。
  • \Swoole\Database\MysqliStatementProxy 未从同一语句上的第二次连接丢失中恢复。
  • \Swoole\Database\PDOStatementProxy 将读取行时丢失的连接变成空结果,且未从同一语句上的第二次连接丢失中恢复。现在仅重试 execute(),其他方法报告连接丢失。
  • \Swoole\Database\PDOStatementProxy:在手动启动的事务(例如用 exec('BEGIN'))内连接丢失后,execute() 会在新连接上、事务之外重新执行语句。现在会报告连接丢失。
  • 数据库代理识别更多连接丢失错误:查询期间的 SSL 失败、PostgreSQL 的 canceling statement due to conflict with recovery,以及 PlanetScale PostgreSQL / pg_bouncer 消息,与 Laravel 的列表一致。
  • \Swoole\Database\RedisPool 在仅配置部分连接超时、重试间隔和读超时时,以错误的位置传递 connect() 参数。
  • \Swoole\Coroutine\Server 在无法为已接受连接创建协程时从不退避。
  • FastCGI 请求体 "0" 被当作空而丢弃。
  • 远程对象服务器捕获的异常在其代码不是整数时(如 \PDOException)在客户端变成 TypeError,服务器自身错误(如无效 API key)也是如此。现在两者都以 \Swoole\RemoteObject\Exception 到达。
  • \Swoole\NameResolver\Nacos::getCluster() 抛出 TypeError,因为 Nacos 将节点权重报告为 float。
  • examples/ 下的多个脚本无法运行:远程对象、Nacos、Consul、PDO 和短名称示例。
  • \Swoole\ConnectionPool::close() 第二次调用时以 Error 失败,在已关闭的池上调用 fill() 只会创建连接然后丢弃。
  • \Swoole\Database\PDOProxy:在异常错误模式之外,query() 和 prepare() 以 TypeError 失败。
  • \Swoole\Database\PDOProxy:在 PostgreSQL 上事务外连接丢失后,query()、exec() 和 beginTransaction() 不会重连。
  • \Swoole\Database\PDOProxy 在事务内连接丢失后,以及在 PostgreSQL 上重连失败后,仍停留在死连接上。下一次调用现在会重连,且随连接丢失的事务的 rollBack() 返回 true。
  • \Swoole\ArrayObject 和 swoole_array_default_value() 在 PHP 8.5 上给定 null 键时引发弃用警告,null 键仍代表空字符串。
  • mysqli 代理在 prepare(query: ...)(查询按名称传递)时引发警告,且重连后无法再次准备该语句。
  • \Swoole\Database\PDOStatementProxy:重连后,语句使用调用 bindParam() 时变量的值而非当前值,且不再填充 bindColumn() 的变量。
  • \Swoole\Database\PDOStatementProxy::bindColumn() 在不带类型调用时抛出 TypeError。
  • 对 \Swoole\RemoteObject 偏移量的 isset() 和 empty() 总是以 TypeError 失败。
  • \Swoole\Database\PDOStatementProxy:重连后,同时由 bindValue() 和 bindParam() 绑定的参数可能采用先前的绑定而非最后的绑定。
  • 对 \Swoole\RemoteObject 的数组访问使用服务器上对象的属性而非其 ArrayAccess 实现。
  • 远程对象服务器在调用带有其已不拥有的远程对象的函数时传递 null,现在改为报告对象未找到。
  • 默认远程对象服务器在路径包含空格或引号的目录中无法启动。
  • 对 \Swoole\RemoteObject 属性的 isset()、empty() 和 ?? 在不询问服务器的情况下将属性视为未设置,且 unset() 不会到达服务器。
  • 放弃对 PHP 8.2 以下和 Swoole 6.2 以下的支持。

Don't miss a new swoole-src release

NewReleases is sending notifications on new releases.