2012-02-20 17 views
21

He creado una biblioteca C++ utilizando boost ASIO. La biblioteca debe ser segura para subprocesos y segura para horquillas. Tiene un hilo del programador de servicio, que llama al 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(), el manejador post_fork_parent llama a _io_service.notify_fork(boost::asio::io_service::fork_parent) y las llamadas a post_fork_child _io_service.notify_fork(boost::asio::io_service::fork_child).Cómo hacer que boost asio fork sea seguro

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

El seguimiento de pila que veo en el proceso hijo, que está bloqueado, 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 ????????() 

Al parecer, el "dev_poll_reactor" está bloqueada (ya que parece ser el envío de algunos eventos pendientes) en la rosca servicio del planificador cuando ha ocurrido la bifurcación que está causando el problema.

Creo que para solucionar el problema, necesito asegurarme de que el hilo del programador de servicio no se encuentra en el medio de ningún procesamiento cuando ocurre la horquilla y una forma de garantizar eso sería llamar al io_service.stop() en el controlador pre_fork() pero eso no 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 utilizando boost 1.47 y ejecutando la aplicación en Solaris i386. La biblioteca y la aplicación se crean utilizando studio-12.0.

+0

¿Espera hacer algo distinto de ejecutar exec() o _exit() en el hijo después de llamar al tenedor? Si es así, debes reconsiderar. Si no, no veo el problema. – janm

+0

Puede reservar el hilo principal solo para la administración, las tareas de la interfaz de comando y el control padre-hijo. Después del tenedor, solo el hilo principal existe en el niño. Puede contener datos de configuración internos para restaurar y crear hilos necesarios en el proceso hijo. De esta forma se asegura una encapsulación limpia y se evitan las necesidades de bloqueo. –

+3

Después de intentar usar boost :: asio para dos proyectos, llegué a la conclusión de que es mejor no usar boost. Segfaults incluso en ejemplos simples. Su estructura de plantilla compleja es excesivamente difícil de comprender e imposible de entender de manera significativa e identificar incluso una causa probable. – wallyk

Respuesta

2

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

Esta función no debe ser llamado, mientras que cualquier otra función io_service, o cualquier función en un objeto de E/S asociado con el io_service, está siendo llamada en otro hilo. Sin embargo, es seguro llamar a esta función desde dentro de un controlador de finalización, siempre que ningún otro subproceso esté accediendo 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.

p. Ej. de boost documentation

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 todavía tiene que ser tomada

  1. Asegúrese run() no se llama antes de post_fork() ha completado.
  2. asegurar que los nuevos work objeto se crea para el próximo run
  3. sincronización adecuada para garantizar la terminación run está manchada.
0

Puede usar io_service :: run_one para verificar si una horquilla está programada/el io_service aún debe estar en ejecución. 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); 
} 
Cuestiones relacionadas