systemd-transient 0.2.0

Start transient systemd services using dbus, just like systemd-run.
Documentation
use crate::{
  SystemdTransientManager, UnitExit,
  dbus::unit::UnitProxy,
  error::SystemdTransientError,
  watch::{ExitWatch, WatchState},
};

/// Holds a reference on the unit (`AddRef=true`), so systemd keeps it loaded
/// after it exits. Release it with [`release`](Self::release) or by dropping
/// the handle; until then, starting a unit of the same name fails with
/// `UnitStillLoaded`.
#[derive(Debug)]
pub struct TransientUnitHandle {
  pub(crate) name: String,
  pub(crate) manager: SystemdTransientManager,
  pub(crate) unit: UnitProxy<'static>,
  pub(crate) invocation_id: Vec<u8>,
  pub(crate) watch: ExitWatch,
  pub(crate) released: bool,
}

impl TransientUnitHandle {
  /// Stops the unit and returns the state it ended in. If the unit had
  /// already exited, nothing is stopped and that earlier state is returned.
  /// If someone else started the unit again since, it is left running and
  /// `UnitRestarted` is returned, unless this run's exit was already seen.
  ///
  /// The handle's reference keeps the name from being reused, but the unit
  /// itself can be started again (`systemctl start`). The invocation ID check
  /// keeps this call from stopping that later run. A restart between the
  /// check and `StopUnit` is not caught.
  pub async fn stop(&self) -> Result<UnitExit, SystemdTransientError> {
    if let WatchState::Exited(exit) = self.watch.current() {
      return Ok(exit);
    }

    if self.unit.invocation_id().await? != self.invocation_id {
      return match self.watch.current() {
        WatchState::Exited(exit) => Ok(exit),
        _ => Err(SystemdTransientError::UnitRestarted {
          name: self.name.clone(),
        }),
      };
    }

    self.manager.stop_job(&self.name).await?;

    // A finished stop job means inactive or failed, but not which one.
    self.watch.wait().await
  }

  pub fn name(&self) -> &str {
    &self.name
  }

  /// Resolves once the unit has exited, even if that happened before this
  /// call. Several tasks may wait at once, also while another one calls
  /// `stop`.
  pub async fn wait_for_exit(&self) -> Result<UnitExit, SystemdTransientError> {
    self.watch.wait().await
  }

  /// Drops the reference on the unit, so systemd can collect it once it is
  /// inactive. Does not stop the unit.
  pub async fn release(mut self) -> Result<(), SystemdTransientError> {
    self.released = true;
    self.unit.unref().await?;
    Ok(())
  }
}

impl Drop for TransientUnitHandle {
  /// Releases the reference in a background task. Without a Tokio runtime the
  /// reference lasts until the bus connection closes.
  fn drop(&mut self) {
    if self.released {
      return;
    }
    if let Ok(rt) = tokio::runtime::Handle::try_current() {
      let unit = self.unit.clone();
      rt.spawn(async move {
        if let Err(err) = unit.unref().await {
          tracing::warn!("releasing unit {} failed: {}", unit.inner().path(), err);
        }
      });
    }
  }
}