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

vil*_*pam 8 c++ macos multithreading boost boost-asio

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

#!/bin/csh
set yname="foo"
while ( $yname != "" )
    date
    ./hangtest
end
Run Code Online (Sandbox Code Playgroud)

如果我:我不再能够复制:

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

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

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

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

seh*_*ehe 1

此问题的修复已于 2014 年 4 月 29 日提交给主分支中的 Asio

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

重复重新注册 kqueue 事件过滤器的行为似乎在 MacOS 上存在某种“泄漏”,最终导致挂起的 close() 系统调用和不可终止的进程。为了避免这种情况,我们将仅注册描述符的 kqueue 事件过滤器一次,即在首次创建描述符时。