Skip to content

Commit 208990f

Browse files
jfrochepicnoir
authored andcommitted
feat: implement X-StopOnRemoval=false in service activation
1 parent abd76fa commit 208990f

2 files changed

Lines changed: 77 additions & 1 deletion

File tree

crates/system-manager-engine/src/activate/services.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ pub fn activate(
7373
.into_iter()
7474
.partition(|(_, cfg)| cfg.masked);
7575

76-
let services_to_stop = old_services.clone().relative_complement(active.clone());
76+
let services_to_stop =
77+
get_services_to_stop(old_services.clone().relative_complement(active.clone()));
7778
let services_to_reload = get_services_to_reload(active.clone(), old_services.clone());
7879

7980
let service_manager = systemd::ServiceManager::new_session()
@@ -156,6 +157,21 @@ fn get_services_to_reload(services: Services, old_services: Services) -> Service
156157
services_to_reload
157158
}
158159

160+
fn get_services_to_stop(services_to_stop: Services) -> Services {
161+
let mut services_to_stop = services_to_stop;
162+
services_to_stop.retain(|name, service| {
163+
if let Some(ref path) = service.store_path {
164+
let unit_data = parse_unit(Path::new(&path.store_path)).ok();
165+
if !parse_systemd_bool(unit_data.as_ref(), "Unit", "X-StopOnRemoval", true) {
166+
log::info!("Skipping stop of {name}: X-StopOnRemoval=false");
167+
return false;
168+
}
169+
}
170+
true
171+
});
172+
services_to_stop
173+
}
174+
159175
fn systemd_system_dir(ephemeral: bool) -> PathBuf {
160176
if ephemeral {
161177
Path::new(path::MAIN_SEPARATOR_STR)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
forEachDistro,
3+
system-manager,
4+
system,
5+
...
6+
}:
7+
8+
let
9+
configV2 = system-manager.lib.makeSystemConfig {
10+
modules = [
11+
{
12+
nixpkgs.hostPlatform = system;
13+
}
14+
];
15+
};
16+
in
17+
18+
forEachDistro "stop-on-removal" {
19+
modules = [
20+
{
21+
systemd.services.long-running-task = {
22+
description = "Long Running Task";
23+
wantedBy = [ "system-manager.target" ];
24+
unitConfig.X-StopOnRemoval = false;
25+
serviceConfig.Type = "simple";
26+
script = ''
27+
sleep infinity
28+
'';
29+
};
30+
}
31+
];
32+
extraPathsToRegister = [ configV2 ];
33+
testScriptFunction =
34+
{ toplevel, hostPkgs, ... }:
35+
''
36+
start_all()
37+
38+
machine.wait_for_unit("multi-user.target")
39+
40+
with subtest("activate configV1 and verify service is running"):
41+
machine.activate()
42+
machine.wait_for_unit("system-manager.target")
43+
result = machine.succeed("systemctl is-active long-running-task.service").strip()
44+
assert result == "active", f"Expected active, got {result}"
45+
46+
with subtest("record initial PID"):
47+
pid_before = machine.succeed("systemctl show -p MainPID --value long-running-task.service").strip()
48+
assert pid_before != "0", "Service should have a non-zero PID"
49+
50+
with subtest("activate configV2 with X-StopOnRemoval=false (service removed from config)"):
51+
activation_logs = machine.activate("${configV2}")
52+
53+
with subtest("service was not stopped (process still alive)"):
54+
machine.succeed(f"test -d /proc/{pid_before}")
55+
56+
with subtest("activation logs show skip message"):
57+
assert "Skipping stop" in activation_logs and "long-running-task" in activation_logs, \
58+
f"Expected skip message in logs, got: {activation_logs}"
59+
'';
60+
}

0 commit comments

Comments
 (0)