如何让boost asio fork安全

发布于 2025-01-07 11:27:08 字数 3724 浏览 0 评论 0原文

我使用 boost ASIO 构建了一个 C++ 库。该库需要线程安全和分叉安全。 它有服务调度程序线程,它调用 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 构建的。

I have built a C++ library using boost ASIO. The library needs to be both thread-safe and fork-safe.
It has service scheduler thread, which calls io_service::run(). To support fork-safety, I've registered pre_fork, post_fork_parent and post_fork_child handlers. The pre_fork() handler, calls _io_service.notify_fork(boost::io_service:fork_prepare(), post_fork_parent handler calls _io_service.notify_fork(boost::asio::io_service::fork_parent) and post_fork_child calls _io_service.notify_fork(boost::asio::io_service::fork_child).

The problem I'm facing in, when the fork() happens, the service scheduler thread might be in middle of some operation and might have acquired lock on data members of io_service object. So, the child process sees them in the same state and in the post_fork_child() when we call _io_service.notify_fork(boost::asio::io_service::fork_child) it tries to acquire the lock on the same object and hence gets blocked indefinitely (as there is no thread in child to release the unlock).

The stack trace I see in the child process, which is blocked, is -

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 ???????? () 

Apparently the "dev_poll_reactor" is locked (because it seems to be dispatching some pending events) in the service scheduler thread when the fork has happened which is causing the problem.

I think to solve the problem, I need to ensure that service scheduler thread is not in the middle of any processing when the fork happens and one way to guarantee that would be to call io_service.stop() in pre_fork() handler but that doesn't sound like a good solution. Could you please let me know what is the right approach to make the library fork safe?

The code snippets looks something like this.

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

I'm using boost 1.47 and running the application on Solaris i386. The library and application are built using studio-12.0.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

握住我的手 2025-01-14 11:27:08

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

在任何其他 io_service 函数时不得调用此函数,或者
与 io_service 关联的 I/O 对象上的任何函数都被
在另一个线程中调用。但是,从以下位置调用此函数是安全的
在完成处理程序中,前提是没有其他线程正在访问
io_service。

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

例如来自 Boost 文档

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. 确保在 post_fork() 完成之前不会调用 run()
  2. 确保为下一次运行创建新的工作对象
  3. 正确的同步以确保发现运行终止。

The asio code specifies that the notify_fork() do not work when there is any code in io_service code.

This function must not be called while any other io_service function, or
any function on an I/O object associated with the io_service, is being
called in another thread. It is, however, safe to call this function from
within a completion handler, provided no other thread is accessing the
io_service.

That appears to include run or any of the IO associated with the library. I think your pre_fork processing, should reset a work item.

e.g. from 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 still needs to be taken

  1. Ensure run() is not called before post_fork() has completed.
  2. Ensure new work object is created for next run
  3. Proper synchronization to ensure run termination is spotted.
倒数 2025-01-14 11:27:08

您可以使用 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);
}

You could use io_service::run_one to check if a fork is scheduled / the io_service should still be running. When a fork should be happening some work can be added to the io_service to make the thread to wake up. The thread checks the run condition and imediately stop. After the fork happened either the parent or the child can restart a worker thread.

/**
 * 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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文