2012-02-20 2 views
21

Я построил 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).Как сделать форсированный вилочный винт asio

Проблема, с которой я столкнулся, когда происходит 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(), но это doesn Это звучит как хорошее решение. Не могли бы вы сообщить мне, какой подход подходит для того, чтобы сделать вилку библиотеки безопасной?

Фрагменты кода выглядят примерно так.

/** 
* 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.

+0

Ожидаете ли вы что-нибудь другое вызывать exec() или _exit() у ребенка после вызова fork? Если это так, вы должны пересмотреть. Если нет, я не вижу проблемы. – janm

+0

Вы можете зарезервировать основной поток только для администрирования, задач командного интерфейса и обработки родительского элемента. После fork в ребенке существует только основной поток. Вы можете хранить внутренние данные конфигурации для восстановления и создавать необходимые потоки в дочернем процессе. Этот способ обеспечивает чистую инкапсуляцию и позволяет избежать необходимости блокировки. –

+3

После попытки использовать boost :: asio для двух проектов, я пришел к выводу, что лучше не использовать boost. Это происходит даже на простых примерах. Его сложная структура шаблонов чрезмерно трудно понять и невозможна для значимого шага и идентифицировать даже вероятную причину. – wallyk

ответ

2

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

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

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

например. от 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(...); 
} 

Care еще необходимо принять

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

Вы можете использовать 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); 
} 
Смежные вопросы