Skip to content

Commit

Permalink
wip: impl the std side
Browse files Browse the repository at this point in the history
  • Loading branch information
passcod committed Mar 11, 2024
1 parent 8891e7e commit 2d30571
Show file tree
Hide file tree
Showing 8 changed files with 541 additions and 3 deletions.
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

pub(crate) mod generic_wrap;

// #[cfg(feature = "std")]
// pub mod std;
#[cfg(feature = "std")]
pub mod std;

#[cfg(feature = "tokio1")]
pub mod tokio;
Expand All @@ -23,5 +23,5 @@ mod windows;
#[derive(Debug)]
pub(crate) enum ChildExitStatus {
Running,
Exited(std::process::ExitStatus),
Exited(::std::process::ExitStatus),
}
29 changes: 29 additions & 0 deletions src/std.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#[doc(inline)]
pub use core::{StdChildWrapper, StdCommandWrap, StdCommandWrapper};
#[cfg(all(windows, feature = "creation-flags"))]
#[doc(inline)]
pub use creation_flags::CreationFlags;
#[cfg(all(windows, feature = "job-object"))]
#[doc(inline)]
pub use job_object::JobObject;
#[cfg(feature = "kill-on-drop")]
#[doc(inline)]
pub use kill_on_drop::KillOnDrop;
#[cfg(all(unix, feature = "process-group"))]
#[doc(inline)]
pub use process_group::{ProcessGroup, ProcessGroupChild};
#[cfg(all(unix, feature = "process-session"))]
#[doc(inline)]
pub use process_session::ProcessSession;

mod core;
#[cfg(all(windows, feature = "creation-flags"))]
mod creation_flags;
#[cfg(all(windows, feature = "job-object"))]
mod job_object;
#[cfg(feature = "kill-on-drop")]
mod kill_on_drop;
#[cfg(all(unix, feature = "process-group"))]
mod process_group;
#[cfg(all(unix, feature = "process-session"))]
mod process_session;
104 changes: 104 additions & 0 deletions src/std/core.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use std::{
io::Result,
process::{Child, ChildStderr, ChildStdin, ChildStdout, Command, ExitStatus, Output},
};

#[cfg(unix)]
use nix::{
sys::signal::{kill, Signal},
unistd::Pid,
};

crate::generic_wrap::Wrap!(StdCommandWrap, Command, StdCommandWrapper, StdChildWrapper);

pub trait StdCommandWrapper: std::fmt::Debug {
// process-wrap guarantees that `other` will be of the same type as `self`
// note that other crates that may use this trait should guarantee this, but
// that cannot be enforced by the type system, so you should still panic if
// downcasting fails, instead of potentially causing UB
fn extend(&mut self, _other: Box<dyn StdCommandWrapper>) {}

fn pre_spawn(&mut self, _command: &mut Command, _core: &StdCommandWrap) -> Result<()> {
Ok(())
}

fn post_spawn(&mut self, _child: &mut Child, _core: &StdCommandWrap) -> Result<()> {
Ok(())
}

fn wrap_child(
&mut self,
child: Box<dyn StdChildWrapper>,
_core: &StdCommandWrap,
) -> Result<Box<dyn StdChildWrapper>> {
Ok(child)
}
}

pub trait StdChildWrapper: std::fmt::Debug + Send + Sync {
fn inner(&self) -> &Child;
fn inner_mut(&mut self) -> &mut Child;
fn into_inner(self: Box<Self>) -> Child;

fn stdin(&mut self) -> &mut Option<ChildStdin> {
&mut self.inner_mut().stdin
}

fn stdout(&mut self) -> &mut Option<ChildStdout> {
&mut self.inner_mut().stdout
}

fn stderr(&mut self) -> &mut Option<ChildStderr> {
&mut self.inner_mut().stderr
}

fn id(&self) -> u32 {
self.inner().id()
}

fn kill(&mut self) -> Result<()> {
self.start_kill()?;
self.wait()?;
Ok(())
}

fn start_kill(&mut self) -> Result<()> {
self.inner_mut().start_kill()
}

fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
self.inner_mut().try_wait()
}

fn wait(&mut self) -> Result<ExitStatus> {
self.inner_mut().wait()
}

fn wait_with_output(self: Box<Self>) -> Result<Output>
where
Self: 'static,
{
todo!()
}

#[cfg(unix)]
fn signal(&self, sig: Signal) -> Result<()> {
kill(
Pid::from_raw(i32::try_from(self.id()).map_err(std::io::Error::other)?),
sig,
)
.map_err(std::io::Error::from)
}
}

impl StdChildWrapper for Child {
fn inner(&self) -> &Child {
self
}
fn inner_mut(&mut self) -> &mut Child {
self
}
fn into_inner(self: Box<Self>) -> Child {
*self
}
}
15 changes: 15 additions & 0 deletions src/std/creation_flags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use std::{io::Result, os::windows::process::CommandExt, process::Command};

use windows::Win32::System::Threading::PROCESS_CREATION_FLAGS;

use super::{StdCommandWrap, StdCommandWrapper};

#[derive(Debug, Clone)]
pub struct CreationFlags(pub PROCESS_CREATION_FLAGS);

impl StdCommandWrapper for CreationFlags {
fn pre_spawn(&mut self, command: &mut Command, _core: &StdCommandWrap) -> Result<()> {
command.creation_flags((self.0).0);
Ok(())
}
}
142 changes: 142 additions & 0 deletions src/std/job_object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use std::{
io::Result,
os::windows::{io::AsRawHandle, process::CommandExt},
process::{Child, Command, ExitStatus},
time::Duration,
};

use tracing::{debug, instrument};
use windows::Win32::{
Foundation::{CloseHandle, HANDLE},
System::Threading::CREATE_SUSPENDED,
};

use crate::{
windows::{make_job_object, resume_threads, terminate_job, wait_on_job, JobPort},
ChildExitStatus,
};

#[cfg(feature = "creation-flags")]
use super::CreationFlags;
#[cfg(feature = "kill-on-drop")]
use super::KillOnDrop;
use super::{StdChildWrapper, StdCommandWrap, StdCommandWrapper};

#[derive(Debug)]
pub struct JobObject;

impl StdCommandWrapper for JobObject {
#[instrument(level = "debug", skip(self))]
fn pre_spawn(&mut self, command: &mut Command, core: &StdCommandWrap) -> Result<()> {
let mut flags = CREATE_SUSPENDED;
#[cfg(feature = "creation-flags")]
if let Some(CreationFlags(user_flags)) = core.get_wrap::<CreationFlags>() {
flags |= *user_flags;
}

command.creation_flags(flags.0);
Ok(())
}

#[instrument(level = "debug", skip(self))]
fn wrap_child(
&mut self,
inner: Box<dyn StdChildWrapper>,
core: &StdCommandWrap,
) -> Result<Box<dyn StdChildWrapper>> {
#[cfg(feature = "kill-on-drop")]
let kill_on_drop = core.has_wrap::<KillOnDrop>();
#[cfg(not(feature = "kill-on-drop"))]
let kill_on_drop = false;

#[cfg(feature = "creation-flags")]
let create_suspended = core
.get_wrap::<CreationFlags>()
.map_or(false, |flags| flags.0.contains(CREATE_SUSPENDED));
#[cfg(not(feature = "creation-flags"))]
let create_suspended = false;

debug!(
?kill_on_drop,
?create_suspended,
"options from other wrappers"
);

let handle = HANDLE(inner.inner().as_raw_handle() as _);

let job_port = make_job_object(handle, kill_on_drop)?;

// only resume if the user didn't specify CREATE_SUSPENDED
if !create_suspended {
resume_threads(handle)?;
}

Ok(Box::new(JobObjectChild::new(inner, job_port)))
}
}

#[derive(Debug)]
pub struct JobObjectChild {
inner: Box<dyn StdChildWrapper>,
exit_status: ChildExitStatus,
job_port: JobPort,
}

impl JobObjectChild {
#[instrument(level = "debug", skip(job_port))]
pub(crate) fn new(inner: Box<dyn StdChildWrapper>, job_port: JobPort) -> Self {
Self {
inner,
exit_status: ChildExitStatus::Running,
job_port,
}
}
}

impl StdChildWrapper for JobObjectChild {
fn inner(&self) -> &Child {
self.inner.inner()
}
fn inner_mut(&mut self) -> &mut Child {
self.inner.inner_mut()
}
fn into_inner(self: Box<Self>) -> Child {
// manually drop the completion port
let its = std::mem::ManuallyDrop::new(self.job_port);
unsafe { CloseHandle(its.completion_port) }.ok();
// we leave the job handle unclosed, otherwise the Child is useless
// (as closing it will terminate the job)

self.inner.into_inner()
}

#[instrument(level = "debug", skip(self))]
fn start_kill(&mut self) -> Result<()> {
terminate_job(self.job_port.job, 1)
}

#[instrument(level = "debug", skip(self))]
fn wait(&mut self) -> Result<ExitStatus> {
if let ChildExitStatus::Exited(status) = &self.exit_status {
return Ok(*status);
}

// always wait for parent to exit first, as by the time it does,
// it's likely that all its children have already exited.
let status = self.inner.wait()?;
self.exit_status = ChildExitStatus::Exited(status);

// nevertheless, now wait and make sure we reap all children.
let JobPort {
completion_port, ..
} = self.job_port;
wait_on_job(completion_port, None)?;
Ok(status)
}

#[instrument(level = "debug", skip(self))]
fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
wait_on_job(self.job_port.completion_port, Some(Duration::ZERO))?;
self.inner.try_wait()
}
}
13 changes: 13 additions & 0 deletions src/std/kill_on_drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use std::{io::Result, process::Command};

use super::{StdCommandWrap, StdCommandWrapper};

#[derive(Debug)]
pub struct KillOnDrop;

impl StdCommandWrapper for KillOnDrop {
fn pre_spawn(&mut self, command: &mut Command, _core: &StdCommandWrap) -> Result<()> {
command.kill_on_drop(true);
Ok(())
}
}
Loading

0 comments on commit 2d30571

Please sign in to comment.