Skip to content

brpc socket引用计数极端场景下少减了一次,导致socket无法建立新连接 #1773

Description

@zhaodongzhi

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

image

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

image
上图中标记了三处代码位置,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 (更多上下文/截图)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions