某些用户,机器重启后,到第三方服务器的连接起不来,而到我们自己服务器的连接就没事。
如果连接由于网络或其他原因fail掉,过一定时间后应该会重新尝试建立连接的。
测试组做初步调查,他们能在本地环境复现,只是不是稳定复现,时而有时而没有。(嗯,race condition的问题就常常比较飘忽不定,并且往往在系统繁忙负载高的时候爆发)。我开始参与。
分析了一下现有的log,不能得出任何结论。所以在代码中另外增加了一些debug log,然后测试组再跑... 最后终于看到问题根本。
我们有两种类型的链路,分开管理,但基本的socket特性是共享代码的。
这个到第三方服务器的接口,是没有应用层的保活(或者说心跳)协议的。需要依靠TCP层的状态和事件来管理连接。并且这个第三方也禁止持久连接,只能需要发请求的时候按需建立连接。空闲时(没有请求需要发送),需要把连接关闭。所以连接内部有一种状态是"Idle Closed", 表示连接因为闲置而关闭但相信是健康的。
最初代码里的设计是有点倾向异步的。跟我们自己的服务器的链路,连接是这样建立的:一个线程对一个非阻塞socket调用connect() ,(将状态标识为Connecting),然后另外一个线程对所有在Connecting 状态的socket做select()来探测可写事件。如果捕获可写事件,则连接状态变为Connected。
也许部分是由于第三方链路的非持久特性,或是写代码的简易性(调用一个函数返回之后连接要么建立成功要么失败,明显代码流简单很多),跟第三方服务器的连接是同步地建立的。一个线程调用名字像****ConnectSync()这样的一个函数来同步地建立连接。然而,里面用到的系统调用却不是同步的那一个connect:对一个非阻塞socket调用connect(),(将状态标识为Connecting),然后阻塞在一个select()调用。select() 得到可写事件或者超时****ConnectSync()就返回,这样****ConnectSync() 就看起来像同步的。
如果一个socket在****ConnectSync()被设为Connecting状态,另外一个线程会对这个socket调用select() 来探测可写事件。
两个线程对同一个socket调用select(),只有其中一个线程可以获得代表连接被建立起来的可写事件 (至少在Windows平台上是这样)。
所以,一个线程把连接标识为connected 然后idle closed掉它,另外一个线程认为连接失败(因为没得到可写事件)所以把连接标识为not Available。
如果一个链路即是 idle closed的又是not Available的,那将不再对它做链路测试(因为idle closed表明链路是健康的),也不会再有请求来的时候选中它来建立连接(not Available所以不会被选中)。(嗯,链路的多个标志位也让人看起来有点混乱) 所以到第三方服务器的链路才会永久性失败,因为再也不会尝试它了。
修复很明显...
-------------------------------------------------------------------------------------------------