2013-03-30 76 views
6

Boost的ASIO調度程序似乎有一個嚴重問題,而且我似乎無法找到解決方法。現象的症狀是,儘管有待處理的I/O操作要求它在epoll_wait中進行阻塞,但仍等待分派的唯一線程仍留在pthread_cond_wait feven中。即使異步I/O操作正在等待,只有處理io_service的線程正在等待

我可以通過讓一個線程在循環中調用poll_one直到它返回零來輕鬆地複製此問題。這可以使線程調用run卡在pthread_cond_wait而線程調用poll_one突破循環。據推測,io_service期待該線程返回到epoll_wait中的塊,但沒有義務這樣做,並且期望似乎是致命的。

是否需要線程與io_service s靜態關聯?

下面是一個顯示死鎖的例子。這是處理這個io_service的唯一線程,因爲其他人已經移動了。肯定有未決插座操作:

#0 pthread_co[email protected]@GLIBC_2.3.2() from /lib64/libpthread.so.0 
#1 boost::asio::detail::posix_event::wait<boost::asio::detail::scoped_lock<boost::asio::detail::posix_mutex> > (...) at /usr/include/boost/asio/detail/posix_event.hpp:80 
#2 boost::asio::detail::task_io_service::do_run_one (...) at /usr/include/boost/asio/detail/impl/task_io_service.ipp:405 
#3 boost::asio::detail::task_io_service::run (...) at /usr/include/boost/asio/detail/impl/task_io_service.ipp:146 

我相信錯誤是如下:如果一個線程提供服務的I/O隊列是的阻止在I/O插槽就緒檢查線程,它調用調度函數,如果有任何其他線程在io服務上被阻塞,它必須發出信號。它目前僅表示當時是否有處理程序可以運行。但是這不會讓套接字準備就緒的線程檢查。

+0

run_one()返回的代碼是什麼? –

+0

如果它返回1是正常的,只有當它返回0時才需要重置io_service。這聽起來不像你做錯任何事情,你可以發佈sscce嗎? –

+0

@DavidSchwartz你確定pthread_cond_timedwait調用來自asio嗎?我在代碼中遇到了一些麻煩。 – janm

回答

6

這是一個錯誤。我已經能夠通過在task_io_service::do_poll_one的非關鍵部分添加延遲來複制它。以下是booost/asio/detail/impl/task_io_service.ipp中修改的task_io_service::do_poll_one()的片段。唯一添加的是睡眠。

std::size_t task_io_service::do_poll_one(mutex::scoped_lock& lock, 
    task_io_service::thread_info& this_thread, 
    const boost::system::error_code& ec) 
{ 
    if (stopped_) 
    return 0; 

    operation* o = op_queue_.front(); 
    if (o == &task_operation_) 
    { 
    op_queue_.pop(); 
    lock.unlock(); 

    { 
     task_cleanup c = { this, &lock, &this_thread }; 
     (void)c; 

     // Run the task. May throw an exception. Only block if the operation 
     // queue is empty and we're not polling, otherwise we want to return 
     // as soon as possible. 
     task_->run(false, this_thread.private_op_queue); 
     boost::this_thread::sleep_for(boost::chrono::seconds(3)); 
    } 

    o = op_queue_.front(); 
    if (o == &task_operation_) 
     return 0; 
    } 

... 

我的測試驅動程序是相當基本的:「」

  • 通過一個計時器,將打印的異步工作循環每3秒鐘一次。
  • 產生一個將輪詢io_service的單個線程。
  • 延遲允許新的線程時間輪詢io_service,並有主要電話io_service::run()而輪詢線程睡在task_io_service::do_poll_one()

測試代碼:

#include <iostream> 

#include <boost/asio/io_service.hpp> 
#include <boost/asio/steady_timer.hpp> 
#include <boost/chrono.hpp> 
#include <boost/thread.hpp> 

boost::asio::io_service io_service; 
boost::asio::steady_timer timer(io_service); 

void arm_timer() 
{ 
    std::cout << "."; 
    std::cout.flush(); 
    timer.expires_from_now(boost::chrono::seconds(3)); 
    timer.async_wait(boost::bind(&arm_timer)); 
} 

int main() 
{ 
    // Add asynchronous work loop. 
    arm_timer(); 

    // Spawn poll thread. 
    boost::thread poll_thread(
    boost::bind(&boost::asio::io_service::poll, boost::ref(io_service))); 

    // Give time for poll thread service reactor. 
    boost::this_thread::sleep_for(boost::chrono::seconds(1)); 

    io_service.run(); 
} 

和Debug:

[[email protected] bug]$ gdb a.out 
... 
(gdb) r 
Starting program: /home/twsansbury/dev/bug/a.out 

[Thread debugging using libthread_db enabled] 
.[New Thread 0xb7feeb90 (LWP 31892)] 
[Thread 0xb7feeb90 (LWP 31892) exited]

在這一點上,arm_timer()已打印 「」一次(當它是武裝時)。輪詢螺紋以非阻塞的方式爲反應堆提供服務,並且睡眠3秒,而op_queue_爲空(將在task_cleanup c退出範圍時被添加回op_queue_)。 op_queue_是空的,主線程調用io_service::run(),看到op_queue_是空的,並且自己成爲first_idle_thread_,它在wakeup_event上等待。輪詢線程完成睡眠,並返回0,使主線程等待wakeup_event

等待10〜秒,充足的時間爲arm_timer()要準備好後,我中斷調試器:

Program received signal SIGINT, Interrupt. 
0x00919402 in __kernel_vsyscall() 
(gdb) bt 
#0 0x00919402 in __kernel_vsyscall() 
#1 0x0081bbc5 in [email protected]@GLIBC_2.3.2() from /lib/libpthread.so.0 
#2 0x00763b3d in [email protected]@GLIBC_2.3.2() from /lib/libc.so.6 
#3 0x08059dc2 in void boost::asio::detail::posix_event::wait >(boost::asio::detail::scoped_lock&)() 
#4 0x0805a009 in boost::asio::detail::task_io_service::do_run_one(boost::asio::detail::scoped_lock&, boost::asio::detail::task_io_service_thread_info&, boost::system::error_code const&)() 
#5 0x0805a11c in boost::asio::detail::task_io_service::run(boost::system::error_code&)() 
#6 0x0805a1e2 in boost::asio::io_service::run()() 
#7 0x0804db78 in main()

側由端的時間表如下:

   poll thread     |   main thread 
---------------------------------------+--------------------------------------- 
    lock()        | 
    do_poll_one()      |       
    |-- pop task_operation_ from   | 
    | queue_op_      | 
    |-- unlock()       | lock() 
    |-- create task_cleanup    | do_run_one() 
    |-- service reactor (non-block)  | `-- queue_op_ is empty 
    |-- ~task_cleanup()     |  |-- set thread as idle 
    | |-- lock()      |  `-- unlock() 
    | `-- queue_op_.push(    | 
    |  task_operation_)    | 
    `-- task_operation_ is    | 
     queue_op_.front()    | 
     `-- return 0      | // still waiting on wakeup_event 
    unlock()        |

盡我所知,沒有修補的副作用:

if (o == &task_operation_) 
    return 0; 

到:

if (o == &task_operation_) 
{ 
    if (!one_thread_) 
    wake_one_thread_and_unlock(lock); 
    return 0; 
} 

無論如何,我已經提交了bug and fix。考慮留意官方迴應的機票。