Подтвердить что ты не робот

Как сделать так, чтобы увеличить

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

4b9b3361

Ответ 1

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

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

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

например. от повысить документацию

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

Необходимо по-прежнему соблюдать осторожность

  • Убедитесь, что run() не вызывается до завершения post_fork().
  • Обеспечить создание нового объекта work для следующего run
  • Определена правильная синхронизация для обеспечения завершения run.

Ответ 2

Вы можете использовать io_service:: run_one, чтобы проверить, запланирована ли fork/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);
}