Как сделать boost asio форком безопасным - PullRequest
21 голосов
/ 20 февраля 2012

Я создал библиотеку C ++, используя 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.

Ответы [ 2 ]

2 голосов
/ 07 сентября 2015

Код asio указывает, что notify_fork() не работает, если в коде io_service есть какой-либо код.

Эта функция не должна вызываться, пока любая другая функция io_service или любая функция вОбъект ввода / вывода, связанный с io_service, вызывается в другом потоке.Тем не менее, безопасно вызывать эту функцию из обработчика завершения, при условии, что никакой другой поток не обращается к io_service.

, который, по-видимому, включает run или любой из операций ввода-вывода, связанных с библиотекой,Я думаю, что ваша обработка pre_fork должна сбросить рабочий элемент.

например, из форсированная документация

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

Необходимо еще позаботиться

  1. Убедитесь, что run() не вызывается до завершения post_fork().
  2. Убедитесь, что новый объект work создан для следующего run
  3. Правильная синхронизация для обеспечения завершения runпятнистый.
0 голосов
/ 02 октября 2016

Вы можете использовать io_service :: run_one, чтобы проверить, запланирован ли форк / io_service все еще должен работать.Когда должно произойти форк, в io_service можно добавить некоторую работу, чтобы поток проснулся.Поток проверяет условия выполнения и немедленно останавливается.После того, как произошла форк, родительский или дочерний процесс может перезапустить рабочий поток.

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