I have a .NET Core service running on a Debian 9, let's call it MyService. At some point this service is running a bash script update.sh
using Process.Start()
with ShellExecute=true
.
This script basically runs apt-get update; apt-get upgrade
.
During package upgrade, MyService process is terminated: update script is terminated as well and apt-get upgrade
is killed as well, leaving inconsistent packages that must be fixed manually.
What I want is that update.sh
is NOT terminated when MyService is terminated.
I tried splitting update.sh
in 2 parts, the first running the second in different ways; I tried starting update2.sh
with setsid
and nohup
but I always get same result.
I tried to execute update2.sh
script in a new bash shell with /bin/bash /c "update2.sh"
, same result.
How do I run a script started from a binary and completely detach from binary process, so I can kill binary while script keeps running?
Here's my environment. MyService is a binary running as a service. update.sh
is started by MyService.
.NET Core code to start shell script, inside MyService binary:
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
is never executed because it's terminated when MyService is terminated by update.sh
.
update.sh
returns code 143, it seems it has been killed.
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
UPDATE
I tried following approaches, thanks for suggestions:
- setsid
- disown
- nohup
- screen
- tmux
- unshare
Every approach has same result, termination of all spawned processes.
I suspect this is a .NET Core "feature".
UPDATE 2
I discovered that systemctl stop MyService
by default explicitly kills all spawned processes by a service.
https://stackoverflow.com/questions/40898077/systemd-systemctl-stop-aggressively-kills-subprocesses
If I add KillMode=process
to service descriptor, update script is not terminated when service is terminated.
There is NO WAY to escape from PID space for a service started by systemctl
. Every technique used, included the one in accepted answer, does not generate a separate process. Every spawned process is always killed by systemctl stop MyService
unless KillMode=process
is specified.
I ended up creating a separate service MyServiceUpdater
: this service runs the plain updater script without any forking. Since PID space is different, everything works as expected. That was a long ride.
MyServiceUpdater example:
[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
Best Answer
On a Centos 7 test system via
which results in
dotnet-sdk-2.1-2.1.400-1.x86_64
being installed then with the test codeand a shell script as
/var/tmp/foo
astrace
stalls out and shows that/var/tmp/foo
is run throughxdg-open
which on my system does...I'm not sure what, it seems a needless complication.a simpler solution is to simply
exec
a program that in turn can be a shell script that does what you want, which for .NET requires not using the shell:with this set the
strace
shows that/var/tmp/foo
is being run via a (much simpler)execve(2)
call:and that .NET refuses to exit:
because
foo
replaces itself with something that ignores most signals (notably notUSR2
, or there is alwaysKILL
(but avoid using that!)):daemonize
With a process that disassociates itself from the parent and a shell script that runs a few commands (hopefully equivalent to your intended
apt-get update; apt-get upgrade
)we can modify the .NET program to run
/var/tmp/solitary /var/tmp/a-few-things
which when run causes the .NET program to exit fairly quickly
and, eventually, the
/var/tmp/output
file does contain two lines written by a process that was not killed when the .NET program when away.You probably should save the output from the APT commands somewhere, and may also need something so that two (or more!) updates are not trying to be run at the same time, etc. This version does not stop for questions and ignores any
TERM
signals (INT
may also need to be ignored).