yyzybb537 / libgo

Go-style concurrency in C++11
MIT License
3.22k stars 756 forks source link

tcp读取数据的时候,断开后一段时间后出现死锁 #302

Open gooker opened 1 year ago

gooker commented 1 year ago

centos7.8 gcc 10

场景很常见,目前一个设备,大约每秒1次执行下协程读取网口状态, 开始网络正常,然后断开设备网络,几个小时甚至更多就出现死锁,如下下数据 @yyzybb537 这个锁是libgo内部的吧

 go co_scheduler(sched_test) [=](){
                dev_tcp_status_check(id);
            };

死锁情况

Thread 10 (Thread 0x7f9a267c0700 (LWP 47760)):
#0  0x00007f9a2e0c854d in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x00007f9a2e0c3e9b in _L_lock_883 () from /lib64/libpthread.so.0
#2  0x00007f9a2e0c3d68 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x0000000000bec9aa in __gthread_mutex_lock (__mutex=<optimized out>) at /opt/rh/devtoolset-10/root/usr/include/c++/10/x86_64-redhat-linux/bits/gthr-default.h:749
#4  std::mutex::lock (this=<optimized out>) at /opt/rh/devtoolset-10/root/usr/include/c++/10/bits/std_mutex.h:100
#5  std::unique_lock<std::mutex>::lock (this=0x7f9a267bc900) at /opt/rh/devtoolset-10/root/usr/include/c++/10/bits/unique_lock.h:138
#6  std::unique_lock<std::mutex>::unique_lock (__m=..., this=0x7f9a267bc900) at /opt/rh/devtoolset-10/root/usr/include/c++/10/bits/unique_lock.h:68
#7  libgo::RoutineSyncTimerT<std::mutex, std::condition_variable>::join_unschedule (id=..., this=0x7f9a2000a050) at /mnt/hgfs/vmshare/github/libgo/libgo/scheduler/../task/../routine_sync/timer.h:128
#8  co::Processer::WakeupBySelf(co::IncursivePtr<co::Task> const&, unsigned long, std::function<void ()> const&) (this=0x208c360, tkPtr=..., id=<optimized out>, functor=...) at /mnt/hgfs/vmshare/github/libgo/libgo/scheduler/processer.cpp:384
#9  0x0000000000bed19f in co::Processer::Wakeup(co::Processer::SuspendEntry const&, std::function<void ()> const&) (entry=..., functor=...) at /mnt/hgfs/vmshare/github/libgo/libgo/scheduler/processer.cpp:366
#10 0x0000000000c081f1 in co::ReactorElement::TriggerListWithoutLock (this=<optimized out>, revent=28, entryList=std::vector of length 1, capacity 1 = {...}) at /opt/rh/devtoolset-10/root/usr/include/c++/10/bits/std_function.h:337
#11 0x0000000000c088c8 in co::ReactorElement::Trigger (this=this@entry=0x7f9a08001510, reactor=reactor@entry=0x7f9a2000a5c0, pollEvent=<optimized out>) at /mnt/hgfs/vmshare/github/libgo/libgo/netio/unix/reactor_element.cpp:78
#12 0x0000000000c0dc3a in co::EpollReactor::Run (this=0x7f9a2000a5c0) at /mnt/hgfs/vmshare/github/libgo/libgo/netio/unix/epoll_reactor.cpp:97
#13 0x0000000000c07711 in operator() (__closure=0x7f9a2000a5e8) at /mnt/hgfs/vmshare/github/libgo/libgo/netio/unix/reactor.cpp:47
#14 std::__invoke_impl<void, co::Reactor::InitLoopThread()::<lambda()> > (__f=...) at /opt/rh/devtoolset-10/root/usr/include/c++/10/bits/invoke.h:60
#15 std::__invoke<co::Reactor::InitLoopThread()::<lambda()> > (__fn=...) at /opt/rh/devtoolset-10/root/usr/include/c++/10/bits/invoke.h:95
#16 std::thread::_Invoker<std::tuple<co::Reactor::InitLoopThread()::<lambda()> > >::_M_invoke<0> (this=0x7f9a2000a5e8) at /opt/rh/devtoolset-10/root/usr/include/c++/10/thread:264
#17 std::thread::_Invoker<std::tuple<co::Reactor::InitLoopThread()::<lambda()> > >::operator() (this=0x7f9a2000a5e8) at /opt/rh/devtoolset-10/root/usr/include/c++/10/thread:271
#18 std::thread::_State_impl<std::thread::_Invoker<std::tuple<co::Reactor::InitLoopThread()::<lambda()> > > >::_M_run(void) (this=0x7f9a2000a5e0) at /opt/rh/devtoolset-10/root/usr/include/c++/10/thread:215
#19 0x00007f9a2faba4e0 in execute_native_thread_routine () from /lib/libpaho-mqttpp3.so.1
#20 0x00007f9a2e0c1ea5 in start_thread () from /lib64/libpthread.so.0
#21 0x00007f9a2d5cb8dd in clone () from /lib64/libc.so.6
paradiseforgithub commented 1 year ago

已收到您的邮件,如有问题会尽快给您回复. 

gooker commented 1 year ago

连接的时候使用如下的非阻塞方式,不确定对libgo 有没有影响


//非阻塞方式连接
        int error=-1, len;
        len = sizeof(int);
        struct timeval tm;
        fd_set set;
        unsigned long ul = 1;
        ioctl(sockfd, FIONBIO, &ul); //设置为非阻塞模式
        if( connect(sockfd,(struct sockaddr *)&plcaddr,sizeof(plcaddr)) == -1)
        {
            tm={out_time,0};//10s
            //tm.tv_set = TIME_OUT_TIME;
            //tm.tv_uset = 0;
            FD_ZERO(&set);
            FD_SET(sockfd, &set);
            if(select(sockfd+1, NULL, &set, NULL, &tm) > 0)
            {
                getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
                if(error == 0)
                    ret = true;
                else
                    ret = false;
            }
            else
                ret = false;
        }
        else
            ret = true;

        ul = 0;
        ioctl(sockfd, FIONBIO, &ul); //设置为阻塞模式
qinrhx commented 1 year ago

这库还维护不?我也遇到了死锁,服务跑几天就会出现,__lll_lock_wait只有一个线程出来,然后好几个线程在LFLock上面,把cpu都跑满了,提交的协程也不再执行,截几个图出来看有没帮助 image image image image image

gooker commented 1 year ago

@qinrhx 这个协程调用函数有锁,协程外也对这个锁也有处理,容易锁. 我这里只是用了tcp非阻塞遇到问题,改成阻塞貌似还正常.

qinrhx commented 1 year ago

@qinrhx 这个协程调用函数有锁,协程外也对这个锁也有处理,容易锁. 我这里只是用了tcp非阻塞遇到问题,改成阻塞貌似还正常.

我看更新记录,近期有更换了协程锁套件。应该是这个锁套件有bug,我换回原来的版本,跑了一个星期没出现死锁。 @gooker

gooker commented 1 year ago

@qinrhx 多谢,等有时间我试试,不好复现

SignalEmit commented 1 year ago

我也遇到了这个问题,难受的一逼莫名奇妙死锁了。你用的是哪个版本

SignalEmit commented 1 year ago

@qinrhx 这个协程调用函数有锁,协程外也对这个锁也有处理,容易锁. 我这里只是用了tcp非阻塞遇到问题,改成阻塞貌似还正常.

我看更新记录,近期有更换了协程锁套件。应该是这个锁套件有bug,我换回原来的版本,跑了一个星期没出现死锁。 @gooker

你换的哪个版本啊

SignalEmit commented 1 year ago

@qinrhx 这个协程调用函数有锁,协程外也对这个锁也有处理,容易锁. 我这里只是用了tcp非阻塞遇到问题,改成阻塞貌似还正常.

我看更新记录,近期有更换了协程锁套件。应该是这个锁套件有bug,我换回原来的版本,跑了一个星期没出现死锁。 @gooker

@gooker 你用的哪个版本啊,跪求

gooker commented 1 year ago

@qinrhx 这个协程调用函数有锁,协程外也对这个锁也有处理,容易锁. 我这里只是用了tcp非阻塞遇到问题,改成阻塞貌似还正常.

我看更新记录,近期有更换了协程锁套件。应该是这个锁套件有bug,我换回原来的版本,跑了一个星期没出现死锁。 @gooker

@gooker 你用的哪个版本啊,跪求

我用的最新的,暂时没测试没换版本

SignalEmit commented 1 year ago

@qinrhx 这个协程调用函数有锁,协程外也对这个锁也有处理,容易锁. 我这里只是用了tcp非阻塞遇到问题,改成阻塞貌似还正常.

我看更新记录,近期有更换了协程锁套件。应该是这个锁套件有bug,我换回原来的版本,跑了一个星期没出现死锁。 @gooker

@gooker 你用的哪个版本啊,跪求

我用的最新的,暂时没测试没换版本

@gooker matser分支不是还存在死锁问题嘛,我换3.1-stable发现这个版本有bug,携程有时候会空转

qinrhx commented 1 year ago

@qinrhx 这个协程调用函数有锁,协程外也对这个锁也有处理,容易锁. 我这里只是用了tcp非阻塞遇到问题,改成阻塞貌似还正常.

我看更新记录,近期有更换了协程锁套件。应该是这个锁套件有bug,我换回原来的版本,跑了一个星期没出现死锁。 @gooker

你换的哪个版本啊

@SignalEmit 具体哪个版本不记得了,是之前从master下载的,你找master提交记录更换锁套件之前的试试吧,比如这个 image

SignalEmit commented 1 year ago

@qinrhx 这个协程调用函数有锁,协程外也对这个锁也有处理,容易锁. 我这里只是用了tcp非阻塞遇到问题,改成阻塞貌似还正常.

我看更新记录,近期有更换了协程锁套件。应该是这个锁套件有bug,我换回原来的版本,跑了一个星期没出现死锁。 @gooker

你换的哪个版本啊

@SignalEmit 具体哪个版本不记得了,是之前从master下载的,你找master提交记录更换锁套件之前的试试吧,比如这个 image @qinrhx 我切过这个版本,这个版本有bug。举个例子 go[](){ while(true){ sleep(1); } }; 这个休眠挂起这里会出bug,无限空转,sleep也失效了。还会影响其他携程。3.1的稳定版也有这问题

qinrhx commented 1 year ago

@qinrhx 这个协程调用函数有锁,协程外也对这个锁也有处理,容易锁. 我这里只是用了tcp非阻塞遇到问题,改成阻塞貌似还正常.

我看更新记录,近期有更换了协程锁套件。应该是这个锁套件有bug,我换回原来的版本,跑了一个星期没出现死锁。 @gooker

你换的哪个版本啊

@SignalEmit 具体哪个版本不记得了,是之前从master下载的,你找master提交记录更换锁套件之前的试试吧,比如这个 image @qinrhx 我切过这个版本,这个版本有bug。举个例子 go{ while(true){ sleep(1); } }; 这个休眠挂起这里会出bug,无限空转,sleep也失效了。还会影响其他携程。3.1的稳定版也有这问题

哦,这我倒没测过,我没有在协程里做循环休眠的。 之前是听说定时器有内存泄漏,失效的bug,你看看能不能改下业务逻辑避免掉了。 这个库看上去bug不少,也不怎么维护了,所以能不用就别用了。。。要用也简单用

SignalEmit commented 1 year ago

@gooker @qinrhx

我这两天仔细看了一下这个代码,发现这个锁加的不太好,巨大的区域锁(有空了代码仔细看明白了,我重构重构这个锁)。上面那个tcp模块的死锁我找到原因了 复现方法:写了个httpserver epoll方式。开50个client并发,一直发发get请求,不出30秒就死锁了。

测试出了两处死锁

死锁1:

线程1 routine_sync/timer.h run函数 timer.h 150行 bool locked = invoke_lock.try_lock(); scheduler/processer.cpp WakeupBySelf函数 processer:cpp 381行 std::unique_lock lock(waitQueue_.LockRef());

线程2 scheduler/processer.cpp WakeupBySelf函数 processer:cpp 381行 std::unique_lock lock(waitQueue_.LockRef()); routine_sync/timer.h run函数 timer.h 128行 std::unique_lock invoke_lock(*invoke_mtx);

我仔细看了一下代码waitQueue_.LockRef()这个锁不好改,这个锁不仅锁携程调度队列,还要task,改了会导致压入的数据校验的时候,多线程会置空。我得做法是屏蔽掉 routine_sync/timer.h run函数 timer.h 128行 std::unique_lock invoke_lock(*invoke_mtx); 写了个http并发示例测试了半个小时暂时没发现有异常。代码没细细研究,暂时不清楚拆了这块会导致什么异常问题。暂时看没发现有崩溃情况。

死锁2: 打开debug模式 DebugPrint宏里有个锁和TSQueue里的队列锁会形成死锁。

我得处理办法是 TSQueue size计数换成原子计数

SignalEmit commented 1 year ago

@gooker @qinrhx

我这两天仔细看了一下这个代码,发现这个锁加的不太好,巨大的区域锁(有空了代码仔细看明白了,我重构重构这个锁)。上面那个tcp模块的死锁我找到原因了 复现方法:写了个httpserver epoll方式。开50个client并发,一直发发get请求,不出30秒就死锁了。

测试出了两处死锁

死锁1:

线程1 routine_sync/timer.h run函数 timer.h 150行 bool locked = invoke_lock.try_lock(); scheduler/processer.cpp WakeupBySelf函数 processer:cpp 381行 std::unique_lock lock(waitQueue_.LockRef());

线程2 scheduler/processer.cpp WakeupBySelf函数 processer:cpp 381行 std::unique_lock lock(waitQueue_.LockRef()); routine_sync/timer.h run函数 timer.h 128行 std::unique_lock invoke_lock(*invoke_mtx);

我仔细看了一下代码waitQueue_.LockRef()这个锁不好改,这个锁不仅锁携程调度队列,还要task,改了会导致压入的数据校验的时候,多线程会置空。我得做法是屏蔽掉 routine_sync/timer.h run函数 timer.h 128行 std::unique_lock invoke_lock(*invoke_mtx); 写了个http并发示例测试了半个小时暂时没发现有异常。代码没细细研究,暂时不清楚拆了这块会导致什么异常问题。暂时看没发现有崩溃情况。

死锁2: 打开debug模式 DebugPrint宏里有个锁和TSQueue里的队列锁会形成死锁。

我得处理办法是 TSQueue size计数换成原子计数

nqf commented 1 year ago

现在这个库不怎么维护了是大问题,都不敢用了