我有一个 .NET 6 应用程序在 Linux 上作为系统服务运行。此应用程序使用如下命令执行另一个程序:
var process = Process.Start(new ProcessStartInfo
{
FileName = "foo",
Arguments = "bar",
});
即使启动它的应用程序终止,该进程也应继续运行。据我了解,这是
Process.Start
应该做的。但就我而言,我现在观察到当我通过 systemctl stop
. 停止原始进程时,生成的进程与原始进程同时终止
两者都在同一毫秒内停止,我为被触发的
AppDomain.CurrentDomain.ProcessExit
事件添加了一个处理程序,这确实向我表明它们都从 systemd 获得了 SIGTERM。
现在,我假设 systemd 在这里做了一些其他事情来清理服务,因为通常我的派生进程应该在原始进程死亡时继续存在。我怎样才能以一种在父进程停止时不会被 systemd 杀死的方式启动进程?
Systemd 通过设计在服务应该停止时清理服务的 cgroup 中的所有剩余进程——它将发送配置的 KillSignal (SIGTERM) 并在必要时几秒钟后跟进 SIGKILL。
没有 Start() 选项可以让您的进程避免从 C# 代码中进行清理。在 systemd .service 单元级别,服务可以使用
KillMode=
部分选择退出清理,但有计划最终删除此选项。
启动需要比您的服务更持久的进程的正确方法是使其不再是服务的一部分——也就是说,使用systemd D-Bus API或
systemd-run
工具来a)拥有systemd本身将进程作为自己的瞬态服务启动,或者 b) 将您刚刚启动的进程移动到它自己的瞬态 .scope 单元中。 (后者不能通过 systemd-run 完成,只能通过 D-Bus API。)
例如,使用 Tmds.DBus(它似乎比 dbus-sharp 维护得更积极),将 PID 分离到它自己的 .scope 的代码可能看起来有点像这样:
var bus = Tmds.DBus.Connection.System;
var sd = bus.CreateProxy<IHaveNoIdea>("org.freedesktop.systemd1",
"/org/freedesktop/systemd1");
var properties = /* I don't remember how to create dicts in C# */;
properties.Add("Description", "FooBar worker process");
properties.Add("PIDs", new int[] { pid_of_worker });
properties.Add("CollectMode", "inactive-or-failed");
var aux = ...;
var job = sd.StartTransientUnit("foobar-worker.scope", "fail", properties, aux);
或者,您可以定义一个普通的 .service 单元,您的主程序可以启动它——如果有问题的进程只需要接收最多一个选项,这将起作用,该选项可以作为服务实例名称传递。