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.
¿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
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. –
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