switch-to-configuration-ng: wait for NameOwnerChanged after systemd Reexecute
To re-execute a systemd manager interface, the current switch-to-configuration-ng code calls the org.freedesktop.systemd1.Manager.Reexecute method on D-Bus. As noted in a comment, the systemd manager does not reply to that method call (it has the org.freedesktop.DBus.Method.NoReply attribute). Instead, to signal when it is done re-executing, it passes its old D-Bus connection socket to the new systemd manager and once the new systemd manager is ready to receive D-Bus messages on the new D-Bus connection socket, it will close the old connection socket. As the old connection socket is closed, the D-Bus daemon assigns the org.freedesktop.systemd1 bus name to the new D-Bus connection [1]. The dbus-codegen crate does not support the org.freedesktop.DBus.Method.NoReply attribute. What currently happens in switch-to-configuration-ng is that it blocks until the old connection socket is closed which makes the D-Bus daemon send a org.freedesktop.DBus.Error.NoReply error to switch-to-configuration-ng which then unblocks it. If the systemd manager takes a long time to re-execute (more than 10 seconds), then the method call can also timeout which means that switch-to-configuration-ng will proceed with the next systemd manager method calls, leading to the following error: > Error: Failed to restart nixos-activation.service > > Caused by: > Message recipient disconnected from message bus without replying This was observed on a server with a very large number of filesystem mounts that make re-execution take more than 10 seconds. Use the org.freedesktop.DBus.NameOwnerChanged signal [2] to determine when systemd is done reexecuting and is ready to receive new D-Bus messages again. [1]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-overview [2]: https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-name-owner-changed
This commit is contained in:
@@ -18,6 +18,10 @@ fn main() {
|
||||
|
||||
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
|
||||
let fdo_dbus_code = code_for_dbus_xml("org.freedesktop.DBus.xml");
|
||||
let mut file = std::fs::File::create(out_path.join("fdo_dbus.rs")).unwrap();
|
||||
file.write_all(fdo_dbus_code.as_bytes()).unwrap();
|
||||
|
||||
let systemd_manager_code =
|
||||
code_for_dbus_xml(systemd_dbus_interface_dir.join("org.freedesktop.systemd1.Manager.xml"));
|
||||
let mut file = std::fs::File::create(out_path.join("systemd_manager.rs")).unwrap();
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<!-- From https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-messages. -->
|
||||
<interface name="org.freedesktop.DBus">
|
||||
<signal name="NameOwnerChanged">
|
||||
<arg type="s" direction="out" name="name"/>
|
||||
<arg type="s" direction="out" name="old_owner"/>
|
||||
<arg type="s" direction="out" name="new_owner"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
@@ -17,6 +17,8 @@ use std::{
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use dbus::{
|
||||
blocking::{stdintf::org_freedesktop_dbus::Properties, LocalConnection, Proxy},
|
||||
channel::Sender,
|
||||
strings::{BusName, Interface, Member},
|
||||
Message,
|
||||
};
|
||||
use glob::glob;
|
||||
@@ -32,6 +34,15 @@ use nix::{
|
||||
use regex::Regex;
|
||||
use syslog::Facility;
|
||||
|
||||
mod fdo_dbus {
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(unused)]
|
||||
#![allow(clippy::all)]
|
||||
include!(concat!(env!("OUT_DIR"), "/fdo_dbus.rs"));
|
||||
}
|
||||
|
||||
mod systemd_manager {
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
@@ -50,7 +61,9 @@ mod logind_manager {
|
||||
include!(concat!(env!("OUT_DIR"), "/logind_manager.rs"));
|
||||
}
|
||||
|
||||
use crate::systemd_manager::OrgFreedesktopSystemd1Manager;
|
||||
use crate::{
|
||||
fdo_dbus::OrgFreedesktopDBusNameOwnerChanged, systemd_manager::OrgFreedesktopSystemd1Manager,
|
||||
};
|
||||
use crate::{
|
||||
logind_manager::OrgFreedesktopLogin1Manager,
|
||||
systemd_manager::{
|
||||
@@ -894,6 +907,14 @@ impl std::fmt::Display for Job {
|
||||
}
|
||||
}
|
||||
|
||||
fn fdo_dbus_proxy(conn: &LocalConnection) -> Proxy<'_, &LocalConnection> {
|
||||
conn.with_proxy(
|
||||
"org.freedesktop.DBus",
|
||||
"/org/freedesktop/DBus",
|
||||
Duration::from_millis(500),
|
||||
)
|
||||
}
|
||||
|
||||
fn systemd1_proxy(conn: &LocalConnection) -> Proxy<'_, &LocalConnection> {
|
||||
conn.with_proxy(
|
||||
"org.freedesktop.systemd1",
|
||||
@@ -930,6 +951,54 @@ fn remove_file_if_exists(p: impl AsRef<Path>) -> std::io::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
fn reexecute_systemd_manager(
|
||||
dbus_conn: &LocalConnection,
|
||||
fdo_dbus: &Proxy<'_, &LocalConnection>,
|
||||
) -> anyhow::Result<()> {
|
||||
let reexecute_done = Rc::new(RefCell::new(false));
|
||||
let _reexecute_done = reexecute_done.clone();
|
||||
let owner_changed_token = fdo_dbus
|
||||
.match_signal(
|
||||
move |signal: OrgFreedesktopDBusNameOwnerChanged, _: &LocalConnection, _: &Message| {
|
||||
if signal.name.as_str() == "org.freedesktop.systemd1" {
|
||||
*_reexecute_done.borrow_mut() = true;
|
||||
}
|
||||
|
||||
true
|
||||
},
|
||||
)
|
||||
.context("Failed to add signal match for DBus name owner changes")?;
|
||||
|
||||
let bus_name = BusName::from("org.freedesktop.systemd1");
|
||||
let object_path = dbus::Path::from("/org/freedesktop/systemd1");
|
||||
let interface = Interface::new("org.freedesktop.systemd1.Manager")
|
||||
.expect("the org.freedesktop.systemd1.Manager interface name should be valid");
|
||||
let method_name = Member::new("Reexecute").expect("the Reexecute method name should be valid");
|
||||
|
||||
// Systemd does not reply to the Reexecute method.
|
||||
let _serial = dbus_conn
|
||||
.send(Message::method_call(
|
||||
&bus_name,
|
||||
&object_path,
|
||||
&interface,
|
||||
&method_name,
|
||||
))
|
||||
.map_err(|_err| anyhow!("Failed to send org.freedesktop.systemd1.Manager.Reexecute"))?;
|
||||
|
||||
log::debug!("waiting for systemd to finish reexecuting");
|
||||
while !*reexecute_done.borrow() {
|
||||
_ = dbus_conn
|
||||
.process(Duration::from_secs(500))
|
||||
.context("Failed to process dbus messages")?;
|
||||
}
|
||||
|
||||
dbus_conn
|
||||
.remove_match(owner_changed_token)
|
||||
.context("Failed to remove jobs token")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Performs switch-to-configuration functionality for a single non-root user
|
||||
fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
|
||||
if Path::new(&parent_exe)
|
||||
@@ -945,8 +1014,11 @@ fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
let dbus_conn = LocalConnection::new_session().context("Failed to open dbus connection")?;
|
||||
let fdo_dbus = fdo_dbus_proxy(&dbus_conn);
|
||||
let systemd = systemd1_proxy(&dbus_conn);
|
||||
|
||||
reexecute_systemd_manager(&dbus_conn, &fdo_dbus)?;
|
||||
|
||||
let nixos_activation_done = Rc::new(RefCell::new(false));
|
||||
let _nixos_activation_done = nixos_activation_done.clone();
|
||||
let jobs_token = systemd
|
||||
@@ -963,10 +1035,6 @@ fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
|
||||
)
|
||||
.context("Failed to add signal match for systemd removed jobs")?;
|
||||
|
||||
// The systemd user session seems to not send a Reloaded signal, so we don't have anything to
|
||||
// wait on here.
|
||||
_ = systemd.reexecute();
|
||||
|
||||
systemd
|
||||
.restart_unit("nixos-activation.service", "replace")
|
||||
.context("Failed to restart nixos-activation.service")?;
|
||||
@@ -1139,6 +1207,7 @@ won't take effect until you reboot the system.
|
||||
let mut units_to_reload = map_from_list_file(RELOAD_LIST_FILE);
|
||||
|
||||
let dbus_conn = LocalConnection::new_system().context("Failed to open dbus connection")?;
|
||||
let fdo_dbus = fdo_dbus_proxy(&dbus_conn);
|
||||
let systemd = systemd1_proxy(&dbus_conn);
|
||||
let logind = login1_proxy(&dbus_conn);
|
||||
|
||||
@@ -1664,7 +1733,7 @@ won't take effect until you reboot the system.
|
||||
// just in case the new one has trouble communicating with the running pid 1.
|
||||
if restart_systemd {
|
||||
eprintln!("restarting systemd...");
|
||||
_ = systemd.reexecute(); // we don't get a dbus reply here
|
||||
reexecute_systemd_manager(&dbus_conn, &fdo_dbus)?;
|
||||
|
||||
log::debug!("waiting for systemd restart to finish");
|
||||
while !*systemd_reload_status.borrow() {
|
||||
|
||||
Reference in New Issue
Block a user