Skip to content

Swoole6.x不开启iouring的时候,fread操作疑似存在BUG #5801

Open
@Moxyu

Description

@Moxyu

Swoole 信息

swoole

Swoole => enabled
Author => Swoole Team <team@swoole.com>
Version => 6.0.2
Built => Jun 18 2025 12:07:15
coroutine => enabled with boost asm context
epoll => enabled
eventfd => enabled
signalfd => enabled
cpu_affinity => enabled
spinlock => enabled
rwlock => enabled
sockets => enabled
openssl => OpenSSL 3.0.13 30 Jan 2024
dtls => enabled
http2 => enabled
json => enabled
curl-native => enabled
curl-version => 7.74.0
zlib => 1.2.13
brotli => E16777225/D16777225
mutex_timedlock => enabled
pthread_barrier => enabled
futex => enabled

Directive => Local Value => Master Value
swoole.enable_library => On => On
swoole.enable_fiber_mock => Off => Off
swoole.enable_preemptive_scheduler => Off => Off
swoole.display_errors => On => On
swoole.use_shortname => Off => Off
swoole.unixsock_buffer_size => 8388608 => 8388608

PHP 信息

PHP 8.4.7 (cli) (built: May  9 2025 10:58:14) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.7, Copyright (c) Zend Technologies

目前我遇到的一个问题是,我目前的项目是基于Webman框架进行开发,使用了Swoole作为协程驱动

其他场景都正常,但是在一次意外的8M图片静态资源访问场景中,报了一个错:

Workerman[start.php] start in DEBUG mode
-------------------------------------------- WORKERMAN ---------------------------------------------
Workerman/5.1.3         PHP/8.4.7 (JIT off)           Linux/5.15.167.4-microsoft-standard-WSL2
--------------------------------------------- WORKERS ----------------------------------------------
event-loop  proto       user        worker      listen                 count       state            
swoole      tcp         root        webman      http://0.0.0.0:8787    80           [OK]            
----------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.
ErrorException: fread(): Read of 18446744073709543424 bytes failed with errno=14 Bad address in /www/wwwroot/valorix/server-api/vendor/workerman/workerman/src/Protocols/Http.php:263
Stack trace:
#0 [internal function]: {closure:/www/wwwroot/valorix/server-api/support/bootstrap.php:31}()
#1 /www/wwwroot/valorix/server-api/vendor/workerman/workerman/src/Protocols/Http.php(263): fread()
#2 /www/wwwroot/valorix/server-api/vendor/workerman/workerman/src/Protocols/Http.php(281): Workerman\Protocols\Http::{closure:Workerman\Protocols\Http::sendStream():247}()
#3 /www/wwwroot/valorix/server-api/vendor/workerman/workerman/src/Connection/TcpConnection.php(808): Workerman\Protocols\Http::{closure:Workerman\Protocols\Http::sendStream():279}()
#4 /www/wwwroot/valorix/server-api/vendor/workerman/workerman/src/Events/Swoole.php(288): Workerman\Connection\TcpConnection->baseWrite()
#5 [internal function]: Workerman\Events\Swoole->{closure:Workerman\Events\Swoole::safeCall():286}()
#6 {main}

静态资源是直接通过Webman(核心是Workerman)进程来处理的,没有经过任何Web服务器。经过不断调试,200K的图片没问题,但是当图片大于5M就会报这个错误。

起初调试毫无头绪,后来看了v6发布说明,注意到了iouring这个东西。然后重新编译 Swoole 加上--enable-iouring参数,问题就解决了。

然后为了验证这个结论,我又重新编译 Swoole 故意不加 --enable-iouring ,果然问题又出现了,所以这个问题的原因是 iouring

本着学习的态度去追溯了下 Workerman 的源码,位置/vendor/workerman/workerman/src/Protocols/Http.php:263

protected static function sendStream(TcpConnection $connection, $handler, int $offset = 0, int $length = 0): void
    {
        $connection->context->bufferFull = false;
        $connection->context->streamSending = true;
        if ($offset !== 0) {
            fseek($handler, $offset);
        }
        $offsetEnd = $offset + $length;
        // Read file content from disk piece by piece and send to client.
        $doWrite = function () use ($connection, $handler, $length, $offsetEnd) {
            // Send buffer not full.
            while ($connection->context->bufferFull === false) {
                // Read from disk.
                $size = 1024 * 1024;
                if ($length !== 0) {
                    $tell = ftell($handler);
                    $remainSize = $offsetEnd - $tell;
                    if ($remainSize <= 0) {
                        fclose($handler);
                        $connection->onBufferDrain = null;
                        return;
                    }
                    $size = min($remainSize, $size);
                }

                $buffer = fread($handler, $size);
                // Read eof.
                if ($buffer === '' || $buffer === false) {
                    fclose($handler);
                    $connection->onBufferDrain = null;
                    $connection->context->streamSending = false;
                    return;
                }
                $connection->send($buffer, true);
            }
        };
        // Send buffer full.
        $connection->onBufferFull = function ($connection) {
            $connection->context->bufferFull = true;
        };
        // Send buffer drain.
        $connection->onBufferDrain = function ($connection) use ($doWrite) {
            $connection->context->bufferFull = false;
            $doWrite();
        };
        $doWrite();
    }

大概意思就是流式分块发送文件内容,通过分块读取文件内容并动态发送,避免一次性加载大文件导致内存溢出,同时处理TCP连接的缓冲区状态。整体代码看着是没什么问题。

但是Wokerman使用Swoole作为EventLoop的情况下,满足以下3个条件的时候,就会触发这个报错。

  • Swoole版本为6.x
  • 没有 iouring 的时候
  • 静态资源超过5M(大约是这个值)

报错内容就是 ErrorException: fread(): Read of 18446744073709543424 bytes failed with errno=14 Bad address ,像是资源竞争?

然后我本地临时修改了 Workerman 的源码,把 $size 固定了 1024, 然后fread之前加上了一个dump打印操作来用作延迟,问题就没有出现了。

资源竞争?或者是其他?具体我就不太清楚了。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions