ZLMediaKit/ZLToolKit

在Socket::onAccept中 peer_sock与peer_sock_fd的生命周期太长 可能会有问题

hankai17 opened this issue · 10 comments


在Socket::onAccept中 peer_sock与peer_sock_fd的生命周期太长 可能会有问题
eg:
有poller1 poller2两个线程同时监听某端口

  1. poller1 accept一个新链接 并将其绑定到poller2线程(_on_before_accept)
  2. 将新链接加入到poller2上进行监听(attachEvent); 且poller2立即触发epoll_wait并回调上层 上层业务清除该链接(_session_map.erase)
  3. 由于系统原因poller1此时被调度执行 然后poller1 析构peer_sock与peer_sock_fd 并调用close

peer_sock与peer_sock_fd应该在poller2中析构并close 但是步骤3有很小的几率发生 导致跨线程关闭

既然peer_sock已经给poller2管理了 那么智能指针已经共享给poller2线程了 那么在poller1线程就不可能触发该peer_sock引用计数归零close的问题

“那么智能指针已经共享给poller2线程了”
这里如果peer_sock在poller2线程中 非常快的被上层业务清除 poller2线程中也就没有peer_sock的引用计数 那么可能poller1线程中仍有peer_sock的引用计数 于是便在poller1中析构peer_sock

不会的 因为在把所有权转交给poller2线程前 这个fd就没加入到epoll去监听 它本身就产生不了事件

图片

图片

一般情况下 complete 在所绑定的线程(poller2中)析构
即在poller2中 先设置链接的(read/write/err)回调 最后析构complete 将链接加入到poller2的epoll监听

有特殊情况 eg:
在 _on_accept(peer_sock, completed); 这一行之后sleep 1s钟
那么completed 将会在 poller1线程析构
而在poller1线程执行competed析构时 在peer_sock->attachEvent之后又 sleep 2s钟
这2s钟内 poller2将fd加入到epoll去监听 并有事件触发上层业务回调 回调中清空peer_sock引用(_session_map.erase)
最终poller1仍引用着 peer_sock 然后在poller1中析构peer_sock

确实如你所说存在在poller1线程中最后销毁complete从而导致在poller1线程中把peerfd添加到poller2监听的极端情况。
这种情况下,其实也并不会丢失事件,因为那样说明已经在poller2线程执行 server->onAcceptConnection(sock);代码了。
也就是已经做好了处理peerfd事件的准备工作了

另外 在本次循环以及poller2->async异步执行完毕后 才可能在poller2线程收到peerfd的事件。所以你说的在哪里close peer fd的疑惑其实并不用担心

跨线程close 会导致链接"卡死"
eg:
在poller1析构peer_sock时 会有两个动作

  1. delEvent(12(假设文件描述符为12)) 这里将会 放到poller2的任务队列上异步(poller2->async)执行
  2. shutdown(12) & close(12) 这里直接将12(fd)关闭了 假设此时在(1)中 poller2也正在进行 accept 便很有可能 由系统分配到一个新的fd 也是12 那么poller2在这一轮epoll循环后将会执行(1)的delEvent(12) 将刚刚accept得到的12上的事件取消

你说的问题不存在啊 首先在poller1线程中停止引用peer socket时 才会导致complete引用归零啊,complete引用归零才会把fd添加进poller2线程监听啊,也就是peer socket fd触发事件时 必定是在poller1线程已经没有再使用peer socket了。所以不存在在poller1线程中close peer fd的可能

也就会执行peer_sock->attachEvent 代码时,poller1本次循环都结束了 poller1对peer socket和complete的引用都释放了。
它释放完了,才有执行peer_sock->attachEvent代码的可能 所以poller1线程不可能是最后持有peer_sock的线程