C++:在 SIGUSR 信号上序列化对象

C++: Serialize object on SIGUSR signal

让我们假设一个程序在它运行到适当的时候之前必然会终止。终止信号可以由用户指定(程序在使用 MOAB 调度程序的集群上运行)。 此外,我们假设可以添加冷却时间,即在终止 (SIGKILL),

之前发送警告信号 (SIGUSR1) X seconds/minutes ]

如何保存内存状态以用于检查点目的?

我考虑了涉及 boost::serialization 感兴趣的对象 + 程序状态变量的想法,例如

class Foo
{
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & x;
        ar & y;
    }
    int x;
    int y;
public:
    Foo(){};
    Foo(int x_, int y_) :
        x(x_), y(y_)
    {}
};

void handler (const Foo & f)
{
  //serialize and dump FOO
  std::ofstream ofs("dump");
  {
      boost::archive::text_oarchive oa(ofs);
      oa << f;
  }
  // exit
  exit(0);
}


int main ()
{

  Foo bar(1, 2);

  void (*sighandler)( /* ?*/ );
  sighandler = signal (SIGUSR, handler);

  //do stuff
}

我不知道如何将 Foo 传递给信号处理程序。 据我所知,信号处理必须在全局范围内执行,即必须保存的对象不能被适当地限定范围。 因此,我的解决方案行不通。

作为记录,我查看了更高级别的选项,例如 BLCR, but the problem is that I handle parallelization at a high level with gnu-parallel,以便 BLCR 保存并行状态,而不是子进程。

正确处理信号可能涉及很多复杂的细节。例如,当异步处理信号时,仅限于使用 async-signal-safe functions。调用任何不可重入的安全函数可能会导致未定义的行为。

对于这种特殊情况,可能值得使用 Boost.Asio 对 signal handling 的支持。它处理所有底层细节,允许用户控制何时处理信号,而不受限于异步信号安全功能。

void handle_signal(const boost::system::error_code& error,
                   int signal_number)
{
  if (error) return;
  // signal occurred, can use non-reentrant functions to handle it.
}

...

// Register to handle SIGUSR1.
boost::asio::io_service io_service;
boost::asio::signal_set signals(io_service, SIGUSR1);

// Asynchronously wait for a signal to occur.
signals.async_wait(&handle_signal);

while (running)
{
  ...
  io_service.poll(); // Check it signal was received.
}

处理信号时,有两类信号:

  • thread-directed 信号是绑定到特定线程的信号。其中包括某些信号,例如 SIGSEGVSIGFPE,它们是在给定线程执行特定机器语言指令时生成的。此外,信号可以通过 pthread_kill() 发送到特定线程。一些文献将这些信号称为 同步 信号。
  • process-directed 信号是发往特定进程的信号。内核会将信号传递给任何没有阻塞信号的线程。一些文献将这些称为 异步 信号。

并且可以执行信号处理:

  • 异步:信号在任意执行点处理。当一个信号被传递给一个没有阻塞它的线程时,该线程将被中断并执行已在 sigaction() 注册的关联信号处理程序。由于线程在任意点中断,当前上下文可能是未知的,因此只能安全地调用可重入函数。例如,考虑以下情况:线程已获取互斥量,被信号中断,然后尝试在信号处理程序中获取相同的互斥量,从而导致死锁。甚至信号处理程序本身也可能被信号中断。
  • 同步:信号被阻塞并在已知点明确处理。线程可以选择阻塞信号。当线程定向信号被发送到阻塞信号的线程时,或者当所有线程都阻塞信号的进程接收到进程定向信号时,信号将被放入 pending状态。可以调用 sigwait() 等函数来获取有关挂起信号的信息。由于应用程序显式调用以获取信号信息而不是被中断,因此当前上下文是已知的,因此可以调用不可重入函数。

同步处理进程导向信号时,例如从 kill command, it is important to make sure all threads have blocked the signal. Otherwise, the signal may be asynchronously handled by another thread. This is often accomplished by using a signal mask to set blocked and ignored signals in the main thread, as child threads will inherit their parent's thread mask upon creation. However, this approach may not be guaranteed to work, as one may not have control over internal threads in third-party libraries. There is a technique that eliminates the need to manage signal mask while still allowing signals to be handled in a synchronous manner known as the self-pipe trick.

发送的信号

自管道技巧将创建一个内部管道来传递信号信息。 sigaction() 注册了一个相当基本的信号处理程序,它将通过将接收到的信号编号写入管道来异步处理信号。然后可以从管道的另一端读取以接收信号信息。 Boost.Asio 使用了此技术。此外,由于Boost.Asio使用reactor并且内部管道是非阻塞的,因此可以异步处理信号信息而不会处于中断的上下文中。

这是一个完整的示例demonstrating一个处理SIGUSR1然后优雅退出的小应用程序:

#include <chrono>
#include <iostream>
#include <thread>
#include <boost/asio.hpp>

// Mockup model.
class Foo {};

// Mockup serializer.
void store_state(Foo& foo)
{
  // ... serialize state with Boost.Serialization ...
  // ... persists state, perhaps to the filesystem or a
  //     message queue
  std::cout << "Storing state for object: " << &foo << std::endl;
}

int main()
{
  bool running = true;
  Foo foo;
  std::cout << "Object is: " << &foo << std::endl;

  // Setup signal handling.
  boost::asio::io_service io_service;
  boost::asio::signal_set signals(io_service);
  signals.add(SIGUSR1);
  signals.async_wait([&running, &foo](
      const boost::system::error_code& error,
      int signal_number) 
    {
      // On error, return early.
      if (error) return;
      std::cout << "Handling SIGUSR1: " 
                << std::boolalpha << (SIGUSR1 == signal_number)
                << std::noboolalpha << std::endl;

      // Set flag to allow graceful exit.
      running = false;

      // Store the state.
      store_state(foo);
    });

  // Main work loop.
  while (running)
  {
    std::cout << "Busy work" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    io_service.poll(); // Handle signals.
  }
  std::cout << "Finish" << std::endl;
}

执行和输出:

./a.out &
PID=$!
sleep 2
kill -s SIGUSR1 $PID
Object is: 0x7fff8a1e620f
Busy work
Busy work
Busy work
Busy work
Handling SIGUSR1: true
Storing state for object: 0x7fff8a1e620f
Finish