Skip to content

Commit d16f916

Browse files
authored
Only poll known tracees with wait(2) (#102)
1 parent 57f5230 commit d16f916

File tree

4 files changed

+110
-13
lines changed

4 files changed

+110
-13
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Add accessors for `wait()` poll delay
13+
1214
### Changed
1315

1416
### Fixed
1517

18+
- Only poll known tracees for `wait(2)` status changes
19+
1620
## [v0.11.0] - 2023-09-04
1721

1822
### Added

src/ptracer.rs

+64-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::io;
66
use std::marker::PhantomData;
77
use std::os::unix::process::CommandExt;
88
use std::process::{Child, Command};
9+
use std::time::Duration;
910

1011
use nix::{
1112
errno::Errno,
@@ -46,8 +47,6 @@ pub type Registers = libc::user_regs_struct;
4647
/// Extra signal info, such as its cause.
4748
pub type Siginfo = libc::siginfo_t;
4849

49-
const WALL: Option<WaitPidFlag> = Some(WaitPidFlag::__WALL);
50-
5150
/// Linux constant defined in `include/uapi/linux/elf.h`.
5251
#[cfg(target_arch = "aarch64")]
5352
const NT_PRSTATUS: i32 = 0x1;
@@ -343,16 +342,22 @@ pub struct Ptracer {
343342
/// Ptrace options that will be applied to tracees, by default.
344343
options: Options,
345344

345+
/// Time to sleep for before polling tracees for new events.
346+
poll_delay: Duration,
347+
346348
/// Known tracees, and their state.
347349
tracees: BTreeMap<i32, State>,
348350
}
349351

352+
const DEFAULT_POLL_DELAY: Duration = Duration::from_millis(100);
353+
350354
impl Ptracer {
351355
pub fn new() -> Self {
352356
let options = Options::all();
357+
let poll_delay = DEFAULT_POLL_DELAY;
353358
let tracees = BTreeMap::new();
354359

355-
Self { options, tracees }
360+
Self { options, poll_delay, tracees }
356361
}
357362

358363
/// Returns a reference to the default ptrace options applied to newly-spawned tracees.
@@ -365,6 +370,16 @@ impl Ptracer {
365370
&mut self.options
366371
}
367372

373+
/// Returns a reference to the poll delay.
374+
pub fn poll_delay(&self) -> &Duration {
375+
&self.poll_delay
376+
}
377+
378+
/// Returns a mutable reference to the poll delay.
379+
pub fn poll_delay_mut(&mut self) -> &mut Duration {
380+
&mut self.poll_delay
381+
}
382+
368383
/// Resume the stopped tracee, delivering any pending signal.
369384
pub fn restart(&mut self, tracee: Tracee, restart: Restart) -> Result<()> {
370385
let Tracee { pid, pending, .. } = tracee;
@@ -417,21 +432,58 @@ impl Ptracer {
417432
r
418433
}
419434

435+
// Poll tracees for a `wait(2)` status change.
436+
fn poll_tracees(&self) -> Result<Option<WaitStatus>> {
437+
let flag = WaitPidFlag::__WALL | WaitPidFlag::WNOHANG;
438+
439+
for tracee in self.tracees.keys().copied() {
440+
let pid = Pid::from_raw(tracee);
441+
442+
match wait::waitpid(pid, Some(flag)) {
443+
Ok(WaitStatus::StillAlive) => {
444+
// Alive, no state change. Check remaining tracees.
445+
continue;
446+
},
447+
Ok(status) => {
448+
// One of our tracees changed state.
449+
return Ok(Some(status));
450+
},
451+
Err(errno) if errno == Errno::ECHILD => {
452+
// No more children to wait on: we're done.
453+
return Ok(None)
454+
},
455+
Err(err) => {
456+
// Something else went wrong.
457+
return Err(err.into())
458+
},
459+
};
460+
}
461+
462+
// No tracee changed state.
463+
Ok(None)
464+
}
465+
420466
/// Wait for some running tracee process to stop.
421467
///
422468
/// If there are no tracees to wait on, returns `None`.
423469
pub fn wait(&mut self) -> Result<Option<Tracee>> {
424470
use Signal::*;
425471

426-
let status = match wait::waitpid(None, WALL) {
427-
Ok(status) =>
428-
status,
429-
Err(errno) if errno == Errno::ECHILD =>
430-
// No more children to wait on: we're done.
431-
return Ok(None),
432-
Err(err) =>
433-
return Err(err.into()),
434-
};
472+
let status;
473+
474+
loop {
475+
if self.tracees.is_empty() {
476+
return Ok(None);
477+
}
478+
479+
if let Some(new_status) = self.poll_tracees()? {
480+
// A tracee changed state, examine its `wait(2)` status.
481+
status = new_status;
482+
break;
483+
} else {
484+
std::thread::sleep(self.poll_delay);
485+
}
486+
}
435487

436488
let tracee = match status {
437489
WaitStatus::Exited(pid, _exit_code) => {

tests/tracee_died.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ macro_rules! assert_matches {
1717

1818
#[cfg(target_arch = "x86_64")]
1919
#[test]
20-
#[timeout(100)]
20+
#[timeout(1000)]
2121
fn test_tracee_died() -> Result<()> {
2222
let cmd = Command::new("true");
2323

tests/wait_untraced_child.rs

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use std::process::Command;
2+
3+
use anyhow::Result;
4+
use ntest::timeout;
5+
use pete::{Ptracer, Restart};
6+
7+
#[test]
8+
#[timeout(5000)]
9+
fn test_wait_untraced_child() -> Result<()> {
10+
// Untraced, exits before tracee.
11+
let mut fast = Command::new("sleep").arg("0.1").spawn()?;
12+
13+
// Untraced, exits after tracee.
14+
let mut slow = Command::new("sleep").arg("2").spawn()?;
15+
16+
// Traced.
17+
let mut traceme = Command::new("sleep");
18+
traceme.arg("1");
19+
20+
let mut tracer = Ptracer::new();
21+
let mut tracee = tracer.spawn(traceme)?;
22+
23+
while let Some(tracee) = tracer.wait()? {
24+
eprintln!("{}: {:?}", tracee.pid, tracee.stop);
25+
26+
tracer.restart(tracee, Restart::Continue)?;
27+
}
28+
29+
eprintln!("waiting on tracee: {}", tracee.id());
30+
println!("tracee status: {}", tracee.wait()?);
31+
32+
eprintln!("waiting on fast: {}", fast.id());
33+
println!("fast status: {}", fast.wait()?);
34+
35+
eprintln!("waiting on slow: {}", slow.id());
36+
println!("slow status: {}", slow.wait()?);
37+
38+
eprintln!("ok!");
39+
40+
Ok(())
41+
}

0 commit comments

Comments
 (0)