zhaodongzhi opened a new issue, #1773:
URL: https://github.com/apache/incubator-brpc/issues/1773

   **Describe the bug (描述bug)**
   
使用过程中发现存在极小概率的socket始终无法重连,debug发现内部的Socket::WaitAndReset函数始终无法等到引用计数释放完不会重连,导致其他请求无法通过Address获取到Socket
   和issue https://github.com/apache/incubator-brpc/issues/1168 中描述的问题有点类似
   
   
![image](https://user-images.githubusercontent.com/6293650/171837136-692cddf2-6d36-4dff-834b-8b94ab511e46.png)
   
   socket请求失败触发HealthCheck进入Socket::WaitAndReset等待已有的请求引用计数清零后再close 
fd并重新connect建立链接
   但是这里现象是图中的条件NRefOfVRef(vref)是中是3,大于expected_nref=2,导致这里陷入死循环无法重连
   
   
![image](https://user-images.githubusercontent.com/6293650/171837239-39ccb255-c528-4ca4-bc0d-476b0e7643aa.png)
   
上图中标记了三处代码位置,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 (更多上下文/截图)**
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to