【问题标题】:when shutting down linux systemd sends multiple sigterm signals to my application关闭 linux systemd 时向我的应用程序发送多个 sigterm 信号
【发布时间】:2020-04-07 07:45:51
【问题描述】:

我已经为设计为在 linux 上运行的应用程序设置了如下所示的 C++ 信号处理程序:

设置信号处理程序以调用静态函数:

// Setup the SIGNTERM signal handler for kill/pkill or systemd terminate
if (signal(SIGTERM, manager_signal_handler) == SIG_ERR)
{
    ERROR << "Failed to add signal SIGTERM to signal handler with error code: " << std::strerror(errno) << ENDL;
}

静态信号处理程序——调用类实例成员函数

static dds_manager_base *p_this_dds_manager_base = nullptr;
static void dds_manager_signal_handler(int signum)
{
    if (p_this_manager_base)
    {
        DEBUG << "static manager_signal_handler calling: p_this_manager_base->signal_handler\n";
        p_this_manager_base->signal_handler(signum);
    }
    else
    {
        ERROR << "manager_signal_handler - manager pointer not set\n";
    }
}

成员函数 - 处理信号:

void dds_manager_base::signal_handler(int signum)
{
    DEBUG << "dds_manager_base::signal_handler - received signal: " << signum << ENDL;
    // Termination actions - can take 5-10 seconds...
}

我正在使用sytemctnl powerdown 来关闭 linux(这会停止运行我的应用程序的服务)。我注意到我的应用程序接收到 2 个甚至 3 个 SIGTERM 信号,间隔大约 500 毫秒。

我可以处理这种情况,但我期望的是 1 个 SIGTERM,如果我的应用程序没有在该时间限制内终止,那么可能会在 90 秒后发出 SIGABRT。

我的问题是,在某些情况下,一旦我处理了信号,我的应用程序已经终止得足够快,以至于我的类不再存在,并且我得到一个核心转储,因为 signal_handler() 函数不再存在。因此,一旦我处理了信号,我就有想法将信号返回给default behaviour signal(SIGTERM, SIG_DFL); - 但是由于systemd在第二次调用std::exit()(我相信这是默认行为)时调用SIGTERM不止一次(我相信这是默认行为)被调用并终止我的应用程序才能正常关闭。

这是我的服务/单元文件:

[Unit]
Description=Invoke script to start the corvus application
After = network.target mnt-appdata.mount

[Service]
Type=idle
ExecStart=/mnt/appdata/deploy/services/start_module.sh ${SYS_NUM} ${DDS_NUM}

[Install]
WantedBy=multi-user.target

您可以看到脚本 start_module.sh 被调用 - 这就是运行我的应用程序的方法:./my-application &amp;

所以我的问题是: - 为什么我得到不止一个 SIGTERM 并且如此接近? - 我能做些什么来解决这种行为?

【问题讨论】:

  • 与您的问题不严格相关,但如果您的 ERRORDEBUG 是 C++ 流或类似的,则不能在信号处理程序中使用它们。只有a very restricted set 的 POSIX 系统调用和 C 库函数可以在信号处理程序中使用,特别是不包括任何用于 IO 或内存分配的标准 C 和 C++ 工具。由于在信号处理程序中几乎不可能做任何有意义的事情,所以您要做的就是引发sig_atomic_t 标志并让主程序处理关闭。
  • 为什么不在启动 SIGTERM 函数之前设置一个标志。如果标志已经设置,让信号处理程序什么都不做。使用互斥锁保护标志,或使用std::atomic
  • systemd发出第一个关机信号后,你的应用需要多长时间才能关机? Sysemd 有一种机制,它只是想确保您的应用程序在关闭电源之前关闭。 (您可以设置一些设置以防止关机继续进行,除非您的应用程序并且直到您的应用程序结束释放锁定以防止关机。systemd 发送信号进行检查没有任何问题,如果您已经开始关机过程 - - 你为什么不能忽略它们?
  • @super 在信号处理程序中使用互斥锁是在信号处理程序和“常规”代码之间获得死锁的最佳方法。此外,std::atomic&lt;T&gt; 不是异步信号安全的,除非给定的std::atomic 您正在使用is_lock_free() == true(我什至不确定它是否得到保证)。信号!=线程,不要混淆他们的东西。使用 sig_atomic_t 并对此感到满意。
  • 另外:你的应用程序是多线程的吗?

标签: c++ linux systemd


【解决方案1】:

我注意到我的应用程序收到 2 个甚至 3 个 SIGTERM 信号,间隔大约 500 毫秒。

这不是systemd 的行为,您不妨验证一下您的观察。

详情请见man systemd.kill

   KillMode=

指定如何杀死该单元的进程。对照组,过程,混合,无之一。

如果设置为 control-group,则该单元的控制组中的所有剩余进程将在单元停止时被杀死(对于服务:在执行停止命令后,按照 ExecStop= 配置)。如果设置为进程,则只有主进程本身被杀死。如果设置为混合,则 SIGTERM 信号(见下文)被发送到主进程,而随后的 SIGKILL 信号(见下文)被发送到单元控制组的所有剩余进程。如果设置为 none,则不会杀死任何进程。在这种情况下,只会在单元停止时执行停止命令,否则不会杀死进程。停止后保持活动的进程留在其控制组中,并且控制组在停止后继续存在,除非它为空。

进程将首先通过 SIGTERM 终止(除非要发送的信号通过 KillSignal= 更改)。可选地,紧随其后的是 SIGHUP(如果使用 SendSIGHUP= 启用)。如果在延迟(通过 TimeoutStopSec= 选项配置)之后,进程仍然存在,则使用 SIGKILL 信号重复终​​止请求(除非通过 SendSIGKILL= 选项禁用此功能)。有关详细信息,请参阅 kill(2)。

默认为控制组。

【讨论】:

  • @code_fodder 脚本大概应该是exec的可执行文件,这样就没有中间脚本了,使用Type=simple,见freedesktop.org/software/systemd/man/systemd.service.html
  • 为什么需要脚本/在后台启动主程序?你不能把你的“真正的”可执行文件作为ExecStart的目标吗?在simpleidle 服务中,预计启动的程序将继续运行。
  • @code_fodder 这解释了预期行为和实际行为的差异。您的脚本至少应该exec 您的可执行文件,以便systemd 将信号发送到您的可执行守护进程,而不是您的shell 脚本。否则,您必须在您的 shell 脚本中使用 trap 将信号转发到您的可执行文件,但要使其正常工作,您需要准确了解您在做什么、进程组和会话负责人、控制终端和其他重要的微小细节,例如那个。
  • @MaximEgorushkin:这就是我的怀疑,但后来我尝试了这个测试程序bitbucket.org/mitalia/test-signals-service/src/master/…,信号只传递到主线程。除非在最初启动的进程不再存在时出现一些奇怪的启发式方法......毕竟GuessMainPID 是 systemd 中存在的东西(尽管可能与这个特定问题无关)。
  • @MatteoItalia 我将不得不开始分解我的设置以查看导致多个 SIGTERM 的原因,直到我最终得到与您的示例类似的内容。我必须在两者之间的某个地方找到原因(我认为)。
猜你喜欢
  • 2020-04-27
  • 2016-10-31
  • 1970-01-01
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-05
  • 2016-01-27
相关资源
最近更新 更多