Skip to content

Commit b5ae8bd

Browse files
committed
Auto merge of rust-lang#3663 - RalfJung:timeouts, r=RalfJung
don't panic if time computaton overflows Let the thread blocking system handle timeout computation, and on overflows we just set the timeout to 1h.
2 parents 509eec1 + 87c4d29 commit b5ae8bd

File tree

8 files changed

+102
-67
lines changed

8 files changed

+102
-67
lines changed

src/tools/miri/src/clock.rs

+20-13
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,20 @@ enum InstantKind {
2020
}
2121

2222
impl Instant {
23-
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
23+
/// Will try to add `duration`, but if that overflows it may add less.
24+
pub fn add_lossy(&self, duration: Duration) -> Instant {
2425
match self.kind {
25-
InstantKind::Host(instant) =>
26-
instant.checked_add(duration).map(|i| Instant { kind: InstantKind::Host(i) }),
27-
InstantKind::Virtual { nanoseconds } =>
28-
nanoseconds
29-
.checked_add(duration.as_nanos())
30-
.map(|nanoseconds| Instant { kind: InstantKind::Virtual { nanoseconds } }),
26+
InstantKind::Host(instant) => {
27+
// If this overflows, try adding just 1h and assume that will not overflow.
28+
let i = instant
29+
.checked_add(duration)
30+
.unwrap_or_else(|| instant.checked_add(Duration::from_secs(3600)).unwrap());
31+
Instant { kind: InstantKind::Host(i) }
32+
}
33+
InstantKind::Virtual { nanoseconds } => {
34+
let n = nanoseconds.saturating_add(duration.as_nanos());
35+
Instant { kind: InstantKind::Virtual { nanoseconds: n } }
36+
}
3137
}
3238
}
3339

@@ -63,8 +69,9 @@ pub struct Clock {
6369
#[derive(Debug)]
6470
enum ClockKind {
6571
Host {
66-
/// The "time anchor" for this machine's monotone clock.
67-
time_anchor: StdInstant,
72+
/// The "epoch" for this machine's monotone clock:
73+
/// the moment we consider to be time = 0.
74+
epoch: StdInstant,
6875
},
6976
Virtual {
7077
/// The "current virtual time".
@@ -76,7 +83,7 @@ impl Clock {
7683
/// Create a new clock based on the availability of communication with the host.
7784
pub fn new(communicate: bool) -> Self {
7885
let kind = if communicate {
79-
ClockKind::Host { time_anchor: StdInstant::now() }
86+
ClockKind::Host { epoch: StdInstant::now() }
8087
} else {
8188
ClockKind::Virtual { nanoseconds: 0.into() }
8289
};
@@ -111,10 +118,10 @@ impl Clock {
111118
}
112119
}
113120

114-
/// Return the `anchor` instant, to convert between monotone instants and durations relative to the anchor.
115-
pub fn anchor(&self) -> Instant {
121+
/// Return the `epoch` instant (time = 0), to convert between monotone instants and absolute durations.
122+
pub fn epoch(&self) -> Instant {
116123
match &self.kind {
117-
ClockKind::Host { time_anchor } => Instant { kind: InstantKind::Host(*time_anchor) },
124+
ClockKind::Host { epoch } => Instant { kind: InstantKind::Host(*epoch) },
118125
ClockKind::Virtual { .. } => Instant { kind: InstantKind::Virtual { nanoseconds: 0 } },
119126
}
120127
}

src/tools/miri/src/concurrency/sync.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::{hash_map::Entry, VecDeque};
22
use std::ops::Not;
3+
use std::time::Duration;
34

45
use rustc_data_structures::fx::FxHashMap;
56
use rustc_index::{Idx, IndexVec};
@@ -623,7 +624,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
623624
&mut self,
624625
condvar: CondvarId,
625626
mutex: MutexId,
626-
timeout: Option<Timeout>,
627+
timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>,
627628
retval_succ: Scalar,
628629
retval_timeout: Scalar,
629630
dest: MPlaceTy<'tcx>,
@@ -704,7 +705,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
704705
&mut self,
705706
addr: u64,
706707
bitset: u32,
707-
timeout: Option<Timeout>,
708+
timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>,
708709
retval_succ: Scalar,
709710
retval_timeout: Scalar,
710711
dest: MPlaceTy<'tcx>,

src/tools/miri/src/concurrency/thread.rs

+50-5
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> {
407407

408408
/// The moment in time when a blocked thread should be woken up.
409409
#[derive(Debug)]
410-
pub enum Timeout {
410+
enum Timeout {
411411
Monotonic(Instant),
412412
RealTime(SystemTime),
413413
}
@@ -421,6 +421,34 @@ impl Timeout {
421421
time.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO),
422422
}
423423
}
424+
425+
/// Will try to add `duration`, but if that overflows it may add less.
426+
fn add_lossy(&self, duration: Duration) -> Self {
427+
match self {
428+
Timeout::Monotonic(i) => Timeout::Monotonic(i.add_lossy(duration)),
429+
Timeout::RealTime(s) => {
430+
// If this overflows, try adding just 1h and assume that will not overflow.
431+
Timeout::RealTime(
432+
s.checked_add(duration)
433+
.unwrap_or_else(|| s.checked_add(Duration::from_secs(3600)).unwrap()),
434+
)
435+
}
436+
}
437+
}
438+
}
439+
440+
/// The clock to use for the timeout you are asking for.
441+
#[derive(Debug, Copy, Clone)]
442+
pub enum TimeoutClock {
443+
Monotonic,
444+
RealTime,
445+
}
446+
447+
/// Whether the timeout is relative or absolute.
448+
#[derive(Debug, Copy, Clone)]
449+
pub enum TimeoutAnchor {
450+
Relative,
451+
Absolute,
424452
}
425453

426454
/// A set of threads.
@@ -995,13 +1023,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
9951023
fn block_thread(
9961024
&mut self,
9971025
reason: BlockReason,
998-
timeout: Option<Timeout>,
1026+
timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>,
9991027
callback: impl UnblockCallback<'tcx> + 'tcx,
10001028
) {
10011029
let this = self.eval_context_mut();
1002-
if !this.machine.communicate() && matches!(timeout, Some(Timeout::RealTime(..))) {
1003-
panic!("cannot have `RealTime` callback with isolation enabled!")
1004-
}
1030+
let timeout = timeout.map(|(clock, anchor, duration)| {
1031+
let anchor = match clock {
1032+
TimeoutClock::RealTime => {
1033+
assert!(
1034+
this.machine.communicate(),
1035+
"cannot have `RealTime` timeout with isolation enabled!"
1036+
);
1037+
Timeout::RealTime(match anchor {
1038+
TimeoutAnchor::Absolute => SystemTime::UNIX_EPOCH,
1039+
TimeoutAnchor::Relative => SystemTime::now(),
1040+
})
1041+
}
1042+
TimeoutClock::Monotonic =>
1043+
Timeout::Monotonic(match anchor {
1044+
TimeoutAnchor::Absolute => this.machine.clock.epoch(),
1045+
TimeoutAnchor::Relative => this.machine.clock.now(),
1046+
}),
1047+
};
1048+
anchor.add_lossy(duration)
1049+
});
10051050
this.machine.threads.block_thread(reason, timeout, callback);
10061051
}
10071052

src/tools/miri/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ pub use crate::concurrency::{
132132
init_once::{EvalContextExt as _, InitOnceId},
133133
sync::{CondvarId, EvalContextExt as _, MutexId, RwLockId, SynchronizationObjects},
134134
thread::{
135-
BlockReason, EvalContextExt as _, StackEmptyCallback, ThreadId, ThreadManager, Timeout,
136-
UnblockCallback,
135+
BlockReason, EvalContextExt as _, StackEmptyCallback, ThreadId, ThreadManager,
136+
TimeoutAnchor, TimeoutClock, UnblockCallback,
137137
},
138138
};
139139
pub use crate::diagnostics::{

src/tools/miri/src/shims/time.rs

+5-13
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
7878
this.check_no_isolation("`clock_gettime` with `REALTIME` clocks")?;
7979
system_time_to_duration(&SystemTime::now())?
8080
} else if relative_clocks.contains(&clk_id) {
81-
this.machine.clock.now().duration_since(this.machine.clock.anchor())
81+
this.machine.clock.now().duration_since(this.machine.clock.epoch())
8282
} else {
8383
let einval = this.eval_libc("EINVAL");
8484
this.set_last_error(einval)?;
@@ -246,7 +246,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
246246

247247
// QueryPerformanceCounter uses a hardware counter as its basis.
248248
// Miri will emulate a counter with a resolution of 1 nanosecond.
249-
let duration = this.machine.clock.now().duration_since(this.machine.clock.anchor());
249+
let duration = this.machine.clock.now().duration_since(this.machine.clock.epoch());
250250
let qpc = i64::try_from(duration.as_nanos()).map_err(|_| {
251251
err_unsup_format!("programs running longer than 2^63 nanoseconds are not supported")
252252
})?;
@@ -282,7 +282,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
282282

283283
// This returns a u64, with time units determined dynamically by `mach_timebase_info`.
284284
// We return plain nanoseconds.
285-
let duration = this.machine.clock.now().duration_since(this.machine.clock.anchor());
285+
let duration = this.machine.clock.now().duration_since(this.machine.clock.epoch());
286286
let res = u64::try_from(duration.as_nanos()).map_err(|_| {
287287
err_unsup_format!("programs running longer than 2^64 nanoseconds are not supported")
288288
})?;
@@ -323,16 +323,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
323323
return Ok(-1);
324324
}
325325
};
326-
// If adding the duration overflows, let's just sleep for an hour. Waking up early is always acceptable.
327-
let now = this.machine.clock.now();
328-
let timeout_time = now
329-
.checked_add(duration)
330-
.unwrap_or_else(|| now.checked_add(Duration::from_secs(3600)).unwrap());
331-
let timeout_time = Timeout::Monotonic(timeout_time);
332326

333327
this.block_thread(
334328
BlockReason::Sleep,
335-
Some(timeout_time),
329+
Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)),
336330
callback!(
337331
@capture<'tcx> {}
338332
@unblock = |_this| { panic!("sleeping thread unblocked before time is up") }
@@ -351,12 +345,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
351345
let timeout_ms = this.read_scalar(timeout)?.to_u32()?;
352346

353347
let duration = Duration::from_millis(timeout_ms.into());
354-
let timeout_time = this.machine.clock.now().checked_add(duration).unwrap();
355-
let timeout_time = Timeout::Monotonic(timeout_time);
356348

357349
this.block_thread(
358350
BlockReason::Sleep,
359-
Some(timeout_time),
351+
Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)),
360352
callback!(
361353
@capture<'tcx> {}
362354
@unblock = |_this| { panic!("sleeping thread unblocked before time is up") }

src/tools/miri/src/shims/unix/linux/sync.rs

+15-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::time::SystemTime;
2-
31
use crate::*;
42

53
/// Implementation of the SYS_futex syscall.
@@ -84,15 +82,9 @@ pub fn futex<'tcx>(
8482
}
8583

8684
let timeout = this.deref_pointer_as(&args[3], this.libc_ty_layout("timespec"))?;
87-
let timeout_time = if this.ptr_is_null(timeout.ptr())? {
85+
let timeout = if this.ptr_is_null(timeout.ptr())? {
8886
None
8987
} else {
90-
let realtime = op & futex_realtime == futex_realtime;
91-
if realtime {
92-
this.check_no_isolation(
93-
"`futex` syscall with `op=FUTEX_WAIT` and non-null timeout with `FUTEX_CLOCK_REALTIME`",
94-
)?;
95-
}
9688
let duration = match this.read_timespec(&timeout)? {
9789
Some(duration) => duration,
9890
None => {
@@ -102,23 +94,22 @@ pub fn futex<'tcx>(
10294
return Ok(());
10395
}
10496
};
105-
Some(if wait_bitset {
97+
let timeout_clock = if op & futex_realtime == futex_realtime {
98+
this.check_no_isolation(
99+
"`futex` syscall with `op=FUTEX_WAIT` and non-null timeout with `FUTEX_CLOCK_REALTIME`",
100+
)?;
101+
TimeoutClock::RealTime
102+
} else {
103+
TimeoutClock::Monotonic
104+
};
105+
let timeout_anchor = if wait_bitset {
106106
// FUTEX_WAIT_BITSET uses an absolute timestamp.
107-
if realtime {
108-
Timeout::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
109-
} else {
110-
Timeout::Monotonic(
111-
this.machine.clock.anchor().checked_add(duration).unwrap(),
112-
)
113-
}
107+
TimeoutAnchor::Absolute
114108
} else {
115109
// FUTEX_WAIT uses a relative timestamp.
116-
if realtime {
117-
Timeout::RealTime(SystemTime::now().checked_add(duration).unwrap())
118-
} else {
119-
Timeout::Monotonic(this.machine.clock.now().checked_add(duration).unwrap())
120-
}
121-
})
110+
TimeoutAnchor::Relative
111+
};
112+
Some((timeout_clock, timeout_anchor, duration))
122113
};
123114
// There may be a concurrent thread changing the value of addr
124115
// and then invoking the FUTEX_WAKE syscall. It is critical that the
@@ -172,7 +163,7 @@ pub fn futex<'tcx>(
172163
this.futex_wait(
173164
addr_usize,
174165
bitset,
175-
timeout_time,
166+
timeout,
176167
Scalar::from_target_isize(0, this), // retval_succ
177168
Scalar::from_target_isize(-1, this), // retval_timeout
178169
dest.clone(),

src/tools/miri/src/shims/unix/sync.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::sync::atomic::{AtomicBool, Ordering};
2-
use std::time::SystemTime;
32

43
use rustc_target::abi::Size;
54

@@ -849,19 +848,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
849848
return Ok(());
850849
}
851850
};
852-
let timeout_time = if is_cond_clock_realtime(this, clock_id) {
851+
let timeout_clock = if is_cond_clock_realtime(this, clock_id) {
853852
this.check_no_isolation("`pthread_cond_timedwait` with `CLOCK_REALTIME`")?;
854-
Timeout::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
853+
TimeoutClock::RealTime
855854
} else if clock_id == this.eval_libc_i32("CLOCK_MONOTONIC") {
856-
Timeout::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap())
855+
TimeoutClock::Monotonic
857856
} else {
858857
throw_unsup_format!("unsupported clock id: {}", clock_id);
859858
};
860859

861860
this.condvar_wait(
862861
id,
863862
mutex_id,
864-
Some(timeout_time),
863+
Some((timeout_clock, TimeoutAnchor::Absolute, duration)),
865864
Scalar::from_i32(0),
866865
this.eval_libc("ETIMEDOUT"), // retval_timeout
867866
dest.clone(),

src/tools/miri/src/shims/windows/sync.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
157157
};
158158
let size = Size::from_bytes(size);
159159

160-
let timeout_time = if timeout_ms == this.eval_windows_u32("c", "INFINITE") {
160+
let timeout = if timeout_ms == this.eval_windows_u32("c", "INFINITE") {
161161
None
162162
} else {
163163
let duration = Duration::from_millis(timeout_ms.into());
164-
Some(Timeout::Monotonic(this.machine.clock.now().checked_add(duration).unwrap()))
164+
Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration))
165165
};
166166

167167
// See the Linux futex implementation for why this fence exists.
@@ -177,7 +177,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
177177
this.futex_wait(
178178
addr,
179179
u32::MAX, // bitset
180-
timeout_time,
180+
timeout,
181181
Scalar::from_i32(1), // retval_succ
182182
Scalar::from_i32(0), // retval_timeout
183183
dest.clone(),

0 commit comments

Comments
 (0)