switch-to-configuration-ng: Reload when ExecReload changes (#512578)

This commit is contained in:
Ramses
2026-04-28 15:12:55 +00:00
committed by GitHub
2 changed files with 156 additions and 10 deletions
@@ -347,6 +347,8 @@ See <https://github.com/NixOS/nixpkgs/issues/481673>.
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
- `switch-to-configuration` now reloads a service instead of restarting it when the only change to its unit is `ExecReload=`, and takes no action when `ExecReload=` is removed. Previously both cases triggered a restart.
- [`hardware.nvidia.branch`](#opt-hardware.nvidia.branch) was added to select the NVIDIA driver branch; setting [`hardware.nvidia.package`](#opt-hardware.nvidia.package) overrides this.
- The NixOS NVIDIA module wiring has been updated to match the new `nvidia-x11` output layout.
@@ -489,9 +489,10 @@ enum UnitComparison {
// Compare the contents of two unit files and return whether the unit needs to be restarted or
// reloaded. If the units differ, the service is restarted unless the only difference is
// `X-Reload-Triggers` in the `Unit` section. If this is the only modification, the unit is
// reloaded instead of restarted. If the only difference is `Options` in the `[Mount]` section, the
// unit is reloaded rather than restarted.
// `X-Reload-Triggers` in the `[Unit]` section, `Options` in the `[Mount]` section, or `ExecReload`
// in the `[Service]` section, in which case the unit is reloaded rather than restarted. Removing
// `ExecReload` is treated as a no-op since the running process is unaffected and the new unit can
// no longer be reloaded.
fn compare_units(current_unit: &UnitInfo, new_unit: &UnitInfo) -> UnitComparison {
let mut ret = UnitComparison::Equal;
@@ -532,6 +533,13 @@ fn compare_units(current_unit: &UnitInfo, new_unit: &UnitInfo) -> UnitComparison
}
}
continue; // check the next section
} else if section_name == "Service"
&& section_val.len() == 1
&& section_val.contains_key("ExecReload")
{
// Dropping ExecReload does not affect the running process and the
// new unit can no longer be reloaded, so there is nothing to do.
continue;
} else {
return UnitComparison::UnequalNeedsRestart;
}
@@ -562,6 +570,11 @@ fn compare_units(current_unit: &UnitInfo, new_unit: &UnitInfo) -> UnitComparison
if section_name == "Unit" && unit_section_ignores.contains_key(ini_key.as_str()) {
continue;
}
// Dropping ExecReload does not affect the running process and the
// new unit can no longer be reloaded, so there is nothing to do.
if section_name == "Service" && ini_key == "ExecReload" {
continue;
}
return UnitComparison::UnequalNeedsRestart;
};
@@ -576,6 +589,12 @@ fn compare_units(current_unit: &UnitInfo, new_unit: &UnitInfo) -> UnitComparison
}
}
// If this is a service unit, check if it was only `ExecReload`
if section_name == "Service" && ini_key == "ExecReload" {
ret = UnitComparison::UnequalNeedsReload;
continue;
}
// If this is a mount unit, check if it was only `Options`
if section_name == "Mount" && ini_key == "Options" {
ret = UnitComparison::UnequalNeedsReload;
@@ -598,6 +617,12 @@ fn compare_units(current_unit: &UnitInfo, new_unit: &UnitInfo) -> UnitComparison
return UnitComparison::UnequalNeedsRestart;
}
}
} else if section_name == "Service"
&& ini_cmp.len() == 1
&& ini_cmp.contains_key("ExecReload")
{
ret = UnitComparison::UnequalNeedsReload;
continue;
} else {
return UnitComparison::UnequalNeedsRestart;
}
@@ -606,15 +631,28 @@ fn compare_units(current_unit: &UnitInfo, new_unit: &UnitInfo) -> UnitComparison
// A section was introduced that was missing in the previous unit
if !section_cmp.is_empty() {
if section_cmp.keys().len() == 1 && section_cmp.contains_key("Unit") {
if let Some(new_unit_unit) = new_unit.get("Unit") {
for ini_key in new_unit_unit.keys() {
if !unit_section_ignores.contains_key(ini_key.as_str()) {
return UnitComparison::UnequalNeedsRestart;
} else if ini_key == "X-Reload-Triggers" {
ret = UnitComparison::UnequalNeedsReload;
if section_cmp.keys().len() == 1 {
// Dispatch on which section is actually new.
if section_cmp.contains_key("Unit") {
if let Some(new_unit_unit) = new_unit.get("Unit") {
for ini_key in new_unit_unit.keys() {
if !unit_section_ignores.contains_key(ini_key.as_str()) {
return UnitComparison::UnequalNeedsRestart;
} else if ini_key == "X-Reload-Triggers" {
ret = UnitComparison::UnequalNeedsReload;
}
}
}
} else if section_cmp.contains_key("Service") {
if let Some(new_unit_service) = new_unit.get("Service") {
if new_unit_service.len() == 1 && new_unit_service.contains_key("ExecReload") {
ret = UnitComparison::UnequalNeedsReload;
} else {
return UnitComparison::UnequalNeedsRestart;
}
}
} else {
return UnitComparison::UnequalNeedsRestart;
}
} else {
return UnitComparison::UnequalNeedsRestart;
@@ -2623,6 +2661,112 @@ invalid
) == super::UnitComparison::UnequalNeedsReload
);
assert!(
super::compare_units(
&HashMap::from([]),
&HashMap::from([(
"Service".to_string(),
HashMap::from([("ExecReload".to_string(), vec!["foobar".to_string()])])
)])
) == super::UnitComparison::UnequalNeedsReload
);
assert!(
super::compare_units(
&HashMap::from([(
"Service".to_string(),
HashMap::from([("ExecReload".to_string(), vec!["foobar".to_string()])])
)]),
&HashMap::from([(
"Service".to_string(),
HashMap::from([("ExecReload".to_string(), vec!["barfoo".to_string()])])
)])
) == super::UnitComparison::UnequalNeedsReload
);
// New [Service] section while [Unit] already existed: must inspect
// the [Service] section, not the (unchanged) [Unit] one.
assert!(
super::compare_units(
&HashMap::from([(
"Unit".to_string(),
HashMap::from([("Description".to_string(), vec!["x".to_string()])])
)]),
&HashMap::from([
(
"Unit".to_string(),
HashMap::from([("Description".to_string(), vec!["x".to_string()])])
),
(
"Service".to_string(),
HashMap::from([("ExecStart".to_string(), vec!["y".to_string()])])
),
]),
) == super::UnitComparison::UnequalNeedsRestart
);
assert!(
super::compare_units(
&HashMap::from([(
"Unit".to_string(),
HashMap::from([("Description".to_string(), vec!["x".to_string()])])
)]),
&HashMap::from([
(
"Unit".to_string(),
HashMap::from([("Description".to_string(), vec!["x".to_string()])])
),
(
"Service".to_string(),
HashMap::from([("ExecReload".to_string(), vec!["y".to_string()])])
),
]),
) == super::UnitComparison::UnequalNeedsReload
);
// ExecReload removed: running process is unaffected and the new
// unit cannot be reloaded, so no action is needed.
assert!(
super::compare_units(
&HashMap::from([(
"Service".to_string(),
HashMap::from([
("ExecStart".to_string(), vec!["x".to_string()]),
("ExecReload".to_string(), vec!["y".to_string()]),
])
)]),
&HashMap::from([(
"Service".to_string(),
HashMap::from([("ExecStart".to_string(), vec!["x".to_string()])])
)]),
) == super::UnitComparison::Equal
);
assert!(
super::compare_units(
&HashMap::from([(
"Service".to_string(),
HashMap::from([("ExecReload".to_string(), vec!["y".to_string()])])
)]),
&HashMap::from([]),
) == super::UnitComparison::Equal
);
// ExecReload added to an existing [Service] section
assert!(
super::compare_units(
&HashMap::from([(
"Service".to_string(),
HashMap::from([("ExecStart".to_string(), vec!["x".to_string()])])
)]),
&HashMap::from([(
"Service".to_string(),
HashMap::from([
("ExecStart".to_string(), vec!["x".to_string()]),
("ExecReload".to_string(), vec!["y".to_string()]),
])
)]),
) == super::UnitComparison::UnequalNeedsReload
);
assert!(
super::compare_units(
&HashMap::from([(