Asynchronous Php 7.1+;Pecl事件+;libevent-挂在奇怪的箱子里

Asynchronous Php 7.1+;Pecl事件+;libevent-挂在奇怪的箱子里,asynchronous,libevent,php-7.1,reactphp,pecl-event,Asynchronous,Libevent,Php 7.1,Reactphp,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 T

基于这一点,我转到了图书馆。现在我有:

[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 uuu DIR_uu.'/../vendor/autoload.php';
$inner=计数($argv)>1;
$loop=new\React\EventLoop\ExtEventLoop();
//$loop=new\React\EventLoop\StreamSelectLoop();
runme($loop,$inner);
如果(!$inner){
回显“外部启动\n”;
$loop->run();
}
函数运行名(\React\EventLoop\LoopInterface$loop,$inner)
{
$contextOpts=[];
$flags=STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
$context=stream\u context\u create($contextOpts);
$socket=stream\u socket\u客户端($socket)tcp://127.0.0.1:3306“,$errno,$errstr,0,$flags,$context);
流设置阻塞($socket,0);
$loop->addWriteStream($socket,函数($socket)use($loop){
echo“done”。(false==stream\u socket\u get\u name($socket,true)?'false':'true')。“\n”;
$loop->removeWriteStream($socket);
});
如果($内部){
回显“内部启动\n”;
$loop->run();
}
回显“退出运行时\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...........
我是否遗漏了一些东西,或者是某个库/PHP的问题?有人有运行php7.1+react+libevent的经验吗

更新:====================================================================

我使用最新的“react/socket”库“0.8.6”进行了测试

它正常工作并返回:

$ php ./testMysql.php 
Exit runme
Outer start
Hello MySQL!
$ php ./testMysql.php  --inner
Inner start
Hello MySQL!
Exit runme
但如果您进入\React\Socket\TcpConnector::waitForStreamance()并删除新Promise对象中的$canceler函数,如下图所示,它会再次挂起。看起来它在最新版本的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

嘿,这里是PHP核心开发人员,只要查看一下您的脚本,我就可以在本地复制它,所以我将为此提交一个问题(尽管这可能超出了我们的范围)

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";
但我建议你也可以考虑一下我们的联系方式

这可能看起来像:

$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();
问题是当
runme()
返回时,
$socket
变量会被销毁(就像任何本地PHP变量一样!)。因此,在此插槽上打开的连接将关闭

事件扩展尽最大努力防止内存泄漏,因此尽可能不存储对用户变量的引用。特别是,所有只接受(,例如)输入变量的底层数字文件描述符的方法。用户实际上负责保持这些变量处于活动状态

以下脚本通过将
$socket
移动到全局范围来修复此问题

require_once'vendor/autoload.php';
$inner=计数($argv)>1;
$loop=new\React\EventLoop\ExtEventLoop();
$socket=init_socket();
runme($loop,$socket,$inner);
如果(!$inner){
回显“外部启动\n”;
$loop->run();
}
函数init_socket()
{
$contextOpts=[];
$flags=STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
$context=stream\u context\u create($contextOpts);
$socket=stream\u socket\u客户端($socket)tcp://test.local:80“,$errno,$errstr,0,$flags,$context);
流设置阻塞($socket,0);
返回$socket;
}
函数运行名(\React\EventLoop\LoopInterface$loop、$socket、$inner)
{
$loop->addWriteStream($socket,函数($socket)use($loop){
echo“done”。(false==stream\u socket\u get\u name($socket,true)?'false':'true')。“\n”;
$loop->removeWriteStream($socket);
});
如果($内部){
回显“内部启动\n”;
$loop->run();
}
回显“退出运行时\n”;
}

在实际应用程序中,您可能会将
$socket
存储为类成员变量。

谢谢您的帮助。实际上,我从使用react+react/mysql库的应用程序中提取了上述代码,该库基于\react\SocketClient\Connector()。应用程序在php5.6+libevent 2.0下运行良好。但升级到php7.1后,我无法将可行的解决方案与事件库结合起来。这是异步编程,尤其是事件扩展的常见问题。用户通常没有意识到扩展没有保留对用于配置事件的输入变量的引用。(
$data
参数是特殊的。)从用户的角度来看,只将套接字传递给某个事件构造函数,然后忘记它可能会很方便。但是,这种行为还要求用户在不再需要引用时释放引用。在我看来,后一种情况使得异步编程更加困难。正如我所写的,这段代码是react/socket客户端库v0.4.6的一部分,它在php5.6中运行良好。因此,我似乎必须更新库。在我编写时,这段代码是react/socket客户端库v0.4.6的一部分,它在php5.6中运行良好。因此,我似乎必须在我的应用程序中找到更新此库的方法。@DmitryPismennyy,即使脚本在PHP5.6上按预期工作,也只是因为GC行为认为不会破坏
$socket
。我已经描述了问题的根源:必须保留
$socket
变量(!);否则,连接将丢失,循环的行为将不可预测(它可能会挂起,或者可能会发生故障…如果程序在闭合的文件描述符上运行,您会期望从程序中得到什么?)。这个想法也适用于PHP7,我不是说更新PHP5.6中的工作代码。我的意思是在我的主要帖子中根据最新的react库进行更新,该库乍一看就可以正常工作。@DmitryPismennyy,你问题的第二部分看起来完全不同
$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();