2013-03-30 4 views
6

Boosts ASIO-Dispatcher scheint ein ernstes Problem zu haben, und ich kann keine Abhilfe finden. Das Symptom besteht darin, dass der einzige Thread, der auf den Versand wartet, in pthread_cond_wait feven bleibt, obwohl E/A-Vorgänge anstehen, die eine Blockierung in epoll_wait erfordern.Nur das Threadhandling io_service wartet, obwohl asynchrone E/A-Vorgänge ausstehen

Ich kann dieses Problem am einfachsten replizieren, indem ich einen Thread poll_one in einer Schleife aufrufen, bis er Null zurückgibt. Dies kann dazu führen, dass der Thread, der run aufruft, in pthread_cond_wait steckt, während der Thread, der poll_one aufruft, aus der Schleife ausbricht. Vermutlich erwartet der io_service, dass der Thread in epoll_wait zurückkehrt, aber er ist dazu nicht verpflichtet und diese Erwartung scheint fatal zu sein.

Gibt es eine Anforderung, dass Threads statisch mit io_service s verknüpft sind?

Hier ist ein Beispiel, das den Deadlock zeigt. Dies ist der einzige Thread, der diesen io_service bearbeitet, weil die anderen sich weiter entwickelt haben. Es gibt auf jeden Fall Socket Operationen anstehen:

#0 [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 

Ich glaube, der Fehler ist wie folgt: Wenn ein Thread eine E/A-Warteschlange Wartung ist der rote Faden, der auf dem I/O-Buchse Bereitschaft Check blockt und ruft sie zu einem Versand Funktion, wenn andere Threads auf dem io-Dienst blockiert sind, muss es signalisieren. Es signalisiert momentan nur, ob Handler zu diesem Zeitpunkt bereit sind zu laufen. Aber das lässt keinen Thread auf Socket Readiness prüfen.

+0

Wie lautet der Rückgabecode von run_one()? –

+0

Wenn 1 als normal zurückgegeben wird, muss der io_service nur zurückgesetzt werden, wenn 0 zurückgegeben wird. Es hört sich nicht so an, als ob du irgendetwas falsch machst. Kannst du einen sscce veröffentlichen? –

+0

@DavidSchwartz Sind Sie sicher, dass der Anruf pthread_cond_timedwait von asio stammt? Ich habe Probleme, das im Code zu sehen. – janm

Antwort

6

Dies ist ein Fehler. Ich konnte es kopieren, indem ich eine Verzögerung in den unkritischen Abschnitt von task_io_service::do_poll_one hinzufüge. Hier ist ein Ausschnitt der modifizierten task_io_service::do_poll_one() in booost/asio/detail/impl/task_io_service.ipp. Die einzige hinzugefügte Zeile ist der Schlaf.

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; 
    } 

... 

Mein Testfahrer ist recht einfach: ""

  • Eine asynchrone Arbeit Schleife über einen Timer, der druckt alle 3 Sekunden.
  • Spawn aus einem einzelnen Thread, der die io_service abfragen wird.
  • Verzögerung, um die neue Thread-Zeit zu ermöglichen, io_service abzufragen und Hauptanruf io_service::run() zu haben, während der Abfrage-Thread in task_io_service::do_poll_one() schläft.

Prüfregeln:

#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(); 
} 

Und die 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]

An diesem Punkt der arm_timer() gedruckt hat "" einmal (wenn es anfänglich bewaffnet war). Der Poll-Thread bediente den Reaktor in einer nicht-blockierenden Weise und schlief 3 Sekunden lang, während op_queue_ leer war (task_operation_ wird wieder zu der op_queue_ hinzugefügt, wenn task_cleanup c den Geltungsbereich verlässt). Während der op_queue_ leer war, ruft der Hauptthread io_service::run() auf, sieht die op_queue_ ist leer, und macht sich die first_idle_thread_, wo es auf seine wakeup_event wartet. Der Abfragthread beendet den Ruhezustand und gibt 0 zurück, wobei der Hauptthread auf wakeup_event wartet.

Nach einer Wartezeit 10 ~ Sekunden, viel Zeit für die arm_timer() bereit sein, unterbreche ich den Debugger:

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()

Die Side-by-Side-Timeline ist wie folgt:

   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()        |

So gut ich sagen kann, gibt es keine Nebenwirkungen durch Patchen:

if (o == &task_operation_) 
    return 0; 

zu:

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

Egal, ich habe einen bug and fix vorgelegt. Überlegen Sie, ob Sie das Ticket für eine offizielle Antwort im Auge behalten.