web-dev-qa-db-ja.com

親プロセスが終了するとスクリプトが停止する

Debian9で実行されている.NETCoreサービスがあります。これをMyServiceと呼びましょう。ある時点で、このサービスはProcess.Start()と_update.sh_を使用してbashスクリプト_ShellExecute=true_を実行しています。

このスクリプトは基本的に_apt-get update; apt-get upgrade_を実行します。

パッケージのアップグレード中に、MyServiceプロセスが終了します。更新スクリプトも終了し、_apt-get upgrade_も強制終了されるため、一貫性のないパッケージが残り、手動で修正する必要があります。

私が欲しいのは、MyServiceが終了しても_update.sh_は終了しないということです。

_update.sh_を2つの部分に分割してみました。最初の部分は、2番目の部分をさまざまな方法で実行しました。 _update2.sh_をsetsidNohupで開始しようとしましたが、常に同じ結果が得られます。新しいbashシェルで_update2.sh_スクリプトを_/bin/bash /c "update2.sh"_で実行しようとしましたが、同じ結果になりました。

バイナリから開始され、バイナリプロセスから完全に切り離されたスクリプトを実行して、スクリプトの実行中にバイナリを強制終了するにはどうすればよいですか?

これが私の環境です。 MyServiceは、サービスとして実行されるバイナリです。 _update.sh_はMyServiceによって開始されます。

。MyServiceバイナリ内でシェルスクリプトを開始するためのNET Coreコード:

_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");
}
_

update.sh:

_Nohup /opt/myservice/update2.sh > /opt/myservice/update.log &
systemctl stop MyService
_

update2.sh:

_apt-get update >> /opt/myservice/update.log
apt-get -y install --only-upgrade myservice-1.0 >> /opt/myservice/update.log
_

_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
_

[〜#〜]更新[〜#〜]

私は次のアプローチを試しました、提案に感謝します:

  • setsid
  • 否認
  • Nohup
  • 画面
  • tmux
  • 共有解除

すべてのアプローチで同じ結果が得られ、生成されたすべてのプロセスが終了します。これは.NETCoreの「機能」だと思います。

UPDATE 2

_systemctl stop MyService_は、デフォルトで、サービスによって生成されたすべてのプロセスを明示的に強制終了することを発見しました。

https://stackoverflow.com/questions/40898077/systemd-systemctl-stop-aggressively-kills-subprocesses

サービス記述子に_KillMode=process_を追加すると、サービスが終了しても更新スクリプトが終了しません。

systemctlで開始されたサービスのPIDスペースから脱出する方法はNO WAYです。受け入れられた回答に含まれるものを含め、使用されるすべての手法は、個別のプロセスを生成しません。生成されたすべてのプロセスは、_systemctl stop MyService_が指定されていない限り、常に_KillMode=process_によって強制終了されます。

結局、別のサービスMyServiceUpdaterを作成しました。このサービスは、フォークなしでプレーンアップデータスクリプトを実行します。 PIDスペースが異なるため、すべてが期待どおりに機能します。それは長い道のりでした。

2
Ghigo

Centos7テストシステムで

_$ Sudo rpm -Uvh https://packages.Microsoft.com/config/rhel/7/packages-Microsoft-prod.rpm
$ Sudo yum install dotnet-sdk-2.1
_

その結果、_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");
            }
        }
    }
}
_

_/var/tmp/foo_ a 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 ...>
...
_

より簡単な解決策は、プログラムをexecすることです。このプログラムは、必要なことを実行するシェルスクリプトにすることができます。これは、.NETではシェルを使用する必要がありません。

_            process.StartInfo.UseShellExecute = false;
_

このセットを使用すると、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 ...>
_

そして、その.NETは終了を拒否します。

_$ strace -o foo -f dotnet run
Child process still running after 10 seconds
^C^C^C^C^C^C^C^C
_

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 - @_";
}
_

デーモン化

親との関連付けを解除するプロセス およびいくつかのコマンドを実行するシェルスクリプト(目的の_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
_

.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;
_

これを実行すると、.NETプログラムがかなり早く終了します。

_$ dotnet run
Exit code: 0
_

そして、最終的に、_/var/tmp/output_ファイルには、.NETプログラムが離れているときに強制終了されなかったプロセスによって書き込まれた2行が含まれています。

APTコマンドからの出力をどこかに保存する必要があります。また、2つ(またはそれ以上!)の更新が同時に実行されないようにするために何かが必要になる場合もあります。このバージョン質問のために停止せず、TERMシグナルを無視します(INTも無視する必要がある場合があります)。

_#!/bin/sh
trap '' TERM
set -e
apt-get --yes update
apt-get --yes upgrade
_
1
thrig

Crontab(またはat)(mono/.netではない)を使用してタスクをスケジュールします。

通常のオプション。

  • Nohup my.sh&
  • screen -dm -S my my.sh
  • tmux new -d -s my my.sh
  • service my start/systemctl start my
  • Ctrl + Z、bg、否認
2
user1133275

まったく同じことを自分で戦ってきました。

私が見つけた解決策の1つは、systemd-runを使用することです。これは、基本的に、任意のコマンドを実行するための一時的な1回限りのサービスを作成します。ただし、これを機能させるには、-r(remain-after-exit)引数を指定する必要があるようです。詳細なドキュメントについては、 https://www.freedesktop.org/software/systemd/man/systemd-run.html を参照してください。

例えば:

var process = new Process()
{
    StartInfo = new ProcessStartInfo
    {
        FileName = "systemd-run",
        Arguments = "-r sleep 90",
        RedirectStandardOutput = false,
        RedirectStandardError = false,
        UseShellExecute = false,
        CreateNoWindow = true,
    },
};
process.EnableRaisingEvents = true;
process.Start();
0
Tom