This is mistake. I was able to duplicate it by adding a delay to the non-critical section of task_io_service::do_poll_one . Below is a snippet of the modified task_io_service::do_poll_one() in booost/asio/detail/impl/task_io_service.ipp . The only line added is sleep.
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;
My test driver is pretty simple:
- Asynchronous duty cycle through a timer that will print "." every 3 seconds.
- Create one thread that will poll
io_service . - A delay to allow the new thread time to poll
io_service and have the main call io_service::run() while the poll thread is in task_io_service::do_poll_one() .
Test code:
#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(); }
And debugging:
[ twsansbury@localhost 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]
At this point, arm_timer() printed ".". once (when he was fully armed). The polling flow served the reactor in a non-blocking manner and slept for 3 seconds while op_queue_ was empty ( task_operation_ will be added back to op_queue_ when task_cleanup c leaves the area). While op_queue_ was empty, the main thread calls io_service::run() , sees that op_queue_ empty, and makes itself first_idle_thread_ , where it waits for its wakeup_event . The polling thread finishes sleep mode and returns 0 , leaving the main thread pending on wakeup_event .
After waiting 10 ~ seconds, there is enough time for arm_timer() to be ready, I interrupt the debugger:
Program received signal SIGINT, Interrupt.
0x00919402 in __kernel_vsyscall ()
(gdb) bt
# 0 0x00919402 in __kernel_vsyscall ()
# 1 0x0081bbc5 in pthread_cond_wait@ @ GLIBC_2.3.2 () from /lib/libpthread.so.0
# 2 0x00763b3d in pthread_cond_wait@ @ 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 ()
The timeline side by side is as follows:
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 () |
As far as I can tell, there are no side effects:
if (o == &task_operation_) return 0;
in
if (o == &task_operation_) { if (!one_thread_) wake_one_thread_and_unlock(lock); return 0; }
Regardless, I posted a bug and fixed it . Consider keeping track of the ticket for an official response.