Closed sidyhe closed 2 years ago
这个库会切换socket的block状态, 然后循环调用recv/poll以探测数据, 卡死在这里 (pvio_socket_read) 调试后发现是
poll
异常, fiber库替换了系统实现, 当超时触发acl_fiber_signal
后会给fiber置FIBER_F_SIGNALED
标志 导致后续调用recv
时始终返回EAGAIN (11), 因为在acl_fiber_read
中acl_fiber_canceled
始终成立
请问你那儿如何知道是通过触发了 acl_fiber_signal 后导致的?这个API一般都是一个协程关闭另一个协程时才会被调用.
过程
fe->fiber_r->errnum
(EAGAIN)FIBER_F_SIGNALED
acl_fiber_signal
会设置此标志, 而新生的fiber的flag是0, 且此标志一旦设置永远存在acl_fiber_signal
下断点, 观察调用栈, 出现xxx_timeout字样, 具体上下文记不清了, 所以判定是此标志导致的问题部分mariadb代码如下
while ((r = ma_recv(csock->socket, (void *)buffer, length, read_flags)) == -1)
{
int err = socket_errno;
if ((err != SOCKET_EAGAIN
#ifdef HAVE_SOCKET_EWOULDBLOCK
&& err != SOCKET_EWOULDBLOCK
#endif
) || timeout == 0)
return r;
if (pvio_socket_wait_io_or_timeout(pvio, TRUE, timeout) < 1)
return -1;
}
情况是ma_recv返回-1, 且errno == EAGAIN
ma_recv()
是recv()
的封装
pvio_socket_wait_io_or_timeout()
是poll()
的封装
测试环境是单线程单协程, 个人认为没有其他因素的干扰
我看了一下代码,在 hook/socket.c中 hook 的 setsockopt API 中,确定有一处,就是当应用通过 setsocket 设置读写超时,在这个 hooked setsockopt 中会启用一个定时器 acl_fiber_create_timer,回调为 fiber_timeout,在该回调中会调用 acl_fiber_signal,是不是在 mariadb 中有通过 setsockopt 设置了读写超时的行为?
是的, 会用setsockopt设置超时
我下了mariadb,看到其在开始连接时使用 setsockopt 设置了超时,然后在读时使用poll等待读超时,所以你提的PR应该是正确的,非常感谢!
SO_RCVTIMEO和SO_SNDTIMEO的处理是不是过于复杂了? 调用setsockopt的时候就启动了一个定时器, 其实此时并不代表接下来会立马调用send或recv 假设设置recv超时之后, 做了一些其他事情, 会出现意外情况 可能会提前到达超时(提前计时)或者超时无效(超时已触发但还没调用recv)
所以我认为:
刚才还想使用CancelIoEx (win32)来优雅的中断, 但是没找到unix对应的方法, 只好搁置
SO_RCVTIMEO和SO_SNDTIMEO的处理是不是过于复杂了? 调用setsockopt的时候就启动了一个定时器, 其实此时并不代表接下来会立马调用send或recv 假设设置recv超时之后, 做了一些其他事情, 会出现意外情况 可能会提前到达超时(提前计时)或者超时无效(超时已触发但还没调用recv)
所以我认为:
- 不需要额外fiber辅助
- 调用setsockopt时应当记录这两个超时的值
- 在event_process中找到具有timeout标记的fd, 并取最小值
- 像event_process_poll/event_process_epoll那样处理timeout的fd并设置错误码
插一点, 在acl_fiber_recv/acl_fiber_send中设置倒计时开始时间
是的,这是一个优化点,尤其是定时器这块,之前已经把 poll 里超时的定时器做过优化,在大并发时性能提升比较明显。
仅在linux下出现 (linux mint, gcc 9.4.0)
复现步骤:
原因: 这个库会切换socket的block状态, 然后循环调用recv/poll以探测数据, 卡死在这里 (pvio_socket_read) 调试后发现是
poll
异常, fiber库替换了系统实现, 当超时触发acl_fiber_signal
后会给fiber置FIBER_F_SIGNALED
标志 导致后续调用recv
时始终返回EAGAIN (11), 因为在acl_fiber_read
中acl_fiber_canceled
始终成立解决方法: 所以在signal触发后, fiber switch回来后再把标志去掉
然后发现io count计数不正常, add read 后没有触发read callback, 也是因为上述原因没有触发回调 而io的计数是分散的, 所以改到了一起, 切换前count++切回来count--效果应该是一样的
此PR可能有对acl fiber理解不对的地方, 请作者考虑后再合并