Php 7.1 + Pecl-event + libevent - 在奇怪的情况下挂起
Php 7.1 + Pecl-event + libevent - is hanging in weird case
基于 this answer, I've switched to pecl-event 库。现在我有:
[root]# php -v
PHP 7.1.12 (cli) (built: Nov 22 2017 08:40:02) ( NTS ) Copyright (c) 1997-2017 The PHP Group Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologieswith Zend OPcache v7.1.12, Copyright (c) 1999-2017, by Zend Technologies
[root]# php --info | grep event
/etc/php.d/event.ini event libevent2 headers version => 2.1.8-stable
[root]# pecl list
Installed packages, channel pecl.php.net:
=========================================
Package Version State
event 2.3.0 stable
下面的例子表现得很奇怪。如果从 runme()
函数内部调用 $loop->run()
,它会工作并调用回调。但是如果从 runme()
之外调用 $loop->run()
,它就会挂起!
require_once __DIR__.'/../vendor/autoload.php';
$inner = count($argv) > 1;
$loop = new \React\EventLoop\ExtEventLoop();
//$loop = new \React\EventLoop\StreamSelectLoop();
runme($loop, $inner);
if (!$inner) {
echo "Outer start\n";
$loop->run();
}
function runme(\React\EventLoop\LoopInterface $loop, $inner)
{
$contextOpts = [];
$flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
$context = stream_context_create($contextOpts);
$socket = stream_socket_client('tcp://127.0.0.1:3306', $errno, $errstr, 0, $flags, $context);
stream_set_blocking($socket, 0);
$loop->addWriteStream($socket, function ($socket) use ($loop) {
echo "done ".(false === stream_socket_get_name($socket, true) ? 'false' : 'true')."\n";
$loop->removeWriteStream($socket);
});
if ($inner) {
echo "Inner start\n";
$loop->run();
}
echo "Exit runme\n";
}
运行 结果:
[root@vultr Scraper]# php ./tests/test.php --inner
Inner start
done false
Exit runme
[root@vultr Scraper]# php ./tests/test.php
Exit runme
Outer start
...............HANGING HERE...........
我是不是遗漏了什么,或者这是 libraries/PHP 之一的问题?有人有 运行 php7.1 + react + libevent 的经验吗?
更新:=========================================== =========================
我用最新的 "react/socket" 库“0.8.6”进行了测试。
require_once __DIR__.'/vendor/autoload.php';
$inner = count($argv) > 1;
$loop = new \React\EventLoop\ExtEventLoop();
$connector = new React\Socket\Connector($loop);
runme($loop, $connector, $inner);
if (!$inner) {
echo "Outer start\n";
$loop->run();
}
function runme(\React\EventLoop\LoopInterface $loop, React\Socket\Connector $connector, $inner)
{
$connector->connect('tcp://127.0.0.1:3306')->
then(function (\React\Socket\ConnectionInterface $conn) {
echo ("Hello MySQL!\n");
$conn->close();
},function ($e) {
echo ("Bye MySQL!\n");
})->done();
if ($inner) {
echo "Inner start\n";
$loop->run();
}
echo "Exit runme\n";
}
它工作正常并且returns:
$ php ./testMysql.php
Exit runme
Outer start
Hello MySQL!
$ php ./testMysql.php --inner
Inner start
Hello MySQL!
Exit runme
但是如果你进入 \React\Socket\TcpConnector::waitForStreamOnce() 并删除新 Promise 对象中的 $canceller 函数,如下所示,它将再次挂起。看起来它在最新版本的 React 中工作是一种意外,因为套接字没有以明显的方式存储,实际上类似于 v0.4.6 中的代码。
private function waitForStreamOnce($stream)
{
$loop = $this->loop;
return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream) {
$loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject) {
$loop->removeWriteStream($stream);
// The following hack looks like the only way to
// detect connection refused errors with PHP's stream sockets.
if (false === stream_socket_get_name($stream, true)) {
fclose($stream);
$reject(new \RuntimeException('Connection refused'));
} else {
$resolve(new Connection($stream, $loop));
}
});
});
}
$ php ./testMysql.php --inner
Inner start
.....HANGING
$ php ./testMysql.php
Exit runme
Outer start
...HANGING
嘿 ReactPHP 核心开发人员,刚刚查看了您的脚本,我可以在本地重现它,所以我将为此提交一个问题(即使这可能超出我们的范围)。
require_once __DIR__.'/../vendor/autoload.php';
$inner = count($argv) > 1;
$loop = new \React\EventLoop\ExtEventLoop();
//$loop = new \React\EventLoop\StreamSelectLoop();
runme($loop, $inner);
$contextOpts = [];
$flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
$context = stream_context_create($contextOpts);
$socket = stream_socket_client('tcp://127.0.0.1:3306', $errno, $errstr, 0, $flags, $context);
stream_set_blocking($socket, 0);
$loop->addWriteStream($socket, function ($socket) use ($loop) {
echo "done ".(false === stream_socket_get_name($socket, true) ? 'false' : 'true')."\n";
$loop->removeWriteStream($socket);
});
if ($inner) {
echo "Inner start\n";
$loop->run();
}
echo "Exit runme\n";
但我建议您查看我们的 socket component 来处理连接。
可能看起来像:
$loop = new \React\EventLoop\ExtEventLoop();
$connector = new React\Socket\Connector($loop);
$connector->connect('tcp://127.0.0.1:3306')->then(function (ConnectionInterface $conn) use ($loop) {
$conn->write("Hello MySQL!\n");
});
$loop->run();
问题是 $socket
变量 destroyed 当 runme()
returns(就像任何本地 PHP 变量一样!)。结果,在此套接字 上打开的 连接 已关闭 。
事件扩展尽最大努力防止内存泄漏,因此它尽可能不存储对用户变量的引用。特别是,所有方法都接受来自输入变量的 socket resource (Event::__construct
, for instance) only retrieve 底层数字文件描述符。 用户实际上负责保持那些变量存活 .
以下脚本通过将 $socket
移至全局范围解决了该问题。
require_once 'vendor/autoload.php';
$inner = count($argv) > 1;
$loop = new \React\EventLoop\ExtEventLoop();
$socket = init_socket();
runme($loop, $socket, $inner);
if (!$inner) {
echo "Outer start\n";
$loop->run();
}
function init_socket()
{
$contextOpts = [];
$flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
$context = stream_context_create($contextOpts);
$socket = stream_socket_client('tcp://test.local:80', $errno, $errstr, 0, $flags, $context);
stream_set_blocking($socket, 0);
return $socket;
}
function runme(\React\EventLoop\LoopInterface $loop, $socket, $inner)
{
$loop->addWriteStream($socket, function ($socket) use ($loop) {
echo "done ".(false === stream_socket_get_name($socket, true) ? 'false' : 'true')."\n";
$loop->removeWriteStream($socket);
});
if ($inner) {
echo "Inner start\n";
$loop->run();
}
echo "Exit runme\n";
}
在实际应用程序中,您可能会将 $socket
存储为 class 成员变量。
基于 this answer, I've switched to pecl-event 库。现在我有:
[root]# php -v
PHP 7.1.12 (cli) (built: Nov 22 2017 08:40:02) ( NTS ) Copyright (c) 1997-2017 The PHP Group Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologieswith Zend OPcache v7.1.12, Copyright (c) 1999-2017, by Zend Technologies
[root]# php --info | grep event
/etc/php.d/event.ini event libevent2 headers version => 2.1.8-stable
[root]# pecl list
Installed packages, channel pecl.php.net:
=========================================
Package Version State
event 2.3.0 stable
下面的例子表现得很奇怪。如果从 runme()
函数内部调用 $loop->run()
,它会工作并调用回调。但是如果从 runme()
之外调用 $loop->run()
,它就会挂起!
require_once __DIR__.'/../vendor/autoload.php';
$inner = count($argv) > 1;
$loop = new \React\EventLoop\ExtEventLoop();
//$loop = new \React\EventLoop\StreamSelectLoop();
runme($loop, $inner);
if (!$inner) {
echo "Outer start\n";
$loop->run();
}
function runme(\React\EventLoop\LoopInterface $loop, $inner)
{
$contextOpts = [];
$flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
$context = stream_context_create($contextOpts);
$socket = stream_socket_client('tcp://127.0.0.1:3306', $errno, $errstr, 0, $flags, $context);
stream_set_blocking($socket, 0);
$loop->addWriteStream($socket, function ($socket) use ($loop) {
echo "done ".(false === stream_socket_get_name($socket, true) ? 'false' : 'true')."\n";
$loop->removeWriteStream($socket);
});
if ($inner) {
echo "Inner start\n";
$loop->run();
}
echo "Exit runme\n";
}
运行 结果:
[root@vultr Scraper]# php ./tests/test.php --inner
Inner start
done false
Exit runme
[root@vultr Scraper]# php ./tests/test.php
Exit runme
Outer start
...............HANGING HERE...........
我是不是遗漏了什么,或者这是 libraries/PHP 之一的问题?有人有 运行 php7.1 + react + libevent 的经验吗?
更新:=========================================== =========================
我用最新的 "react/socket" 库“0.8.6”进行了测试。
require_once __DIR__.'/vendor/autoload.php';
$inner = count($argv) > 1;
$loop = new \React\EventLoop\ExtEventLoop();
$connector = new React\Socket\Connector($loop);
runme($loop, $connector, $inner);
if (!$inner) {
echo "Outer start\n";
$loop->run();
}
function runme(\React\EventLoop\LoopInterface $loop, React\Socket\Connector $connector, $inner)
{
$connector->connect('tcp://127.0.0.1:3306')->
then(function (\React\Socket\ConnectionInterface $conn) {
echo ("Hello MySQL!\n");
$conn->close();
},function ($e) {
echo ("Bye MySQL!\n");
})->done();
if ($inner) {
echo "Inner start\n";
$loop->run();
}
echo "Exit runme\n";
}
它工作正常并且returns:
$ php ./testMysql.php
Exit runme
Outer start
Hello MySQL!
$ php ./testMysql.php --inner
Inner start
Hello MySQL!
Exit runme
但是如果你进入 \React\Socket\TcpConnector::waitForStreamOnce() 并删除新 Promise 对象中的 $canceller 函数,如下所示,它将再次挂起。看起来它在最新版本的 React 中工作是一种意外,因为套接字没有以明显的方式存储,实际上类似于 v0.4.6 中的代码。
private function waitForStreamOnce($stream)
{
$loop = $this->loop;
return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream) {
$loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject) {
$loop->removeWriteStream($stream);
// The following hack looks like the only way to
// detect connection refused errors with PHP's stream sockets.
if (false === stream_socket_get_name($stream, true)) {
fclose($stream);
$reject(new \RuntimeException('Connection refused'));
} else {
$resolve(new Connection($stream, $loop));
}
});
});
}
$ php ./testMysql.php --inner
Inner start
.....HANGING
$ php ./testMysql.php
Exit runme
Outer start
...HANGING
嘿 ReactPHP 核心开发人员,刚刚查看了您的脚本,我可以在本地重现它,所以我将为此提交一个问题(即使这可能超出我们的范围)。
require_once __DIR__.'/../vendor/autoload.php';
$inner = count($argv) > 1;
$loop = new \React\EventLoop\ExtEventLoop();
//$loop = new \React\EventLoop\StreamSelectLoop();
runme($loop, $inner);
$contextOpts = [];
$flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
$context = stream_context_create($contextOpts);
$socket = stream_socket_client('tcp://127.0.0.1:3306', $errno, $errstr, 0, $flags, $context);
stream_set_blocking($socket, 0);
$loop->addWriteStream($socket, function ($socket) use ($loop) {
echo "done ".(false === stream_socket_get_name($socket, true) ? 'false' : 'true')."\n";
$loop->removeWriteStream($socket);
});
if ($inner) {
echo "Inner start\n";
$loop->run();
}
echo "Exit runme\n";
但我建议您查看我们的 socket component 来处理连接。
可能看起来像:
$loop = new \React\EventLoop\ExtEventLoop();
$connector = new React\Socket\Connector($loop);
$connector->connect('tcp://127.0.0.1:3306')->then(function (ConnectionInterface $conn) use ($loop) {
$conn->write("Hello MySQL!\n");
});
$loop->run();
问题是 $socket
变量 destroyed 当 runme()
returns(就像任何本地 PHP 变量一样!)。结果,在此套接字 上打开的 连接 已关闭 。
事件扩展尽最大努力防止内存泄漏,因此它尽可能不存储对用户变量的引用。特别是,所有方法都接受来自输入变量的 socket resource (Event::__construct
, for instance) only retrieve 底层数字文件描述符。 用户实际上负责保持那些变量存活 .
以下脚本通过将 $socket
移至全局范围解决了该问题。
require_once 'vendor/autoload.php';
$inner = count($argv) > 1;
$loop = new \React\EventLoop\ExtEventLoop();
$socket = init_socket();
runme($loop, $socket, $inner);
if (!$inner) {
echo "Outer start\n";
$loop->run();
}
function init_socket()
{
$contextOpts = [];
$flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
$context = stream_context_create($contextOpts);
$socket = stream_socket_client('tcp://test.local:80', $errno, $errstr, 0, $flags, $context);
stream_set_blocking($socket, 0);
return $socket;
}
function runme(\React\EventLoop\LoopInterface $loop, $socket, $inner)
{
$loop->addWriteStream($socket, function ($socket) use ($loop) {
echo "done ".(false === stream_socket_get_name($socket, true) ? 'false' : 'true')."\n";
$loop->removeWriteStream($socket);
});
if ($inner) {
echo "Inner start\n";
$loop->run();
}
echo "Exit runme\n";
}
在实际应用程序中,您可能会将 $socket
存储为 class 成员变量。