Skip to content

Commit 097d304

Browse files
Merge pull request #711 from Krysztal112233/feat/refactor-pidwait
pidwait: replace busy-loop with `pidfd_open` and `poll` on Linux
2 parents c1be8d3 + ebd7d9b commit 097d304

3 files changed

Lines changed: 75 additions & 29 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/pidwait/Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,21 @@ version.workspace = true
1414
workspace = true
1515

1616
[dependencies]
17-
nix = { workspace = true }
1817
uucore = { workspace = true, features = ["entries"] }
1918
clap = { workspace = true }
2019
regex = { workspace = true }
20+
2121
uu_pgrep = { path = "../pgrep" }
2222

23+
[target.'cfg(unix)'.dependencies]
24+
rustix = { workspace = true, features = ["event", "std"] }
25+
26+
[target.'cfg(windows)'.dependencies]
27+
windows-sys = { workspace = true, features = [
28+
"Win32_Foundation",
29+
"Win32_System_Threading",
30+
] }
31+
2332
[lib]
2433
path = "src/pidwait.rs"
2534

src/uu/pidwait/src/wait.rs

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,78 @@
55

66
use uu_pgrep::process::ProcessInformation;
77

8-
// Dirty, but it works.
9-
// TODO: Use better implementation instead
108
#[cfg(target_os = "linux")]
119
pub(crate) fn wait(procs: &[ProcessInformation]) {
12-
use std::{thread::sleep, time::Duration};
10+
use std::os::fd::OwnedFd;
1311

14-
let mut list = procs.to_vec();
12+
use rustix::event::{poll, PollFd, PollFlags};
13+
use rustix::process::{pidfd_open, Pid, PidfdFlags};
1514

16-
loop {
17-
for proc in &list.clone() {
18-
// Check is running
19-
if !is_running(proc.pid) {
20-
list.retain(|it| it.pid != proc.pid);
15+
let mut pidfds: Vec<OwnedFd> = procs
16+
.iter()
17+
.filter_map(|proc| {
18+
let pid = Pid::from_raw(proc.pid as i32)?;
19+
pidfd_open(pid, PidfdFlags::empty()).ok()
20+
})
21+
.collect();
22+
23+
while !pidfds.is_empty() {
24+
let to_remove = {
25+
let mut fds: Vec<PollFd> = pidfds
26+
.iter()
27+
.map(|fd| PollFd::new(fd, PollFlags::IN))
28+
.collect();
29+
30+
if poll(&mut fds, None).is_err() {
31+
break;
2132
}
22-
}
2333

24-
if list.is_empty() {
25-
return;
26-
}
34+
fds.iter()
35+
.enumerate()
36+
.filter(|(_, pfd)| pfd.revents().contains(PollFlags::IN))
37+
.map(|(i, _)| i)
38+
.rev()
39+
.collect::<Vec<_>>()
40+
};
2741

28-
sleep(Duration::from_millis(50));
29-
}
30-
}
31-
#[cfg(target_os = "linux")]
32-
fn is_running(pid: usize) -> bool {
33-
use uu_pgrep::process::RunState;
34-
35-
match ProcessInformation::from_pid(pid) {
36-
Ok(mut proc) => proc
37-
.run_state()
38-
.map(|it| it != RunState::Stopped)
39-
.unwrap_or(false),
40-
Err(_) => false,
42+
for i in to_remove {
43+
pidfds.remove(i);
44+
}
4145
}
4246
}
4347

44-
// Just for passing compile on other system.
4548
#[cfg(not(target_os = "linux"))]
4649
pub(crate) fn wait(_procs: &[ProcessInformation]) {}
50+
51+
#[cfg(test)]
52+
mod tests {
53+
54+
#[cfg(target_os = "linux")]
55+
#[test]
56+
fn test_wait_single_process() {
57+
use super::*;
58+
use std::process::Command;
59+
use std::time::Instant;
60+
61+
// NOTE: Manually tested with sleep 0.5, sleep 1, and sleep 2. Using 1s here to keep total
62+
// test time reasonable; 2s would also pass.
63+
let mut child = Command::new("sleep").arg("1").spawn().unwrap();
64+
let pid = child.id() as usize;
65+
66+
let info = ProcessInformation::from_pid(pid).unwrap();
67+
let start = Instant::now();
68+
wait(&[info]);
69+
let elapsed = start.elapsed();
70+
71+
assert!(
72+
elapsed >= std::time::Duration::from_millis(900),
73+
"wait returned too early: {elapsed:?}"
74+
);
75+
assert!(
76+
elapsed < std::time::Duration::from_secs(3),
77+
"wait took too long: {elapsed:?}"
78+
);
79+
80+
let _ = child.wait();
81+
}
82+
}

0 commit comments

Comments
 (0)