-
Notifications
You must be signed in to change notification settings - Fork 30
pidwait: optimize & cross-platform #400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7ae897c
7e2813b
dc7b000
903fef8
c41708d
5029113
df176fe
1836a9d
ce291dd
76310ac
1e9a3dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,15 +4,16 @@ | |
// file that was distributed with this source code. | ||
|
||
use clap::{arg, crate_version, Command}; | ||
use std::time::Duration; | ||
use uu_pgrep::process::ProcessInformation; | ||
use uu_pgrep::process_matcher; | ||
use uucore::{error::UResult, format_usage, help_about, help_usage}; | ||
use wait::wait; | ||
|
||
mod wait; | ||
|
||
const ABOUT: &str = help_about!("pidwait.md"); | ||
const USAGE: &str = help_usage!("pidwait.md"); | ||
|
||
mod platform; | ||
|
||
#[uucore::main] | ||
pub fn uumain(args: impl uucore::Args) -> UResult<()> { | ||
let matches = uu_app().try_get_matches_from(args)?; | ||
|
@@ -42,11 +43,23 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { | |
} | ||
} | ||
|
||
wait(&proc_infos); | ||
// It should be fine to reserve a `timeout` parameter for future use. | ||
wait(&proc_infos, None)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) fn wait( | ||
procs: &[ProcessInformation], | ||
timeout: Option<Duration>, | ||
) -> Result<Option<()>, std::io::Error> { | ||
if !procs.is_empty() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A detail: I would drop the |
||
platform::wait(procs, timeout) | ||
} else { | ||
Ok(None) | ||
} | ||
} | ||
|
||
pub fn uu_app() -> Command { | ||
Command::new(env!("CARGO_PKG_NAME")) | ||
.version(crate_version!()) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// This file is part of the uutils procps package. | ||
// | ||
// For the full copyright and license information, please view the LICENSE | ||
// file that was distributed with this source code. | ||
|
||
// Reference: pidwait-any crate. | ||
// Thanks to @oxalica's implementation. | ||
|
||
// FIXME: Test this implementation | ||
|
||
use rustix::event::kqueue::{kevent, kqueue, Event, EventFilter, EventFlags, ProcessEvents}; | ||
use rustix::process::Pid; | ||
use std::io::{Error, ErrorKind, Result}; | ||
use std::mem::MaybeUninit; | ||
use std::time::Duration; | ||
use uu_pgrep::process::ProcessInformation; | ||
|
||
pub fn wait(procs: &[ProcessInformation], timeout: Option<Duration>) -> Result<Option<()>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
let mut events = Vec::with_capacity(procs.len()); | ||
let kqueue = kqueue()?; | ||
for proc in procs { | ||
let pid = Pid::from_raw(proc.pid as i32).ok_or_else(|| { | ||
Error::new( | ||
ErrorKind::InvalidInput, | ||
format!("Invalid PID: {}", proc.pid), | ||
) | ||
})?; | ||
let event = Event::new( | ||
EventFilter::Proc { | ||
pid, | ||
flags: ProcessEvents::EXIT, | ||
}, | ||
EventFlags::ADD, | ||
std::ptr::null_mut(), | ||
); | ||
events.push(event); | ||
} | ||
let ret = unsafe { kevent::<_, &mut [Event; 0]>(&kqueue, &events, &mut [], None)? }; | ||
debug_assert_eq!(ret, 0); | ||
let mut buf = [MaybeUninit::uninit()]; | ||
let (events, _rest_buf) = unsafe { kevent(&kqueue, &[], &mut buf, timeout)? }; | ||
if events.is_empty() { | ||
return Ok(None); | ||
}; | ||
debug_assert!(matches!( | ||
events[0].filter(), | ||
EventFilter::Proc { flags, .. } | ||
if flags.contains(ProcessEvents::EXIT) | ||
)); | ||
Ok(Some(())) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// This file is part of the uutils procps package. | ||
// | ||
// For the full copyright and license information, please view the LICENSE | ||
// file that was distributed with this source code. | ||
|
||
// Reference: pidwait-any crate. | ||
// Thanks to @oxalica's implementation. | ||
|
||
use std::io::{Error, ErrorKind}; | ||
use std::os::fd::OwnedFd; | ||
|
||
use rustix::event::{poll, PollFd, PollFlags}; | ||
use rustix::io::Errno; | ||
use rustix::process::{pidfd_open, Pid, PidfdFlags}; | ||
use std::io::Result; | ||
use std::time::Duration; | ||
use uu_pgrep::process::ProcessInformation; | ||
|
||
pub fn wait(procs: &[ProcessInformation], timeout: Option<Duration>) -> Result<Option<()>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
let mut pidfds: Vec<OwnedFd> = Vec::with_capacity(procs.len()); | ||
for proc in procs { | ||
let pid = Pid::from_raw(proc.pid as i32).ok_or_else(|| { | ||
Error::new( | ||
ErrorKind::InvalidInput, | ||
format!("Invalid PID: {}", proc.pid), | ||
) | ||
})?; | ||
let pidfd = pidfd_open(pid, PidfdFlags::empty())?; | ||
pidfds.push(pidfd); | ||
} | ||
let timespec = timeout | ||
.map(|t| t.try_into().map_err(|_| Errno::INVAL)) | ||
.transpose()?; | ||
let mut fds: Vec<PollFd> = Vec::with_capacity(pidfds.len()); | ||
for pidfd in &pidfds { | ||
fds.push(PollFd::new(pidfd, PollFlags::IN)); | ||
} | ||
let ret = poll(&mut fds, timespec.as_ref())?; | ||
if ret == 0 { | ||
return Ok(None); | ||
} | ||
debug_assert!(fds[0].revents().contains(PollFlags::IN)); | ||
Ok(Some(())) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#[cfg(target_os = "linux")] | ||
pub use self::linux::wait; | ||
|
||
#[cfg(windows)] | ||
pub use self::windows::wait; | ||
|
||
#[cfg(any( | ||
target_os = "freebsd", | ||
target_os = "macos", | ||
target_os = "netbsd", | ||
target_os = "openbsd", | ||
))] | ||
pub use self::bsd::wait; | ||
|
||
#[cfg(target_os = "linux")] | ||
mod linux; | ||
|
||
#[cfg(windows)] | ||
mod windows; | ||
|
||
#[cfg(any( | ||
target_os = "freebsd", | ||
target_os = "macos", | ||
target_os = "netbsd", | ||
target_os = "openbsd", | ||
))] | ||
mod bsd; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I noticed that there are some https://github.com/uutils/coreutils/blob/main/CONTRIBUTING.md#unsafe |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// This file is part of the uutils procps package. | ||
// | ||
// For the full copyright and license information, please view the LICENSE | ||
// file that was distributed with this source code. | ||
|
||
// Reference: pidwait-any crate. | ||
// Thanks to @oxalica's implementation. | ||
|
||
use std::time::Duration; | ||
use uu_pgrep::process::ProcessInformation; | ||
|
||
use std::ffi::c_void; | ||
use std::io::{Error, Result}; | ||
use std::ptr::NonNull; | ||
|
||
use windows_sys::Win32::Foundation::{CloseHandle, WAIT_OBJECT_0, WAIT_TIMEOUT}; | ||
use windows_sys::Win32::System::Threading::{ | ||
OpenProcess, WaitForMultipleObjects, INFINITE, PROCESS_SYNCHRONIZE, | ||
}; | ||
|
||
struct HandleWrapper(NonNull<c_void>); | ||
unsafe impl Send for HandleWrapper {} | ||
impl Drop for HandleWrapper { | ||
fn drop(&mut self) { | ||
unsafe { | ||
CloseHandle(self.0.as_ptr()); | ||
}; | ||
} | ||
} | ||
|
||
pub fn wait(procs: &[ProcessInformation], timeout: Option<Duration>) -> Result<Option<()>> { | ||
let hprocess = unsafe { | ||
let mut result = Vec::with_capacity(procs.len()); | ||
for proc in procs { | ||
let handle = OpenProcess(PROCESS_SYNCHRONIZE, 0, proc.pid as u32); | ||
result.push(HandleWrapper( | ||
NonNull::new(handle).ok_or_else(Error::last_os_error)?, | ||
)); | ||
} | ||
result | ||
}; | ||
const _: [(); 1] = [(); (INFINITE == u32::MAX) as usize]; | ||
let timeout = match timeout { | ||
Some(timeout) => timeout | ||
.as_millis() | ||
.try_into() | ||
.unwrap_or(INFINITE - 1) | ||
.min(INFINITE - 1), | ||
None => INFINITE, | ||
}; | ||
let ret = unsafe { | ||
WaitForMultipleObjects( | ||
hprocess.len() as u32, | ||
hprocess | ||
.into_iter() | ||
.map(|proc| proc.0.as_ptr()) | ||
.collect::<Vec<_>>() | ||
.as_ptr(), | ||
1, | ||
timeout, | ||
) | ||
}; | ||
match ret { | ||
WAIT_OBJECT_0 => Ok(Some(())), | ||
WAIT_TIMEOUT => Ok(None), | ||
_ => Err(Error::last_os_error()), | ||
} | ||
} |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for using
Option<()>
? Why not use()
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that it would be better if we can pass exceptions rather than just
unwrap
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I don't understand what you mean :| I think we are talking about different things. Maybe my question was too imprecise?
My question is: why do you use
Result<Option<()>, std::io::Error>
instead ofResult<(), std::io::Error>
as return type?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using
Option<()>
is unnecessary because the meaning of thisOption
is to indicatesomething there
ornothing there
, and it's clear that this function doesn't need that meaning; it only needs to know whether an error occurred