Describe the bug (描述bug)
使用过程中发现存在极小概率的socket始终无法重连,debug发现内部的Socket::WaitAndReset函数始终无法等到引用计数释放完不会重连,导致其他请求无法通过Address获取到Socket
和issue #1168 中描述的问题有点类似

socket请求失败触发HealthCheck进入Socket::WaitAndReset等待已有的请求引用计数清零后再close fd并重新connect建立链接
但是这里现象是图中的条件NRefOfVRef(vref)是中是3,大于expected_nref=2,导致这里陷入死循环无法重连

上图中标记了三处代码位置,3的触发条件常见的主要是socket出错(例如对端close连接),Socket::SetFailed需要把Socket::Create增加的refcount减掉,会进入Socket::ReleaseAdditionalReference这个逻辑,为了防止多次减引用,引入了_recycle_flag这个flag,默认false,这里利用cas操作将
_recycle_flag设置为true,后面再进入这里就不会再次减引用了,保证引用只减一次(仅仅是Socket::Create中增加的refcount)
Socket::Revive是用于
- Socket::SetFailed
- 变更_versioned_ref中的version使得socket无法被Address到,避免socket refcount继续增加
- 触发health check -> HealthCheckTask::OnTriggeringTask
- SocketWaitAndReset等待所有的请求完成等待socket refcount=2(一个是SocketMap中的引用,一个是当前的OnTriggeringTask socket引用,=2说明没有其他的引用了)
- close fd
- connect new fd
- Socket::Revive将_versioned_ref中的version修改回去,使得这个socket可以被Address到了,表明这个Socket“复活”了,同时也会把refcount+1和Socket::Create一样增加一次ref count,并且将_recycle_flag设置为false,方便后续再次到Socket::ReleaseAdditionalReference好把这个额外的ref count释放掉
但是图中1和2之间的atomic操作有间隙,1执行完后,Socket::Address就已经可以获取到Socket了,这是问题关键所在
假如1执行完成后刚好pthread被os切走了,此时另一个request1通过Socket::Address获取到了这个socket,但是请求还是失败了,再次进入Socket::SetFailed->变更_versioned_ref中的version使得socket无法被Address到,避免socket refcount继续增加 -> 触发health check -> HealthCheckTask::OnTriggeringTask-> Socket::ReleaseAdditionalReference
此时由于_recycle_flag还是true,导致这里cas不成立不会进入Dereference,引用计数也就不会减,并且其他请求也Address不到这个socket了,而此时HealthCheckTask::OnTriggeringTask会再一次进入Socket::WaitAndReset,就会发现nref永远是3,而不是2
To Reproduce (复现方法)
可以使用brpc example里面的client server测试例子,将上图中1,2之间加一个bthread_usleep(10000*1000) -> sleep 10s模拟两个操作之间pthread被切走的情况,然后
./server
./client
restart server 为了触发client端第一个SetFailed进入健康检查并进入到Socket::Revive 1的位置(cas成功之后)开始sleep
restart server 在sleep完成之前,为了触发client端第二个SetFailed并且没有把socket refcount-1
就可以稳定复现该问题
Expected behavior (期望行为)
Versions (各种版本)
各种版本
Additional context/screenshots (更多上下文/截图)
Describe the bug (描述bug)
使用过程中发现存在极小概率的socket始终无法重连,debug发现内部的Socket::WaitAndReset函数始终无法等到引用计数释放完不会重连,导致其他请求无法通过Address获取到Socket
和issue #1168 中描述的问题有点类似
socket请求失败触发HealthCheck进入Socket::WaitAndReset等待已有的请求引用计数清零后再close fd并重新connect建立链接
但是这里现象是图中的条件NRefOfVRef(vref)是中是3,大于expected_nref=2,导致这里陷入死循环无法重连
上图中标记了三处代码位置,3的触发条件常见的主要是socket出错(例如对端close连接),Socket::SetFailed需要把Socket::Create增加的refcount减掉,会进入Socket::ReleaseAdditionalReference这个逻辑,为了防止多次减引用,引入了_recycle_flag这个flag,默认false,这里利用cas操作将
_recycle_flag设置为true,后面再进入这里就不会再次减引用了,保证引用只减一次(仅仅是Socket::Create中增加的refcount)
Socket::Revive是用于
但是图中1和2之间的atomic操作有间隙,1执行完后,Socket::Address就已经可以获取到Socket了,这是问题关键所在
假如1执行完成后刚好pthread被os切走了,此时另一个request1通过Socket::Address获取到了这个socket,但是请求还是失败了,再次进入Socket::SetFailed->变更_versioned_ref中的version使得socket无法被Address到,避免socket refcount继续增加 -> 触发health check -> HealthCheckTask::OnTriggeringTask-> Socket::ReleaseAdditionalReference
此时由于_recycle_flag还是true,导致这里cas不成立不会进入Dereference,引用计数也就不会减,并且其他请求也Address不到这个socket了,而此时HealthCheckTask::OnTriggeringTask会再一次进入Socket::WaitAndReset,就会发现nref永远是3,而不是2
To Reproduce (复现方法)
可以使用brpc example里面的client server测试例子,将上图中1,2之间加一个bthread_usleep(10000*1000) -> sleep 10s模拟两个操作之间pthread被切走的情况,然后
./server
./client
restart server 为了触发client端第一个SetFailed进入健康检查并进入到Socket::Revive 1的位置(cas成功之后)开始sleep
restart server 在sleep完成之前,为了触发client端第二个SetFailed并且没有把socket refcount-1
就可以稳定复现该问题
Expected behavior (期望行为)
Versions (各种版本)
各种版本
Additional context/screenshots (更多上下文/截图)