2012-02-20 14 views
21

Zbudowałem bibliotekę C++ używając ASIO doładowania. Biblioteka musi być zarówno bezpieczna dla wątków, jak i bezpieczna dla widżetów. Ma wątek programu do planowania usług, który wywołuje io_service::run(). Aby wesprzeć bezpieczeństwo widelca, zarejestrowałem obsługę pre_fork, post_fork_parent i post_fork_child. Obsługa pre_fork() dzwoni _io_service.notify_fork(boost::io_service:fork_prepare(), post_fork_parent obsługi połączeń _io_service.notify_fork(boost::asio::io_service::fork_parent) i post_fork_child połączeń _io_service.notify_fork(boost::asio::io_service::fork_child).Jak sprawić, by Asio rozwidlało się bezpieczniej

Problem, z którym mam do czynienia, kiedy występuje fork(), wątek programu do planowania usług może znajdować się w środku pewnej operacji i mógł uzyskać blokadę elementów danych obiektu io_service. Tak więc proces potomny widzi je w tym samym stanie i w post_fork_child(), gdy wywołujemy _io_service.notify_fork(boost::asio::io_service::fork_child) próbuje uzyskać blokadę na tym samym obiekcie i tym samym zostaje zablokowany na czas nieokreślony (ponieważ nie ma wątku w pliku potomnym, aby zwolnić odblokowanie).

Ślad stosu widzę w procesie potomnym, który jest zablokowany, to -

fffffd7ffed07577 lwp_park (0, 0, 0) 
fffffd7ffecffc18 mutex_lock_internal() + 378 
fffffd7ffecfffb2 mutex_lock_impl() + 112 
fffffd7ffed0007b mutex_lock() + b 
fffffd7fff26419d __1cFboostEasioGdetailLscoped_lock4n0CLposix_mutex__2t5B6Mrn0D__v_() + 1d 
fffffd7fff2866a2 __1cFboostEasioGdetailQdev_poll_reactorMfork_service6Mn0BKio_serviceKfork_event__v_() + 32 
fffffd7fff278527 __1cFboostEasioGdetailQservice_registryLnotify_fork6Mn0BKio_serviceKfork_event__v_() + 107 
fffffd7fff27531c __1cDdesGtunnelQServiceSchedulerPpost_fork_child6M_v_() + 1c 
fffffd7fff29de24 post_fork_child() + 84 
fffffd7ffec92188 _postfork_child_handler() + 38 
fffffd7ffecf917d fork() + 12d 
fffffd7ffec172d5 fork() + 45 
fffffd7ffef94309 fork() + 9 
000000000043299d main() + 67d 
0000000000424b2c ????????() 

Widocznie „dev_poll_reactor” jest zablokowana (ponieważ wydaje się być wysyłając jakieś oczekujące Wydarzenia) w wątku harmonogramu usługi kiedy nastąpiło rozwidlenie, które powoduje problem.

Myślę, że aby rozwiązać ten problem, muszę się upewnić, że wątek programu do obsługi usług nie znajduje się w trakcie przetwarzania, gdy widelec się wydarzy, i jednym ze sposobów zagwarantowania, że ​​będzie to wywołanie io_service.stop() w procedurze pre_fork(), ale to robi brzmi jak dobre rozwiązanie. Czy mógłbyś mi powiedzieć, jakie jest właściwe podejście, aby rozwidlenie biblioteki było bezpieczne?

Fragmenty kodu wyglądają mniej więcej tak.

/** 
* Combines Boost.ASIO with a thread for scheduling. 
*/ 
class ServiceScheduler : private boost::noncopyable 
{ 
public : 
    /// The actual thread used to perform work. 
    boost::shared_ptr<boost::thread>    _service_thread; 

    /// Service used to manage async I/O events 
    boost::asio::io_service      _io_service; 

    /// Work object to block the ioservice thread. 
    std::auto_ptr<boost::asio::io_service::work> _work; 
    ... 
}; 

/** 
* CTOR 
*/ 
ServiceScheduler::ServiceScheduler() 
    : _io_service(), 
     _work(std::auto_ptr<boost::asio::io_service::work>( 
       new boost::asio::io_service::work(_io_service))), 
     _is_running(false) 
{ 
} 

/** 
* Starts a thread to run async I/O service to process the scheduled work. 
*/ 
void ServiceScheduler::start() 
{ 
    ScopedLock scheduler_lock(_mutex); 
    if (!_is_running) { 
     _is_running = true; 
     _service_thread = boost::shared_ptr<boost::thread>( 
       new boost::thread(boost::bind( 
         &ServiceScheduler::processServiceWork, this))); 
    } 
} 

/** 
* Processes work passed to the ASIO service and handles uncaught 
* exceptions 
*/ 
void ServiceScheduler::processServiceWork() 
{ 
    try { 
     _io_service.run(); 
    } 
    catch (...) { 
    } 
} 

/** 
* Pre-fork handler 
*/ 
void ServiceScheduler::pre_fork() 
{ 
    _io_service.notify_fork(boost::asio::io_service::fork_prepare); 
} 

/** 
* Post-fork parent handler 
*/ 
void ServiceScheduler::post_fork_parent() 
{ 
    _io_service.notify_fork(boost::asio::io_service::fork_parent); 
} 

/** 
* Post-fork child handler 
*/ 
void ServiceScheduler::post_fork_child() 
{ 
    _io_service.notify_fork(boost::asio::io_service::fork_child); 
} 

Używam doładowania 1.47 i uruchomienia aplikacji w systemie Solaris i386. Biblioteka i aplikacja zostały zbudowane przy użyciu studio-12.0.

+0

Czy spodziewasz się wykonać cokolwiek innego wywołując exec() lub _exit() w urządzeniu po wywołaniu fork? Jeśli tak, powinieneś ponownie rozważyć. Jeśli nie, nie widzę problemu. – janm

+0

Możesz zarezerwować główny wątek tylko do administrowania, zadań interfejsu poleceń i obsługi rodzic-dziecko. Po rozwidleniu w wątku istnieje tylko główny wątek. Możesz przechowywać wewnętrzne dane konfiguracyjne do przywracania i tworzyć potrzebne wątki w procesie potomnym. W ten sposób zapewnia czystą hermetyzację i pozwala uniknąć blokowania. –

+3

Po próbie użycia boost :: asio dla dwóch projektów, doszedłem do wniosku, że lepiej nie używać boostu. Odbija się nawet na prostych przykładach. Jego skomplikowana struktura szablonów jest nadmiernie trudna do zrozumienia i niemożliwa do znacznego przekroczenia oraz określenia nawet prawdopodobnej przyczyny. – wallyk

Odpowiedz

2

Kod asio określa, że ​​notify_fork() nie działają, gdy istnieje kod w kodzie io_service.

Funkcja ta może być wywoływana podczas gdy wszelkie inne funkcje io_service lub dowolną funkcją, na obiekt we/wy związanych z tym io_service jest jako zwany w innym wątku. Można jednak bezpiecznie wywoływać tę funkcję z poziomu w obrębie procedury obsługi zakończenia, pod warunkiem, że żaden inny wątek nie łączy się z usługą io_service.

To wydaje się obejmować run lub dowolne z IO powiązanych z biblioteką. Myślę, że twoje przetwarzanie pre_fork, powinno zresetować element pracy.

np. z boost documentation nadal musi być brane

  1. Zapewnienie run() nie jest wywoływana przed post_fork() zakończyła

    boost::asio::io_service io_service; 
    auto_ptr<boost::asio::io_service::work> work(
        new boost::asio::io_service::work(io_service)); 
    ... 
    pre_fork() { 
        work.reset(); // Allow run() to exit. 
        // check run has finished... 
        io_service.notify_fork(...); 
    } 
    

    opieki.

  2. zapewnienie nowych work obiekt jest tworzony dla następnego run
  3. właściwą synchronizację w celu zapewnienia run zakończenie jest punktowane.
0

Można użyć io_service :: run_one, aby sprawdzić, czy widelec jest zaplanowany/usługa io_ nadal powinna działać. Kiedy widelec powinien się zdarzyć, do serwisu io_ można dodać trochę pracy, aby wątek się obudził. Wątek sprawdza warunek działania i natychmiast zatrzymuje się. Po wystąpieniu widma rodzic lub dziecko może ponownie uruchomić wątek roboczy.

/** 
* Combines Boost.ASIO with a thread for scheduling. 
*/ 
class ServiceScheduler : private boost::noncopyable 
{ 
public : 
    /// The actual thread used to perform work. 
    boost::shared_ptr<boost::thread>    _service_thread; 

    /// Service used to manage async I/O events 
    boost::asio::io_service      _io_service; 

    /// Work object to block the ioservice thread. 
    std::auto_ptr<boost::asio::io_service::work> _work; 
    ServiceScheduler(); 
    void start(); 
    void pre_fork(); 
private: 
    void processServiceWork(); 
    void post_fork_parent(); 
    void post_fork_child(); 
    std::atomic<bool> _is_running; 
}; 

/** 
* CTOR 
*/ 
ServiceScheduler::ServiceScheduler() 
    : _io_service(), 
     _work(std::auto_ptr<boost::asio::io_service::work>(
       new boost::asio::io_service::work(_io_service))), 
     _is_running(false) 
{ 
} 

/** 
* Starts a thread to run async I/O service to process the scheduled work. 
*/ 
void ServiceScheduler::start() 
{ 
    if(!_is_running) { 
     _service_thread = boost::shared_ptr<boost::thread>(
       new boost::thread(boost::bind(
         &ServiceScheduler::processServiceWork, this))); 
    } 
} 

/** 
* Processes work passed to the ASIO service and handles uncaught 
* exceptions 
*/ 
void ServiceScheduler::processServiceWork() 
{ 
    try { 
     while(_is_running) { 
      _io_service.run_one(); 
     } 
    } 
    catch (...) { 
    } 
    _is_running = false; 
} 

/** 
* Pre-fork handler 
*/ 
void ServiceScheduler::pre_fork() 
{ 
    _is_running = false; 
    _io_service.post([](){ /*no_op*/}); 
    _service_thread->join(); 
    _service_thread.reset(); 
    _io_service.notify_fork(boost::asio::io_service::fork_prepare); 
} 

/** 
* Post-fork parent handler 
*/ 
void ServiceScheduler::post_fork_parent() 
{ 
    start(); 
    _io_service.notify_fork(boost::asio::io_service::fork_parent); 
} 

/** 
* Post-fork child handler 
*/ 
void ServiceScheduler::post_fork_child() 
{ 
    _io_service.notify_fork(boost::asio::io_service::fork_child); 
} 
Powiązane problemy