use std::ffi::OsString;
use std::io;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::time::Duration;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum SpawnMode {
#[default]
Inherited,
Independent,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum SpawnLifetime {
#[default]
KillOnDrop,
Detached,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum IndependentBackend {
NativeScheduler { launcher: PathBuf },
ExternalBroker { endpoint: String },
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SpawnOptions {
pub mode: SpawnMode,
pub lifetime: SpawnLifetime,
pub backend: Option<IndependentBackend>,
pub timeout: Duration,
}
impl Default for SpawnOptions {
fn default() -> Self {
Self {
mode: SpawnMode::Inherited,
lifetime: SpawnLifetime::KillOnDrop,
backend: None,
timeout: Duration::from_secs(30),
}
}
}
#[derive(Clone)]
pub struct LaunchSpec {
pub program: OsString,
pub args: Vec<OsString>,
pub cwd: OsString,
pub environment: Vec<(OsString, OsString)>,
pub stdout: Option<OsString>,
pub stderr: Option<OsString>,
pub readiness: Readiness,
}
#[derive(Clone, Default)]
pub enum Readiness {
#[default]
ProcessStarted,
File {
path: OsString,
value: Vec<u8>,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SpawnExit {
pub code: Option<i32>,
}
pub struct SpawnHandle {
inner: running_process::SpawnHandle,
}
impl SpawnHandle {
pub fn id(&self) -> u32 {
self.inner.id()
}
pub fn actual_mode(&self) -> SpawnMode {
facade_mode(self.inner.actual_mode())
}
pub fn is_alive(&mut self) -> io::Result<bool> {
self.inner.is_alive()
}
pub fn stop(&mut self, timeout: Duration) -> io::Result<()> {
self.inner.stop(timeout)
}
pub fn wait(&mut self, timeout: Duration, cancelled: &AtomicBool) -> io::Result<SpawnExit> {
self.inner
.wait(timeout, cancelled)
.map(|exit| SpawnExit { code: exit.code })
}
}
pub fn spawn_with_options(
spec: &LaunchSpec,
options: &SpawnOptions,
cancelled: &AtomicBool,
) -> io::Result<SpawnHandle> {
running_process::spawn_with_options(&backend_spec(spec), &backend_options(options), cancelled)
.map(|inner| SpawnHandle { inner })
}
fn backend_mode(mode: SpawnMode) -> running_process::SpawnMode {
match mode {
SpawnMode::Inherited => running_process::SpawnMode::Inherited,
SpawnMode::Independent => running_process::SpawnMode::Independent,
}
}
fn facade_mode(mode: running_process::SpawnMode) -> SpawnMode {
match mode {
running_process::SpawnMode::Inherited => SpawnMode::Inherited,
running_process::SpawnMode::Independent => SpawnMode::Independent,
}
}
fn backend_options(options: &SpawnOptions) -> running_process::SpawnOptions {
running_process::SpawnOptions {
mode: backend_mode(options.mode),
lifetime: match options.lifetime {
SpawnLifetime::KillOnDrop => running_process::SpawnLifetime::KillOnDrop,
SpawnLifetime::Detached => running_process::SpawnLifetime::Detached,
},
backend: options.backend.as_ref().map(|backend| match backend {
IndependentBackend::NativeScheduler { launcher } => {
running_process::IndependentBackend::NativeScheduler {
launcher: launcher.clone(),
}
}
IndependentBackend::ExternalBroker { endpoint } => {
running_process::IndependentBackend::ExternalBroker {
endpoint: endpoint.clone(),
}
}
}),
timeout: options.timeout,
}
}
fn backend_spec(spec: &LaunchSpec) -> running_process::independent_spawn::LaunchSpec {
running_process::independent_spawn::LaunchSpec {
program: spec.program.clone(),
args: spec.args.clone(),
cwd: spec.cwd.clone(),
environment: spec.environment.clone(),
stdout: spec.stdout.clone(),
stderr: spec.stderr.clone(),
readiness: match &spec.readiness {
Readiness::ProcessStarted => running_process::independent_spawn::Readiness::ProcessStarted,
Readiness::File { path, value } => running_process::independent_spawn::Readiness::File {
path: path.clone(),
value: value.clone(),
},
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn modes_convert_losslessly_in_both_directions() {
for mode in [SpawnMode::Inherited, SpawnMode::Independent] {
assert_eq!(facade_mode(backend_mode(mode)), mode);
}
}
#[test]
fn default_options_convert_to_the_substrate_defaults() {
assert_eq!(
backend_options(&SpawnOptions::default()),
running_process::SpawnOptions::default()
);
}
#[test]
fn explicit_options_convert_field_for_field() {
let options = SpawnOptions {
mode: SpawnMode::Independent,
lifetime: SpawnLifetime::Detached,
backend: Some(IndependentBackend::ExternalBroker {
endpoint: "broker".to_owned(),
}),
timeout: Duration::from_secs(7),
};
assert_eq!(
backend_options(&options),
running_process::SpawnOptions {
mode: running_process::SpawnMode::Independent,
lifetime: running_process::SpawnLifetime::Detached,
backend: Some(running_process::IndependentBackend::ExternalBroker {
endpoint: "broker".to_owned(),
}),
timeout: Duration::from_secs(7),
}
);
}
}