当父进程终止时脚本终止

Ghi*_*igo 6 bash debian shell-script

我有一个在 Debian 9 上运行的 .NET Core 服务,我们将其称为 MyService。update.sh在某些时候,该服务正在使用Process.Start()with运行 bash 脚本ShellExecute=true

这个脚本基本上可以运行apt-get update; apt-get upgrade

在软件包升级期间,MyService 进程会终止:更新脚本也会终止并被apt-get upgrade终止,留下必须手动修复的不一致软件包。

我想要的是update.sh当 MyService 终止时它不会终止。

我尝试将其分成update.sh两部分,第一部分以不同的方式运行第二部分;我尝试update2.shsetsidand开始nohup,但总是得到相同的结果。我尝试update2.sh在新的 bash shell 中执行脚本/bin/bash /c "update2.sh",结果相同。

如何运行从二进制文件启动的脚本并完全与二进制进程分离,以便我可以在脚本继续运行时杀死二进制文件?

这是我的环境。MyService 是作为服务运行的二进制文件。update.sh由 MyService 启动。

用于启动 shell 脚本的 .NET Core 代码,位于 MyService 二进制文件中:

var process = new Process();
process.EnableRaisingEvents = true; // to avoid [defunct] sh processes
process.StartInfo.FileName = "/opt/myservice/update.sh";
process.StartInfo.Arguments = "";
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.WaitForExit(10000);
if (process.HasExited)
{
  Console.WriteLine("Exit code: " + process.ExitCode);
}
else
{
  Console.WriteLine("Child process still running after 10 seconds");
}
Run Code Online (Sandbox Code Playgroud)

更新.sh:

nohup /opt/myservice/update2.sh > /opt/myservice/update.log &
systemctl stop MyService
Run Code Online (Sandbox Code Playgroud)

更新2.sh:

apt-get update >> /opt/myservice/update.log
apt-get -y install --only-upgrade myservice-1.0 >> /opt/myservice/update.log
Run Code Online (Sandbox Code Playgroud)

update2.sh永远不会执行,因为当 MyService 被 终止时它就会终止update.sh

update.sh返回代码143,看来它已经被杀死了。

2018-08-16 14:46:14.5215|Running update script: /opt/myservice/update.sh
2018-08-16 14:46:14.5883|Update script /opt/myservice/update.sh returned: 143
Run Code Online (Sandbox Code Playgroud)

更新

我尝试了以下方法,感谢您的建议:

  • 设定值
  • 否认
  • 诺哈普
  • 屏幕
  • 多路复用器
  • 取消共享

每种方法都有相同的结果,即终止所有生成的进程。我怀疑这是一个 .NET Core“功能”。

更新2

我发现systemctl stop MyService默认情况下会显式终止服务生成的所有进程。

/sf/ask/2862865421/

如果我添加KillMode=process到服务描述符,则服务终止时更新脚本不会终止。

对于由 启动的服务,无法逃离PID 空间systemctl。使用的每一种技术,包括接受答案中的技术,都不会生成单独的过程。systemctl stop MyService除非KillMode=process指定,否则每个生成的进程总是被杀死。

我最终创建了一个单独的服务MyServiceUpdater:该服务运行简单的更新程序脚本,无需任何分叉。由于 PID 空间不同,一切都按预期进行。那是一段漫长的旅程。

MyServiceUpdater 示例:

[Unit]
Description=Your Service Updater
After=network.target

[Service]
ExecStart=/path/to/update/script/updatescript.sh
ExecStopPost=
TimeoutStopSec=30
StandardOutput=null
WorkingDirectory=/path/to/service/directory/
KillMode=process

[Install]
WantedBy=multi-user.target
Run Code Online (Sandbox Code Playgroud)

thr*_*rig 1

在 Centos 7 测试系统上通过

$ sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm
$ sudo yum install dotnet-sdk-2.1
Run Code Online (Sandbox Code Playgroud)

这会导致dotnet-sdk-2.1-2.1.400-1.x86_64使用测试代码进行安装

using System;
using System.Diagnostics;
using System.ComponentModel;
namespace myApp {
    class Program {
        static void Main(string[] args) {
            var process = new Process();
            process.EnableRaisingEvents = true; // to avoid [defunct] sh processes
            process.StartInfo.FileName = "/var/tmp/foo";
            process.StartInfo.Arguments = "";
            process.StartInfo.UseShellExecute = true;
            process.StartInfo.CreateNoWindow = true;
            process.Start();
            process.WaitForExit(10000);
            if (process.HasExited) {
                Console.WriteLine("Exit code: " + process.ExitCode);
            } else {
                Console.WriteLine("Child process still running after 10 seconds");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

和一个 shell 脚本作为/var/tmp/foo一个strace停止并显示它在我的系统上/var/tmp/foo运行xdg-open...我不知道什么,这似乎是一个不必要的复杂化。

$ strace -o foo -f dotnet run
Child process still running after 10 seconds
^C
$ grep /var/tmp/foo foo
25907 execve("/usr/bin/xdg-open", ["/usr/bin/xdg-open", "/var/tmp/foo"], [/* 37 vars */] <unfinished ...>
...
Run Code Online (Sandbox Code Playgroud)

一个更简单的解决方案是简单地创建exec一个程序,该程序又可以是执行您想要的操作的 shell 脚本,对于 .NET 来说,这不需要使用 shell:

            process.StartInfo.UseShellExecute = false;
Run Code Online (Sandbox Code Playgroud)

通过此设置,strace显示/var/tmp/foo正在通过(更简单的)execve(2)调用运行:

26268 stat("/var/tmp/foo", {st_mode=S_IFREG|0755, st_size=37, ...}) = 0
26268 access("/var/tmp/foo", X_OK)      = 0
26275 execve("/var/tmp/foo", ["/var/tmp/foo"], [/* 37 vars */] <unfinished ...>
Run Code Online (Sandbox Code Playgroud)

并且 .NET 拒绝退出:

$ strace -o foo -f dotnet run
Child process still running after 10 seconds
^C^C^C^C^C^C^C^C
Run Code Online (Sandbox Code Playgroud)

因为foo用忽略大多数信号的东西替换自身(特别是不是USR2,或者总是有KILL(但避免使用它!)):

$ cat /var/tmp/foo
#!/bin/sh
exec /var/tmp/stayin-alive
$ cat /var/tmp/stayin-alive
#!/usr/bin/perl
use Sys::Syslog;
for my $s (qw(HUP INT QUIT PIPE ALRM TERM CHLD USR1)) {
   $SIG{$s} = \&shandle;
}
openlog( 'stayin-alive', 'ndelay,pid', LOG_USER );
while (1) {
    syslog LOG_NOTICE, "oh oh oh oh oh stayin alive";
    sleep 7;
}
sub shandle {
    syslog LOG_NOTICE, "nice try - @_";
}
Run Code Online (Sandbox Code Playgroud)

守护进程

使用一个与父进程分离的进程和一个运行一些命令的 shell 脚本(希望与您想要的相同apt-get update; apt-get upgrade

$ cat /var/tmp/a-few-things
#!/bin/sh
sleep 17 ; echo a >/var/tmp/output ; echo b >/var/tmp/output
Run Code Online (Sandbox Code Playgroud)

我们可以修改.NET程序来运行/var/tmp/solitary /var/tmp/a-few-things

            process.StartInfo.FileName = "/var/tmp/solitary";
            process.StartInfo.Arguments = "/var/tmp/a-few-things";
            process.StartInfo.UseShellExecute = false;
Run Code Online (Sandbox Code Playgroud)

运行时会导致 .NET 程序相当快地退出

$ dotnet run
Exit code: 0
Run Code Online (Sandbox Code Playgroud)

最终,该/var/tmp/output文件确实包含由 .NET 程序离开时未被终止的进程写入的两行。

您可能应该将 APT 命令的输出保存在某个地方,并且可能还需要一些东西,以便两个(或更多!)更新不会尝试同时运行,等等。此版本不会因问题而停止并忽略任何TERM信号(INT可能也需要被忽略)。

#!/bin/sh
trap '' TERM
set -e
apt-get --yes update
apt-get --yes upgrade
Run Code Online (Sandbox Code Playgroud)