libhv icon indicating copy to clipboard operation
libhv copied to clipboard

多个TcpClient/UdpClient共用一个EventLoop,析构时有概率崩溃

Open hello-Li-sir opened this issue 9 months ago • 13 comments

Image Image

hello-Li-sir avatar Apr 01 '25 06:04 hello-Li-sir

退出线程时应该等待IO线程结束,return 0前,加

loopThread.stop();
loopThread.join();

试试

ithewei avatar Apr 01 '25 07:04 ithewei

没用,return 0之前加过死循环,IO线程没结束也会崩

------------------ 原始邮件 ------------------ 发件人: "ithewei/libhv" @.>; 发送时间: 2025年4月1日(星期二) 下午3:44 @.>; @.@.>; 主题: Re: [ithewei/libhv] 多个TcpClient/UdpClient共用一个EventLoop,析构时有概率崩溃 (Issue #704)

退出线程时应该等待IO线程结束,return 0前,加一个loopThread.stop();试试

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***> ithewei left a comment (ithewei/libhv#704)

退出线程时应该等待IO线程结束,return 0前,加一个loopThread.stop();试试

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

hello-Li-sir avatar Apr 01 '25 07:04 hello-Li-sir

最新master分支上有个修复,看看带上没https://github.com/ithewei/libhv/commit/41c63bfbf0e4817711febfbe695609d524d4af85

ithewei avatar Apr 01 '25 07:04 ithewei

用的最新版本 还是有这个问题 window平台

------------------ 原始邮件 ------------------ 发件人: "ithewei/libhv" @.>; 发送时间: 2025年4月1日(星期二) 下午3:54 @.>; @.@.>; 主题: Re: [ithewei/libhv] 多个TcpClient/UdpClient共用一个EventLoop,析构时有概率崩溃 (Issue #704)

最新master分支上有个修复,看看带上没41c63bf

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***> ithewei left a comment (ithewei/libhv#704)

最新master分支上有个修复,看看带上没41c63bf

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

hello-Li-sir avatar Apr 01 '25 08:04 hello-Li-sir

原因是执行启动的时候 loop_->runInLoop(std::bind(&UdpClientEventLoopTmpl::startRecv, this)); 这个是投递启动事件到loop线程异步触发启动,而startRecv还没来得及执行你的UdpClient对象就析构了,导致后续startRecv启动后使用的this指针父类Channel相关内存数据全部无效从而崩溃。

而不使用外部loop时不存在此现象是因为is_loop_owner=true的内部线程stop时会执行终止线程。

要解决这个问题就必须作者想个好办法优化stop逻辑,我发现许多cpp封装对象涉及异步事件执行的地方都可能因this对象析构从而产生崩溃潜在问题,临时解决方案就是调用start之后检测hio的事件是否不等于0来判断异步触发启动完成可以执行后续停止或析构,我在你的代码示例基础增加 while (hio_events(client1.channel->io()) == 0) std::this_thread::yield(); 即可避免崩溃。

@ithewei @hello-Li-sir

Image

House-Men avatar Apr 01 '25 21:04 House-Men

这个跟你分析的原因还不一样,我测试的时候执行完startRecv还是会崩。本身发现的这个问题时候Tcp就已经建立了连接,使用外部loop析构的时候大概率会崩。后面用Udp试了一下,也有这个问题。winodows上出现了,linux上跑了几遍没复现。库是直接用vs studio+源码的cmake编译的。

------------------ 原始邮件 ------------------ 发件人: @.>; 发送时间: 2025年4月2日(星期三) 凌晨5:11 收件人: @.>; 抄送: @.>; @.>; 主题: Re: [ithewei/libhv] 多个TcpClient/UdpClient共用一个EventLoop,析构时有概率崩溃 (Issue #704)

原因是执行启动的时候 loop_->runInLoop(std::bind(&UdpClientEventLoopTmpl::startRecv, this)); 这个是投递启动事件到loop线程异步触发启动,而startRecv还没来得及执行你的UdpClient对象就析构了,导致后续startRecv启动后使用的this指针父类Channel相关内存数据全部无效从而崩溃。

而不使用外部loop时不存在此现象是因为is_loop_owner=true的内部线程stop时会执行终止线程。

要解决这个问题就必须作者想个好办法优化stop逻辑,我发现许多cpp封装对象涉及异步事件执行的地方都可能因this对象析构从而产生崩溃潜在问题,临时解决方案就是调用start之后检测hio的事件是否不等于0来判断异步触发启动完成可以执行后续停止或析构,我在你的代码示例基础增加 while (hio_events(client1.channel->io()) == 0) std::this_thread::yield(); 即可避免崩溃。

@ithewei @hello-Li-sir

image.png (view on web)

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***> House-Men left a comment (ithewei/libhv#704)

原因是执行启动的时候 loop_->runInLoop(std::bind(&UdpClientEventLoopTmpl::startRecv, this)); 这个是投递启动事件到loop线程异步触发启动,而startRecv还没来得及执行你的UdpClient对象就析构了,导致后续startRecv启动后使用的this指针父类Channel相关内存数据全部无效从而崩溃。

而不使用外部loop时不存在此现象是因为is_loop_owner=true的内部线程stop时会执行终止线程。

要解决这个问题就必须作者想个好办法优化stop逻辑,我发现许多cpp封装对象涉及异步事件执行的地方都可能因this对象析构从而产生崩溃潜在问题,临时解决方案就是调用start之后检测hio的事件是否不等于0来判断异步触发启动完成可以执行后续停止或析构,我在你的代码示例基础增加 while (hio_events(client1.channel->io()) == 0) std::this_thread::yield(); 即可避免崩溃。

@ithewei @hello-Li-sir

image.png (view on web)

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

hello-Li-sir avatar Apr 02 '25 00:04 hello-Li-sir

析构会崩问题同源,就像之前那个onClose析构后触发会崩溃的优化提交和你这个例子会析构后触发startRecv崩溃,本质上都是同一个原因,只要用到外部loop内部的停止逻辑都不会等待终止线程,所以你只要出现析构后loop触发了什么事件回调在回调处理中又用到被释放的对象成员数据就会崩溃。但并不是百分百触发,有时候会因为运行环境的不同对象释放后内存被填充成0导致某些异步回调触发判断是否为空条件成立从而避免崩溃。

House-Men avatar Apr 02 '25 06:04 House-Men

是的,这种方式必须十分小心TcpClient/UdpClient的生命周期,最好的实践是使用堆对象,然后调用loop->runInLoop{delete cli;}在事件循环所在线程里删除对象,参考这个示例程序 https://github.com/ithewei/libhv/blob/master/evpp/TcpClientEventLoop_test.cpp

ithewei avatar Apr 02 '25 14:04 ithewei

这个问题彻底解决了吗。libhv稳定了吗... 我自己对libuv也进行了类似现在libhv的c++封装,一样也碰到这些问题。。后来我写为一个c++类实例调用stop()(异步提交释放资源的函数到loop中)(在真正释放之后把类的isRunning变量再置0),在外面用类似getIsRunning()的方法来判断这个对象的资源在loop中是否彻底都运行释放完毕,很稳定。。 所以libhv这里的各种API稳定吗。 我们写c++模块,不是进入main()之后只都一个个start()开始运行一遍然后就不管了,是要能够每个模块能够单独、随时的启停的,是要求能够随时调用start()和stop()的,而且稳定不崩。

Staok avatar Oct 27 '25 10:10 Staok

https://deepwiki.com/ithewei/libhv

Staok avatar Oct 27 '25 11:10 Staok

https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-coding-standard-libuv

Staok avatar Oct 27 '25 11:10 Staok

模块如果没有一个API用来检查是否所有内部资源都已经释放了可以析构,可否加一个,而不必调用一些别的底层的API检测的东东。 文档里面充分说明模块调用stop()之后内部还有一些异步的资源正在释放,需要调用个比如getIsRunning()来检查,真正不running了然后放心的析构。这样可好?

Staok avatar Oct 27 '25 11:10 Staok

是的,这种方式必须十分小心TcpClient/UdpClient的生命周期,最好的实践是使用堆对象,然后调用loop->runInLoop{delete cli;}在事件循环所在线程里删除对象,参考这个示例程序 https://github.com/ithewei/libhv/blob/master/evpp/TcpClientEventLoop_test.cpp

参考这个结论,最佳实践是在loop所在线程去析构TcpClient/UdpClient对象

ithewei avatar Oct 28 '25 03:10 ithewei