Description
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打印操作来用作延迟,问题就没有出现了。
资源竞争?或者是其他?具体我就不太清楚了。