Skip to main content

systemd_transient/
handle.rs

1use crate::{
2  SystemdTransientManager, UnitExit,
3  dbus::unit::UnitProxy,
4  error::SystemdTransientError,
5  watch::{ExitWatch, WatchState},
6};
7
8/// Holds a reference on the unit (`AddRef=true`), so systemd keeps it loaded
9/// after it exits. Release it with [`release`](Self::release) or by dropping
10/// the handle; until then, starting a unit of the same name fails with
11/// `UnitStillLoaded`.
12#[derive(Debug)]
13pub struct TransientUnitHandle {
14  pub(crate) name: String,
15  pub(crate) manager: SystemdTransientManager,
16  pub(crate) unit: UnitProxy<'static>,
17  pub(crate) invocation_id: Vec<u8>,
18  pub(crate) watch: ExitWatch,
19  pub(crate) released: bool,
20}
21
22impl TransientUnitHandle {
23  /// Stops the unit and returns the state it ended in. If the unit had
24  /// already exited, nothing is stopped and that earlier state is returned.
25  /// If someone else started the unit again since, it is left running and
26  /// `UnitRestarted` is returned, unless this run's exit was already seen.
27  ///
28  /// The handle's reference keeps the name from being reused, but the unit
29  /// itself can be started again (`systemctl start`). The invocation ID check
30  /// keeps this call from stopping that later run. A restart between the
31  /// check and `StopUnit` is not caught.
32  pub async fn stop(&self) -> Result<UnitExit, SystemdTransientError> {
33    if let WatchState::Exited(exit) = self.watch.current() {
34      return Ok(exit);
35    }
36
37    if self.unit.invocation_id().await? != self.invocation_id {
38      return match self.watch.current() {
39        WatchState::Exited(exit) => Ok(exit),
40        _ => Err(SystemdTransientError::UnitRestarted {
41          name: self.name.clone(),
42        }),
43      };
44    }
45
46    self.manager.stop_job(&self.name).await?;
47
48    // A finished stop job means inactive or failed, but not which one.
49    self.watch.wait().await
50  }
51
52  pub fn name(&self) -> &str {
53    &self.name
54  }
55
56  /// Resolves once the unit has exited, even if that happened before this
57  /// call. Several tasks may wait at once, also while another one calls
58  /// `stop`.
59  pub async fn wait_for_exit(&self) -> Result<UnitExit, SystemdTransientError> {
60    self.watch.wait().await
61  }
62
63  /// Drops the reference on the unit, so systemd can collect it once it is
64  /// inactive. Does not stop the unit.
65  pub async fn release(mut self) -> Result<(), SystemdTransientError> {
66    self.released = true;
67    self.unit.unref().await?;
68    Ok(())
69  }
70}
71
72impl Drop for TransientUnitHandle {
73  /// Releases the reference in a background task. Without a Tokio runtime the
74  /// reference lasts until the bus connection closes.
75  fn drop(&mut self) {
76    if self.released {
77      return;
78    }
79    if let Ok(rt) = tokio::runtime::Handle::try_current() {
80      let unit = self.unit.clone();
81      rt.spawn(async move {
82        if let Err(err) = unit.unref().await {
83          tracing::warn!("releasing unit {} failed: {}", unit.inner().path(), err);
84        }
85      });
86    }
87  }
88}