Boost Asio io_service析构函数挂起在OS X上

时间:2014-02-17 17:39:28

标签: c++ multithreading macos boost boost-asio

我在OS X上遇到Boost Asio的问题,其中io_service析构函数有时无限期挂起。我有一个相对简单的责备案例:

#include <boost/asio.hpp>
#include <boost/thread.hpp>

int main(int argc, char* argv[]) {
    timeval tv;
    gettimeofday(&tv, 0);
    std::time_t t = tv.tv_sec;
    std::tm curr;
    // The call to gmtime_r _seems_ innocent, but I cannot reproduce without this
    std::tm* curr_ptr = gmtime_r(&t, &curr);

    {
        boost::asio::io_service ioService;
        boost::asio::deadline_timer timer(ioService);

        ioService.post([&](){
            // This will also call gmtime_r, but just calling that is not enough
            timer.expires_from_now(boost::posix_time::milliseconds(1));
            timer.async_wait([](const boost::system::error_code &) {});
        });
        ioService.post([&](){
            ioService.post([&](){});
        });

        // Run some threads
        boost::thread_group workers;
        for (auto i=0; i<3; ++i) {
            workers.create_thread([&](){ ioService.run(); });
        }
        workers.join_all();
    } // hangs here in the io_service destructor
    return 0;
}

基本上,这只是在队列上发布两个处理程序,其中一个调度计时器而另一个只发布另一个处理程序。有时这个简单的程序导致io_service析构函数无限期挂起,特别是在pipe_select_interrupter销毁期间kqueue_reactor析构函数中。这会阻塞管道读取描述符上的系统调用close()

要触发错误,我使用shell脚本在循环中调用程序(但也可以在上面的示例中使用循环触发):

#!/bin/csh
set yname="foo"
while ( $yname != "" )
    date
    ./hangtest
end

如果我:

,我将无法复制
  • 在开头删除对gmtime_r()的调用(!)。编辑:如果我使用脚本运行,这似乎只适用。如果我在程序中添加循环,我可以在没有调用的情况下重现它,根据ruslo的注释。
  • 取消对处理程序中计时器的async_wait()的调用,或将计时器设置移到处理程序之外。
  • 删除第二个处理程序中的post()
  • 降低线程数。
  • kqueue_reactor::interrupt()中放置一个互斥锁。从async_wait()post()调用此函数,并使用无法关闭的读取描述符调用kevent()

我在上面的代码中做错了什么?

我在OS X 10.8.5上使用Boost 1.54运行并使用clang -stdlib=libc++ -std=c++11进行编译。我还可以使用Boost 1.55中的Boost Asio进行复制(其余的Boost 1.54保持原样)。

编辑:我也可以在OS X 10.9.1上重现(使用相同的可执行文件)。

1 个答案:

答案 0 :(得分:1)

对此的修复是在2014年4月29日的主分支中致力于Asio

  

修复偶尔在MacOS上挂起的close()系统调用。

     

重复重新注册kqueue事件过滤器似乎表现为   尽管MacOS上存在某种“泄密”,但最终导致暂停   close()系统调用和不可杀死的进程。为了避免这种情况,我们会   只注册描述符的kqueue事件过滤器一次,即   首先创建描述符。