c++ boost fork boost-asio

c++ - Cómo hacer que la horquilla boost asio sea segura



fork boost-asio (2)

Creé una biblioteca C ++ con boost ASIO. La biblioteca debe ser segura para subprocesos y segura para horquillas. Tiene un hilo del programador de servicio, que llama a io_service::run() . Para admitir la seguridad de las horquillas, he registrado pre_fork, post_fork_parent y post_fork_child handlers. El controlador pre_fork() , llama a _io_service.notify_fork(boost::io_service:fork_prepare() , post_fork_parent handler llama a _io_service.notify_fork(boost::asio::io_service::fork_parent) y a las llamadas de _io_service.notify_fork(boost::asio::io_service::fork_child) .

El problema al que me enfrento, cuando ocurre el fork() , el hilo del programador del servicio podría estar en medio de alguna operación y podría haber adquirido el bloqueo en los miembros de datos del objeto io_service . Por lo tanto, el proceso hijo los ve en el mismo estado y en el post_fork_child () cuando llamamos a _io_service.notify_fork(boost::asio::io_service::fork_child) intenta adquirir el bloqueo en el mismo objeto y, por lo tanto, se bloquea indefinidamente (ya que no hay ningún hilo en el niño para liberar el desbloqueo).

La traza de pila que veo en el proceso hijo, que está bloqueada, es -

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

Aparentemente, "dev_poll_reactor" está bloqueado (porque parece estar distribuyendo algunos eventos pendientes) en el hilo del programador de servicios cuando ha ocurrido el fork que está causando el problema.

Creo que para resolver el problema, necesito asegurarme de que el hilo del programador del servicio no se encuentre en el medio de ningún proceso cuando se io_service.stop() el fork y una forma de garantizar que sería llamar a io_service.stop() en el controlador pre_fork () pero eso no es así. Parece una buena solución. ¿Podría decirme cuál es el enfoque correcto para hacer que la biblioteca se mantenga segura?

Los fragmentos de código se parecen a esto.

/** * 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); }

Estoy usando boost 1.47 y estoy ejecutando la aplicación en Solaris i386. La biblioteca y la aplicación se crean utilizando studio-12.0.


El código asio especifica que notify_fork() no funciona cuando hay algún código en el código io_service.

Esta función no se debe invocar mientras se está llamando a otra función io_service, o cualquier función en un objeto de E / S asociada con el servicio io, en otra conversación. Sin embargo, es seguro llamar a esta función desde un controlador de finalización, siempre que ningún otro hilo acceda al io_service.

Parece que incluye run o cualquiera de los IO asociados con la biblioteca. Creo que el procesamiento de pre_fork debe restablecer un elemento de trabajo.

por ejemplo, de la documentación de impulso

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

Cuidado aún necesita ser tomado

  1. Asegúrese de que run() no se post_fork() antes de que se post_fork() .
  2. Asegúrese de que se cree un nuevo objeto de work para la próxima run
  3. Se detecta una sincronización adecuada para garantizar la terminación de la run .

Puede usar io_service :: run_one para verificar si un fork está programado / el io_service aún debería estar ejecutándose. Cuando un tenedor debería estar sucediendo, se puede agregar algo de trabajo al io_service para que el subproceso se active. El hilo verifica la condición de ejecución y se detiene inmediatamente. Después de que sucedió el fork, el padre o el hijo pueden reiniciar un hilo de trabajo.

/** * 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); }