如何使提升asio fork安全

时间:2012-02-20 13:53:10

标签: c++ boost fork boost-asio

我使用boost ASIO构建了一个C ++库。该库需要既是线程安全的又是fork安全的。 它有服务调度程序线程,调用io_service::run()。为了支持fork-safety,我已经注册了pre_fork,post_fork_parent和post_fork_child处理程序。 pre_fork()处理程序,调用_io_service.notify_fork(boost::io_service:fork_prepare(),post_fork_parent处理程序调用{​​{1}}和post_fork_child调用_io_service.notify_fork(boost::asio::io_service::fork_parent)

我遇到的问题,当_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)

显然,当fork发生导致问题时,“dev_poll_reactor”在服务调度程序线程中被锁定(因为它似乎正在调度一些挂起的事件)。

我认为要解决这个问题,我需要确保服务调度程序线程在fork发生时不处于任何处理的中间,并且保证在pre_fork()处理程序中调用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 ???????? () 的一种方法但这听起来不是一个好的解决方案。能告诉我什么是使图书馆叉安全的正确方法吗?

代码片段看起来像这样。

io_service.stop()

我正在使用boost 1.47并在Solaris i386上运行该应用程序。库和应用程序使用studio-12.0构建。

2 个答案:

答案 0 :(得分:2)

asio代码指定当{io_service代码中有任何代码时notify_fork()不起作用。

  

在任何其他io_service函数时,不得调用此函数,或   正在使用与io_service关联的I / O对象上的任何函数   在另一个线程中调用。但是,从中调用此函数是安全的   在完成处理程序内,如果没有其他线程正在访问   io_service对象。

这似乎包括run或与库关联的任何IO。我认为你的pre_fork处理应该重置一个工作项。

e.g。来自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(...);
}

仍然需要照顾

  1. 确保在run()完成之前未调用post_fork()
  2. 确保为下一个work
  3. 创建新的run对象
  4. 确保发现run终止的正确同步。

答案 1 :(得分:0)

您可以使用io_service :: run_one来检查是否已安排fork / io_service是否仍在运行。当fork应该发生时,可以将一些工作添加到io_service以使线程唤醒。线程检查运行条件并立即停止。发生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;
    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);
}