Skip to content

Commit 29c2665

Browse files
committed
feat: implement X-RestartIfChanged=false in service activation
We parse the directive from the unit file text and skip restarting the service if X-RestartIfChanged=false is set required for #436
1 parent 8b78ce2 commit 29c2665

2 files changed

Lines changed: 100 additions & 1 deletion

File tree

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ pub struct ServiceConfig {
2222

2323
pub type Services = HashMap<String, ServiceConfig>;
2424

25+
fn unit_has_directive(store_path: &StorePath, directive: &str) -> bool {
26+
fs::read_to_string(&store_path.store_path)
27+
.map(|content| content.contains(directive))
28+
.unwrap_or(false)
29+
}
30+
2531
fn print_services(services: &Services) -> String {
2632
let out = itertools::intersperse(
2733
services.iter().map(|(name, entry)| {
@@ -134,7 +140,16 @@ fn get_services_to_reload(services: Services, old_services: Services) -> Service
134140
return false;
135141
}
136142
if let Some(old_service) = old_services.get(name) {
137-
service.store_path != old_service.store_path
143+
if service.store_path == old_service.store_path {
144+
return false;
145+
}
146+
if let Some(ref path) = service.store_path {
147+
if unit_has_directive(path, "X-RestartIfChanged=false") {
148+
log::info!("Skipping restart of {name}: X-RestartIfChanged=false");
149+
return false;
150+
}
151+
}
152+
true
138153
} else {
139154
// Since we run this on the intersection, this should never happen
140155
panic!("Something went terribly wrong!");
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
forEachDistro,
3+
system-manager,
4+
system,
5+
...
6+
}:
7+
8+
let
9+
makeConfig =
10+
envValue:
11+
system-manager.lib.makeSystemConfig {
12+
modules = [
13+
(
14+
{ lib, ... }:
15+
{
16+
nixpkgs.hostPlatform = system;
17+
system-manager.allowAnyDistro = true;
18+
19+
systemd.services.long-running-task = {
20+
description = "Long Running Task";
21+
wantedBy = [ "system-manager.target" ];
22+
restartIfChanged = false;
23+
serviceConfig.Type = "simple";
24+
serviceConfig.Environment = "FOO=${envValue}";
25+
script = ''
26+
sleep infinity
27+
'';
28+
};
29+
}
30+
)
31+
];
32+
};
33+
34+
configV1 = makeConfig "v1";
35+
configV2 = makeConfig "v2";
36+
in
37+
38+
forEachDistro "restart-if-changed" {
39+
modules = [
40+
{
41+
systemd.services.long-running-task = {
42+
description = "Long Running Task";
43+
wantedBy = [ "system-manager.target" ];
44+
restartIfChanged = false;
45+
serviceConfig.Type = "simple";
46+
serviceConfig.Environment = "FOO=v1";
47+
script = ''
48+
sleep infinity
49+
'';
50+
};
51+
}
52+
];
53+
extraPathsToRegister = [ configV2 ];
54+
testScriptFunction =
55+
{ toplevel, hostPkgs, ... }:
56+
''
57+
start_all()
58+
59+
machine.wait_for_unit("multi-user.target")
60+
61+
with subtest("activate configV1 and verify service is running"):
62+
activation_logs = machine.activate()
63+
machine.wait_for_unit("system-manager.target")
64+
result = machine.succeed("systemctl is-active long-running-task.service").strip()
65+
assert result == "active", f"Expected active, got {result}"
66+
67+
with subtest("record initial PID"):
68+
pid_before = machine.succeed("systemctl show -p MainPID --value long-running-task.service").strip()
69+
assert pid_before != "0", "Service should have a non-zero PID"
70+
71+
with subtest("activate configV2 with X-RestartIfChanged=false"):
72+
activation_logs = machine.activate(profile="${configV2}")
73+
machine.wait_for_unit("system-manager.target")
74+
75+
with subtest("service was not restarted (same PID)"):
76+
pid_after = machine.succeed("systemctl show -p MainPID --value long-running-task.service").strip()
77+
assert pid_after == pid_before, \
78+
f"Service was restarted: PID changed from {pid_before} to {pid_after}"
79+
80+
with subtest("activation logs show skip message"):
81+
assert "Skipping restart" in activation_logs and "long-running-task" in activation_logs, \
82+
f"Expected skip message in logs, got: {activation_logs}"
83+
'';
84+
}

0 commit comments

Comments
 (0)