Skip to content

Commit 4ccfeb0

Browse files
committed
make scheduler preemptive, with configurable preemption rate
1 parent 81f20be commit 4ccfeb0

23 files changed

+111
-21
lines changed

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -288,14 +288,16 @@ environment variable. We first document the most relevant and most commonly used
288288
`-Zmiri-disable-isolation` is set.
289289
* `-Zmiri-ignore-leaks` disables the memory leak checker, and also allows some
290290
remaining threads to exist when the main thread exits.
291-
* `-Zmiri-seed=<hex>` configures the seed of the RNG that Miri uses to resolve
292-
non-determinism. This RNG is used to pick base addresses for allocations. When
293-
isolation is enabled (the default), this is also used to emulate system
294-
entropy. The default seed is 0. You can increase test coverage by running Miri
295-
multiple times with different seeds.
296-
**NOTE**: This entropy is not good enough for cryptographic use! Do not
297-
generate secret keys in Miri or perform other kinds of cryptographic
298-
operations that rely on proper random numbers.
291+
* `-Zmiri-preemption-rate` configures the probability that at the end of a basic block, the active
292+
thread will be preempted. The default is `0.01` (i.e., 1%). Setting this to `0` disables
293+
preemption.
294+
* `-Zmiri-seed=<hex>` configures the seed of the RNG that Miri uses to resolve non-determinism. This
295+
RNG is used to pick base addresses for allocations, to determine preemption and failure of
296+
`compare_exchange_weak`, and to control store buffering for weak memory emulation. When isolation
297+
is enabled (the default), this is also used to emulate system entropy. The default seed is 0. You
298+
can increase test coverage by running Miri multiple times with different seeds. **NOTE**: This
299+
entropy is not good enough for cryptographic use! Do not generate secret keys in Miri or perform
300+
other kinds of cryptographic operations that rely on proper random numbers.
299301
* `-Zmiri-strict-provenance` enables [strict
300302
provenance](https://github.com/rust-lang/rust/issues/95228) checking in Miri. This means that
301303
casting an integer to a pointer yields a result with 'invalid' provenance, i.e., with provenance

src/bin/miri.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,17 @@ fn main() {
449449
),
450450
};
451451
miri_config.cmpxchg_weak_failure_rate = rate;
452+
} else if let Some(param) = arg.strip_prefix("-Zmiri-preemption-rate=") {
453+
let rate = match param.parse::<f64>() {
454+
Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate,
455+
Ok(_) => panic!("-Zmiri-preemption-rate must be between `0.0` and `1.0`"),
456+
Err(err) =>
457+
panic!(
458+
"-Zmiri-preemption-rate requires a `f64` between `0.0` and `1.0`: {}",
459+
err
460+
),
461+
};
462+
miri_config.preemption_rate = rate;
452463
} else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") {
453464
miri_config.measureme_out = Some(param.to_string());
454465
} else if let Some(param) = arg.strip_prefix("-Zmiri-backtrace=") {

src/eval.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ pub struct MiriConfig {
122122
/// Whether to ignore any output by the program. This is helpful when debugging miri
123123
/// as its messages don't get intermingled with the program messages.
124124
pub mute_stdout_stderr: bool,
125+
/// The probability of the active thread being preempted at the end of each basic block.
126+
pub preemption_rate: f64,
125127
}
126128

127129
impl Default for MiriConfig {
@@ -145,12 +147,13 @@ impl Default for MiriConfig {
145147
tag_raw: false,
146148
data_race_detector: true,
147149
weak_memory_emulation: true,
148-
cmpxchg_weak_failure_rate: 0.8,
150+
cmpxchg_weak_failure_rate: 0.8, // 80%
149151
measureme_out: None,
150152
panic_on_unsupported: false,
151153
backtrace_style: BacktraceStyle::Short,
152154
provenance_mode: ProvenanceMode::Legacy,
153155
mute_stdout_stderr: false,
156+
preemption_rate: 0.01, // 1%
154157
}
155158
}
156159
}

src/machine.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,9 @@ pub struct Evaluator<'mir, 'tcx> {
333333

334334
/// Whether weak memory emulation is enabled
335335
pub(crate) weak_memory: bool,
336+
337+
/// The probability of the active thread being preempted at the end of each basic block.
338+
pub(crate) preemption_rate: f64,
336339
}
337340

338341
impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
@@ -389,6 +392,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
389392
cmpxchg_weak_failure_rate: config.cmpxchg_weak_failure_rate,
390393
mute_stdout_stderr: config.mute_stdout_stderr,
391394
weak_memory: config.weak_memory_emulation,
395+
preemption_rate: config.preemption_rate,
392396
}
393397
}
394398

@@ -846,6 +850,11 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
846850
ecx.active_thread_stack_mut()
847851
}
848852

853+
fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
854+
ecx.maybe_preempt_active_thread();
855+
Ok(())
856+
}
857+
849858
#[inline(always)]
850859
fn after_stack_push(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
851860
if ecx.machine.stacked_borrows.is_some() { ecx.retag_return_place() } else { Ok(()) }

src/thread.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
717717
this.machine.threads.yield_active_thread();
718718
}
719719

720+
#[inline]
721+
fn maybe_preempt_active_thread(&mut self) {
722+
use rand::Rng as _;
723+
724+
let this = self.eval_context_mut();
725+
if this.machine.rng.get_mut().gen_bool(this.machine.preemption_rate) {
726+
this.yield_active_thread();
727+
}
728+
}
729+
720730
#[inline]
721731
fn register_timeout_callback(
722732
&mut self,

tests/fail/concurrency/libc_pthread_join_self.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// ignore-windows: No libc on Windows
2+
// We are making scheduler assumptions here.
3+
// compile-flags: -Zmiri-preemption-rate=0
24

35
// Joining itself is undefined behavior.
46

tests/fail/data_race/alloc_read_race.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-weak-memory-emulation
2+
// compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
33
#![feature(new_uninit)]
44

55
use std::thread::spawn;

tests/fail/data_race/alloc_write_race.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-weak-memory-emulation
2+
// compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
33
#![feature(new_uninit)]
44

55
use std::thread::spawn;

tests/fail/data_race/dealloc_read_race_stack.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation
2+
// compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
33

44
use std::thread::{spawn, sleep};
55
use std::ptr::null_mut;

tests/fail/data_race/dealloc_write_race_stack.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation
2+
// compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
33

44
use std::thread::{spawn, sleep};
55
use std::ptr::null_mut;

tests/fail/data_race/read_write_race_stack.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-isolation -Zmir-opt-level=0 -Zmiri-disable-weak-memory-emulation
2+
// compile-flags: -Zmiri-disable-isolation -Zmir-opt-level=0 -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
33

44
// Note: mir-opt-level set to 0 to prevent the read of stack_var in thread 1
55
// from being optimized away and preventing the detection of the data-race.

tests/fail/data_race/relax_acquire_race.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-weak-memory-emulation
2+
// compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
33

44
use std::thread::spawn;
55
use std::sync::atomic::{AtomicUsize, Ordering};

tests/fail/data_race/release_seq_race.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation
2+
// compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
33

44
use std::thread::{spawn, sleep};
55
use std::sync::atomic::{AtomicUsize, Ordering};

tests/fail/data_race/release_seq_race_same_thread.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation
2+
// compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
33

44
use std::thread::spawn;
55
use std::sync::atomic::{AtomicUsize, Ordering};

tests/fail/data_race/rmw_race.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-weak-memory-emulation
2+
// compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
33

44
use std::thread::spawn;
55
use std::sync::atomic::{AtomicUsize, Ordering};

tests/fail/data_race/write_write_race_stack.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation
2+
// compile-flags: -Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
33

44
use std::thread::{spawn, sleep};
55
use std::ptr::null_mut;

tests/pass/concurrency/spin_loop.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use std::thread;
2+
use std::sync::atomic::{AtomicUsize, Ordering};
3+
4+
static FLAG: AtomicUsize = AtomicUsize::new(0);
5+
6+
fn spin() {
7+
let j = thread::spawn(|| {
8+
while FLAG.load(Ordering::Acquire) == 0 {
9+
// We do *not* yield, and yet this should terminate eventually.
10+
}
11+
});
12+
thread::yield_now(); // schedule the other thread
13+
FLAG.store(1, Ordering::Release);
14+
j.join().unwrap();
15+
}
16+
17+
fn two_player_ping_pong() {
18+
static FLAG: AtomicUsize = AtomicUsize::new(0);
19+
20+
let waiter1 = thread::spawn(|| {
21+
while FLAG.load(Ordering::Acquire) == 0 {
22+
// We do *not* yield, and yet this should terminate eventually.
23+
}
24+
});
25+
let waiter2 = thread::spawn(|| {
26+
while FLAG.load(Ordering::Acquire) == 0 {
27+
// We do *not* yield, and yet this should terminate eventually.
28+
}
29+
});
30+
let progress = thread::spawn(|| {
31+
FLAG.store(1, Ordering::Release);
32+
});
33+
// The first `join` blocks the main thread and thus takes it out of the equation.
34+
waiter1.join().unwrap();
35+
waiter2.join().unwrap();
36+
progress.join().unwrap();
37+
}
38+
39+
fn main() {
40+
spin();
41+
two_player_ping_pong();
42+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2+
(see https://github.com/rust-lang/miri/issues/1388)
3+

tests/pass/concurrency/spin_loops.rs renamed to tests/pass/concurrency/spin_loops_nopreempt.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2+
// This specifically tests behavior *without* preemption.
3+
// compile-flags: -Zmiri-preemption-rate=0
24

35
use std::thread;
46
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2+
(see https://github.com/rust-lang/miri/issues/1388)
3+

tests/pass/concurrency/sync.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-isolation -Zmiri-strict-provenance
2+
// We are making scheduler assumptions here.
3+
// compile-flags: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-preemption-rate=0
34

45
use std::sync::{Arc, Barrier, Condvar, Mutex, Once, RwLock};
56
use std::thread;

tests/pass/panic/concurrent-panic.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2+
// We are making scheduler assumptions here.
3+
// compile-flags: -Zmiri-preemption-rate=0
24

35
//! Cause a panic in one thread while another thread is unwinding. This checks
46
//! that separate threads have their own panicking state.

tests/pass/weak_memory/weak.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-ignore-leaks
2+
// compile-flags: -Zmiri-ignore-leaks -Zmiri-preemption-rate=0
33

44
// Tests showing weak memory behaviours are exhibited. All tests
55
// return true when the desired behaviour is seen.

0 commit comments

Comments
 (0)