zoukankan      html  css  js  c++  java
  • 网络库crash以及boost asio strand dispath分析

      最近在做服务器的稳定性的相关测试,服务器的网络底层使用的是boost asio,然后自己做的二次封装以更好的满足需求。

      服务器昨天晚上发现crash了一次,之前测试了将近半个多月,有一次是莫名的退出了,不过由于是新的测试服,忘记将ulimit -c进行修改了,所以没有coredump,这次又发生了。

    coredump如下:

    #0  0x0000000000000091 in ?? ()
    #1  0x0000000000459729 in ClientHandler::HandleConnect(cpnet::IConnection*) ()
    #2  0x00000000004a0bbc in boost::asio::detail::completion_handler<boost::_bi::bind_t<void, boost::_mfi::mf1<void, cpnet::IMsgHandler, cpnet::IConnection*>, boost::_bi::list2<boost::_bi::value<cpnet::IMsgHandler*>, boost::_bi::value<cpnet::Connection*> > > >::do_complete(boost::asio::detail::task_io_service*, boost::asio::detail::task_io_service_operation*, boost::system::error_code const&, unsigned long) ()
    #3  0x0000000000492e25 in boost::asio::detail::strand_service::do_complete(boost::asio::detail::task_io_service*, boost::asio::detail::task_io_service_operation*, boost::system::error_code const&, unsigned long) ()
    #4  0x0000000000493f20 in boost::asio::detail::task_io_service::run(boost::system::error_code&) ()
    #5  0x0000000000495bb5 in boost::asio::io_service::run() ()
    #6  0x00007ff3798153cf in thread_proxy () from /home/slither/slither/depends/libboost_thread.so.1.58.0
    #7  0x00007ff3788d7df5 in start_thread () from /lib64/libpthread.so.0
    #8  0x00007ff3786051ad in clone () from /lib64/libc.so.6

      整个网络库是针对boost asio做的二次封装,程序由于是release版本的,之前也没有特别生成这个版本对应的symbols,我只能看出异常时候的堆栈信息和线程信息,其他的东西我也没有什么好的办法去查看。

      从堆栈异常中可以看出来,最后出错的地方是非法的地址方法,不过调用它的frame 1是我们自己的函数,一个当有连接成功时候的回调函数。不过一开始看这个回调函数的堆栈感觉很奇怪,因为是我们自己内部通过strand dispatch的,按理说调用这个函数的上层函数也应该是我们自己写的代码,然而情况不是这样,那么不可能是堆栈显示错了,而应该是自己之前对于dispatch的立即错了。

      这个HandleConnect我是使用boost strand做dispatch分发的,这里就涉及到我之前对strand dispatch接口的一个误读了,之前看文档不够细致,以为strand dispath是立即执行的,其实这是不对的,dispath的接口说明文档是这样的:

    /**
       * This function is used to ask the strand to execute the given handler.
       *
       * The strand object guarantees that handlers posted or dispatched through
       * the strand will not be executed concurrently. The handler may be executed
       * inside this function if the guarantee can be met. If this function is
       * called from within a handler that was posted or dispatched through the same
       * strand, then the new handler will be executed immediately.
       *
       * The strand's guarantee is in addition to the guarantee provided by the
       * underlying io_service. The io_service guarantees that the handler will only
       * be called in a thread in which the io_service's run member function is
       * currently being invoked.
       *
       * @param handler The handler to be called. The strand will make a copy of the
       * handler object as required. The function signature of the handler must be:
       * @code void handler(); @endcode
       */

      注意红色标记的那段,如果调用strand dispatch的时候,是持有相同strand调用的,那么当前dispatch的handler会立即执行。也就是说在多线程的时候,如果我们的线程调用strand dispatch的时候,其他线程已经在调用了,那么其实它是不会立即执行的,会放到等待队列里面去的。

      asio中的dispatch代码是这样的:

    template <typename Handler>
    void strand_service::dispatch(strand_service::implementation_type& impl,
        Handler& handler)
    {
      // If we are already in the strand then the handler can run immediately.
     // 如果我们已经在这个strand中了,那么这个handler立即执行
    if (call_stack<strand_impl>::contains(impl)) { fenced_block b(fenced_block::full); boost_asio_handler_invoke_helpers::invoke(handler, handler); return; } // Allocate and construct an operation to wrap the handler. typedef completion_handler<Handler> op; typename op::ptr p = { boost::asio::detail::addressof(handler), boost_asio_handler_alloc_helpers::allocate( sizeof(op), handler), 0 }; p.p = new (p.v) op(handler); BOOST_ASIO_HANDLER_CREATION((p.p, "strand", impl, "dispatch"));  

    // do_dispatch判断是否能够立即执行
    bool dispatch_immediately = do_dispatch(impl, p.p); operation* o = p.p; p.v = p.p = 0; if (dispatch_immediately) { // Indicate that this strand is executing on the current thread. call_stack<strand_impl>::context ctx(impl); // Ensure the next handler, if any, is scheduled on block exit. on_dispatch_exit on_exit = { &io_service_, impl }; (void)on_exit; completion_handler<Handler>::do_complete( &io_service_, o, boost::system::error_code(), 0); } }

      再来看下do_dispatch的代码:

    bool strand_service::do_dispatch(implementation_type& impl, operation* op)
    {
      // If we are running inside the io_service, and no other handler already
      // holds the strand lock, then the handler can run immediately.
     // 如果没有其他handler已经持有strand lock锁,那么这个handler就可以立即执行
    bool can_dispatch = io_service_.can_dispatch(); impl->mutex_.lock(); if (can_dispatch && !impl->locked_) { // Immediate invocation is allowed. impl->locked_ = true; impl->mutex_.unlock(); return true; } if (impl->locked_) { // Some other handler already holds the strand lock. Enqueue for later.
      // 如果其他handler已经持有strand锁了,那么放到队列中
    impl->waiting_queue_.push(op); impl->mutex_.unlock(); } else { // The handler is acquiring the strand lock and so is responsible for // scheduling the strand. impl->locked_ = true; impl->mutex_.unlock(); impl->ready_queue_.push(op); io_service_.post_immediate_completion(impl, false); } return false; }

      通过asio strand的dispatch源代码,我们可以看出来,我们dispatch的handler是有可能不会被立即执行的。由于我们自己之前对于dispatch逻辑的认知错误,在dispatch handler之前,我们就开始准备读网络数据,在比较特殊的情况下,也就是客户端刚连上,立即端口,那么我们读网络数据的函数就立即返回错误,由于我自己封装的Connection是使用shared_ptr做的封装,如果没有任何引用,就会析构掉,那么等我们之前dispatch的handler从队列中被执行的时候,之前传递的Connection指针已经是野指针了,就导致程序crash掉了。

      这种偶现的bug,是比较难被测试出来的,通常只有我们自己进行多样的压力测试的时候,才比较容易发现。同时也是告诫自己在使用其他第三方库的时候,还是要更加仔细的弄懂api。

  • 相关阅读:
    牛客IOI周赛17-提高组 卷积 生成函数 多项式求逆 数列通项公式
    6.3 省选模拟赛 Decompose 动态dp 树链剖分 set
    AtCoder Grand Contest 044 A Pay to Win 贪心
    5.29 省选模拟赛 树的染色 dp 最优性优化
    luogu P6097 子集卷积 FST FWT
    CF724C Ray Tracing 扩展欧几里得 平面展开
    5.30 省选模拟赛 方格操作 扫描线 特殊性质
    5.29 省选模拟赛 波波老师 SAM 线段树 单调队列 并查集
    Spring main方法中怎么调用Dao层和Service层的方法
    Bug -- WebService报错(两个类具有相同的 XML 类型名称 "{http://webService.com/}getPriceResponse"。请使用 @XmlType.name 和 @XmlType.namespace 为类分配不同的名称。)
  • 原文地址:https://www.cnblogs.com/chobits/p/5632225.html
Copyright © 2011-2022 走看看