Я построил С++-библиотеку, используя boost ASIO. Библиотека должна быть как поточно-безопасной, так и безопасной.
Он имеет поток планировщика услуг, который вызывает io_service::run()
. Для поддержки безопасности fork я зарегистрировал обработчики pre_fork, post_fork_parent и post_fork_child. Обработчик pre_fork()
вызывает вызовы _io_service.notify_fork(boost::io_service:fork_prepare()
, post_fork_parent вызовы _io_service.notify_fork(boost::asio::io_service::fork_parent)
и post_fork_child _io_service.notify_fork(boost::asio::io_service::fork_child)
.
Проблема, с которой я столкнулся, когда происходит fork()
, поток планировщика сервисов может находиться в середине некоторой операции и мог бы получить блокировку для членов данных объекта io_service
. Таким образом, дочерний процесс видит их в одном и том же состоянии и в post_fork_child(), когда мы вызываем _io_service.notify_fork(boost::asio::io_service::fork_child)
, он пытается получить блокировку на одном и том же объекте и, следовательно, блокируется бесконечно (так как нет потока в дочернем элементе, чтобы освободить разблокировку).
Трассировка стека, которую я вижу в дочернем процессе, который заблокирован, -
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 ???????? ()
По-видимому, "dev_poll_reactor" заблокирован (потому что он, кажется, отправляет некоторые ожидающие события) в поток планировщика обслуживания, когда произошла вилка, которая вызывает проблему.
Я думаю, чтобы решить эту проблему, мне нужно убедиться, что поток планировщика обслуживания не находится в середине любой обработки, когда происходит вилка, и один из способов гарантировать, что это будет вызов io_service.stop()
в обработчике pre_fork(), но это не кажется хорошим решением. Не могли бы вы сообщить мне, какой подход подходит для того, чтобы сделать вилку библиотеки безопасной?
Фрагменты кода выглядят примерно так.
/**
* 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);
}
Я использую boost 1.47 и запускаю приложение на Solaris i386. Библиотека и приложение построены с использованием studio-12.0.