Skip to content

Commit 604a674

Browse files
committed
Auto merge of #1536 - divergentdave:nanosleep, r=RalfJung
Nanosleep This PR adds a shim for `libc::nanosleep`, (available under -Zmiri-disable-isolation only) which backs `thread::sleep` on Linux and macOS. I started off by extracting the `timespec` parsing from the `pthread_cond_timedwait` shim into a helper method, and adding checks for invalid values. The second commit adds the new shim and a small test. The shim blocks the current thread, and registers a timeout callback to unblock the thread again, using the same method as `pthread_cond_timedwait` does.
2 parents 3bc1136 + b06f0d1 commit 604a674

File tree

7 files changed

+133
-22
lines changed

7 files changed

+133
-22
lines changed

src/helpers.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::convert::{TryFrom, TryInto};
22
use std::mem;
33
use std::num::NonZeroUsize;
4+
use std::time::Duration;
45

56
use log::trace;
67

@@ -512,6 +513,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
512513
let value_place = op_place.offset(offset, MemPlaceMeta::None, layout, this)?;
513514
this.write_scalar(value, value_place.into())
514515
}
516+
517+
/// Parse a `timespec` struct and return it as a `std::time::Duration`. It returns `None`
518+
/// if the value in the `timespec` struct is invalid. Some libc functions will return
519+
/// `EINVAL` in this case.
520+
fn read_timespec(
521+
&mut self,
522+
timespec_ptr_op: OpTy<'tcx, Tag>,
523+
) -> InterpResult<'tcx, Option<Duration>> {
524+
let this = self.eval_context_mut();
525+
let tp = this.deref_operand(timespec_ptr_op)?;
526+
let seconds_place = this.mplace_field(tp, 0)?;
527+
let seconds_scalar = this.read_scalar(seconds_place.into())?;
528+
let seconds = seconds_scalar.to_machine_isize(this)?;
529+
let nanoseconds_place = this.mplace_field(tp, 1)?;
530+
let nanoseconds_scalar = this.read_scalar(nanoseconds_place.into())?;
531+
let nanoseconds = nanoseconds_scalar.to_machine_isize(this)?;
532+
533+
Ok(try {
534+
// tv_sec must be non-negative.
535+
let seconds: u64 = seconds.try_into().ok()?;
536+
// tv_nsec must be non-negative.
537+
let nanoseconds: u32 = nanoseconds.try_into().ok()?;
538+
if nanoseconds >= 1_000_000_000 {
539+
// tv_nsec must not be greater than 999,999,999.
540+
None?
541+
}
542+
Duration::new(seconds, nanoseconds)
543+
})
544+
}
515545
}
516546

517547
/// Check that the number of args is what we expect.

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#![feature(map_first_last)]
44
#![feature(never_type)]
55
#![feature(or_patterns)]
6+
#![feature(try_blocks)]
67

78
#![warn(rust_2018_idioms)]
89
#![allow(clippy::cast_lossless)]

src/shims/posix/foreign_items.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
382382
let result = this.sched_yield()?;
383383
this.write_scalar(Scalar::from_i32(result), dest)?;
384384
}
385+
"nanosleep" => {
386+
let &[req, rem] = check_arg_count(args)?;
387+
let result = this.nanosleep(req, rem)?;
388+
this.write_scalar(Scalar::from_i32(result), dest)?;
389+
}
385390

386391
// Miscellaneous
387392
"isatty" => {

src/shims/posix/sync.rs

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
use std::convert::TryInto;
2-
use std::time::{Duration, SystemTime};
1+
use std::time::SystemTime;
32

43
use crate::*;
54
use stacked_borrows::Tag;
65
use thread::Time;
76

8-
97
// pthread_mutexattr_t is either 4 or 8 bytes, depending on the platform.
108

119
// Our chosen memory layout for emulation (does not have to match the platform layout!):
@@ -698,25 +696,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
698696
let mutex_id = mutex_get_or_create_id(this, mutex_op)?;
699697
let active_thread = this.get_active_thread();
700698

701-
release_cond_mutex_and_block(this, active_thread, mutex_id)?;
702-
this.condvar_wait(id, active_thread, mutex_id);
703-
704-
// We return success for now and override it in the timeout callback.
705-
this.write_scalar(Scalar::from_i32(0), dest)?;
706-
707699
// Extract the timeout.
708700
let clock_id = cond_get_clock_id(this, cond_op)?.to_i32()?;
709-
let duration = {
710-
let tp = this.deref_operand(abstime_op)?;
711-
let seconds_place = this.mplace_field(tp, 0)?;
712-
let seconds = this.read_scalar(seconds_place.into())?;
713-
let nanoseconds_place = this.mplace_field(tp, 1)?;
714-
let nanoseconds = this.read_scalar(nanoseconds_place.into())?;
715-
let (seconds, nanoseconds) = (
716-
seconds.to_machine_usize(this)?,
717-
nanoseconds.to_machine_usize(this)?.try_into().unwrap(),
718-
);
719-
Duration::new(seconds, nanoseconds)
701+
let duration = match this.read_timespec(abstime_op)? {
702+
Some(duration) => duration,
703+
None => {
704+
let einval = this.eval_libc("EINVAL")?;
705+
this.write_scalar(einval, dest)?;
706+
return Ok(());
707+
}
720708
};
721709

722710
let timeout_time = if clock_id == this.eval_libc_i32("CLOCK_REALTIME")? {
@@ -727,6 +715,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
727715
throw_unsup_format!("unsupported clock id: {}", clock_id);
728716
};
729717

718+
release_cond_mutex_and_block(this, active_thread, mutex_id)?;
719+
this.condvar_wait(id, active_thread, mutex_id);
720+
721+
// We return success for now and override it in the timeout callback.
722+
this.write_scalar(Scalar::from_i32(0), dest)?;
723+
730724
// Register the timeout callback.
731725
this.register_timeout_callback(
732726
active_thread,
@@ -740,8 +734,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
740734
ecx.condvar_remove_waiter(id, active_thread);
741735

742736
// Set the return value: we timed out.
743-
let timeout = ecx.eval_libc_i32("ETIMEDOUT")?;
744-
ecx.write_scalar(Scalar::from_i32(timeout), dest)?;
737+
let etimedout = ecx.eval_libc("ETIMEDOUT")?;
738+
ecx.write_scalar(etimedout, dest)?;
745739

746740
Ok(())
747741
}),

src/shims/time.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::convert::TryFrom;
44
use crate::stacked_borrows::Tag;
55
use crate::*;
66
use helpers::{immty_from_int_checked, immty_from_uint_checked};
7+
use thread::Time;
78

89
/// Returns the time elapsed between the provided time and the unix epoch as a `Duration`.
910
pub fn system_time_to_duration<'tcx>(time: &SystemTime) -> InterpResult<'tcx, Duration> {
@@ -177,4 +178,40 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
177178
this.write_packed_immediates(info, &imms)?;
178179
Ok(0) // KERN_SUCCESS
179180
}
181+
182+
fn nanosleep(
183+
&mut self,
184+
req_op: OpTy<'tcx, Tag>,
185+
_rem: OpTy<'tcx, Tag>,
186+
) -> InterpResult<'tcx, i32> {
187+
// Signal handlers are not supported, so rem will never be written to.
188+
189+
let this = self.eval_context_mut();
190+
191+
this.check_no_isolation("nanosleep")?;
192+
193+
let duration = match this.read_timespec(req_op)? {
194+
Some(duration) => duration,
195+
None => {
196+
let einval = this.eval_libc("EINVAL")?;
197+
this.set_last_error(einval)?;
198+
return Ok(-1);
199+
}
200+
};
201+
let timeout_time = Time::Monotonic(Instant::now().checked_add(duration).unwrap());
202+
203+
let active_thread = this.get_active_thread();
204+
this.block_thread(active_thread);
205+
206+
this.register_timeout_callback(
207+
active_thread,
208+
timeout_time,
209+
Box::new(move |ecx| {
210+
ecx.unblock_thread(active_thread);
211+
Ok(())
212+
}),
213+
);
214+
215+
Ok(0)
216+
}
180217
}

tests/run-pass/concurrency/libc_pthread_cond.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,38 @@ fn test_timed_wait_timeout(clock_id: i32) {
3737
);
3838
let elapsed_time = current_time.elapsed().as_millis();
3939
assert!(900 <= elapsed_time && elapsed_time <= 1300);
40+
41+
// Test that invalid nanosecond values (above 10^9 or negative) are rejected with the
42+
// correct error code.
43+
let invalid_timeout_1 = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: 1_000_000_000 };
44+
assert_eq!(
45+
libc::pthread_cond_timedwait(
46+
&mut cond as *mut _,
47+
&mut mutex as *mut _,
48+
&invalid_timeout_1
49+
),
50+
libc::EINVAL
51+
);
52+
let invalid_timeout_2 = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: -1 };
53+
assert_eq!(
54+
libc::pthread_cond_timedwait(
55+
&mut cond as *mut _,
56+
&mut mutex as *mut _,
57+
&invalid_timeout_2
58+
),
59+
libc::EINVAL
60+
);
61+
// Test that invalid second values (negative) are rejected with the correct error code.
62+
let invalid_timeout_3 = libc::timespec { tv_sec: -1, tv_nsec: 0 };
63+
assert_eq!(
64+
libc::pthread_cond_timedwait(
65+
&mut cond as *mut _,
66+
&mut mutex as *mut _,
67+
&invalid_timeout_3
68+
),
69+
libc::EINVAL
70+
);
71+
4072
assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
4173
assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0);
4274
assert_eq!(libc::pthread_cond_destroy(&mut cond as *mut _), 0);

tests/run-pass/time.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ fn duration_sanity(diff: Duration) {
88
assert!(diff.as_millis() < 500);
99
}
1010

11+
// Sleeping on Windows is not supported yet.
12+
#[cfg(unix)]
13+
fn test_sleep() {
14+
let before = Instant::now();
15+
std::thread::sleep(Duration::from_millis(100));
16+
let after = Instant::now();
17+
assert!((after - before).as_millis() >= 100);
18+
}
19+
1120
fn main() {
1221
// Check `SystemTime`.
1322
let now1 = SystemTime::now();
@@ -36,4 +45,7 @@ fn main() {
3645
assert_eq!(now1 + diff, now2);
3746
assert_eq!(now2 - diff, now1);
3847
duration_sanity(diff);
48+
49+
#[cfg(unix)]
50+
test_sleep();
3951
}

0 commit comments

Comments
 (0)