Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement futex_wait and futex_wake. #1568

Merged
merged 23 commits into from
Oct 3, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1ffc5bb
Implement futex_wait and futex_wake.
m-ou-se Oct 1, 2020
281a538
Move futex syscall to its own file.
m-ou-se Oct 1, 2020
6c2f36e
Erase tag from futex pointers.
m-ou-se Oct 1, 2020
69cea1d
Only check futex pointer in futex_wait and not in futex_wake.
m-ou-se Oct 1, 2020
1c582e7
Return correct value from futex_wait.
m-ou-se Oct 1, 2020
c2fa27c
Check maximum amount of arguments to SYS_futex.
m-ou-se Oct 2, 2020
712e800
Improve handling of the `addr` argument in SYS_futex.
m-ou-se Oct 2, 2020
ee3eb4b
Add comments that document SYS_futex better.
m-ou-se Oct 2, 2020
dabd980
Update note about number of arguments to SYS_futex.
m-ou-se Oct 2, 2020
422b505
Add note about arguments in futex implementation.
m-ou-se Oct 2, 2020
d5b3f54
Use force_ptr in futex implementation.
m-ou-se Oct 2, 2020
e64ead2
Implement timeouts for FUTEX_WAIT.
m-ou-se Oct 2, 2020
8113882
Add park/park_timeout/unpark test.
m-ou-se Oct 2, 2020
c9627b2
Use correct return type for syscall(SYS_futex).
m-ou-se Oct 2, 2020
924fd56
Only allow FUTEX_WAIT with timeout when isoloation is disabled.
m-ou-se Oct 3, 2020
6628275
Remove backtics from isolation error.
m-ou-se Oct 3, 2020
5880e7d
Update expected error messages in tests.
m-ou-se Oct 3, 2020
6df54c4
Use read_scalar_at_offset in futex_wait instead of memory.get_raw.
m-ou-se Oct 3, 2020
9d764c5
Add FIXME note about variadic syscall().
m-ou-se Oct 3, 2020
dc36988
Add test for futex syscall.
m-ou-se Oct 3, 2020
dfcb46a
Update syscall FIXME to include note about 'wrong' types.
m-ou-se Oct 3, 2020
c268ee2
Add note about use of force_ptr in futex implementation.
m-ou-se Oct 3, 2020
68776d2
Add FIXME about type of `addr` in futex implementation.
m-ou-se Oct 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 40 additions & 7 deletions src/shims/posix/linux/sync.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::thread::Time;
use crate::*;
use rustc_target::abi::{Align, Size};
use std::time::{Instant, SystemTime};

/// Implementation of the SYS_futex syscall.
pub fn futex<'tcx>(
Expand Down Expand Up @@ -38,23 +40,37 @@ pub fn futex<'tcx>(
let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?;
let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?;
let futex_wake = this.eval_libc_i32("FUTEX_WAKE")?;
let futex_realtime = this.eval_libc_i32("FUTEX_CLOCK_REALTIME")?;

// FUTEX_PRIVATE enables an optimization that stops it from working across processes.
// Miri doesn't support that anyway, so we ignore that flag.
match op & !futex_private {
// FUTEX_WAIT: (int *addr, int op = FUTEX_WAIT, int val, const timespec *timeout)
// Blocks the thread if *addr still equals val. Wakes up when FUTEX_WAKE is called on the same address,
// or *timeout expires. `timeout == null` for an infinite timeout.
op if op == futex_wait => {
op if op & !futex_realtime == futex_wait => {
if args.len() < 5 {
throw_ub_format!("incorrect number of arguments for FUTEX_WAIT syscall: got {}, expected at least 5", args.len());
}
let timeout = this.read_scalar(args[4])?.check_init()?;
if !this.is_null(timeout)? {
// FIXME: Implement timeouts. The condvar waiting code is probably a good example to start with.
// Note that a triggered timeout should have this syscall return with -1 and errno set to ETIMEOUT.
throw_ub_format!("miri does not support timeouts for futex operations");
}
let timeout = args[4];
let timeout_time = if this.is_null(this.read_scalar(timeout)?.check_init()?)? {
None
} else {
let duration = match this.read_timespec(timeout)? {
m-ou-se marked this conversation as resolved.
Show resolved Hide resolved
Some(duration) => duration,
None => {
let einval = this.eval_libc("EINVAL")?;
this.set_last_error(einval)?;
this.write_scalar(Scalar::from_i32(-1), dest)?;
return Ok(());
}
};
Some(if op & futex_realtime != 0 {
Time::RealTime(SystemTime::now().checked_add(duration).unwrap())
} else {
Time::Monotonic(Instant::now().checked_add(duration).unwrap())
})
};
// Check the pointer for alignment and validity.
// Atomic operations are only available for fully aligned values.
this.memory.check_ptr_access(addr.into(), Size::from_bytes(4), Align::from_bytes(4).unwrap())?;
Expand All @@ -66,6 +82,22 @@ pub fn futex<'tcx>(
this.futex_wait(addr, thread);
// Succesfully waking up from FUTEX_WAIT always returns zero.
this.write_scalar(Scalar::from_i32(0), dest)?;
// Register a timeout callback if a timeout was specified.
// This callback will override the return value when the timeout triggers.
if let Some(timeout_time) = timeout_time {
this.register_timeout_callback(
thread,
timeout_time,
Box::new(move |this| {
this.unblock_thread(thread);
this.futex_remove_waiter(addr, thread);
let etimedout = this.eval_libc("ETIMEDOUT")?;
this.set_last_error(etimedout)?;
this.write_scalar(Scalar::from_i32(-1), dest)?;
Ok(())
}),
);
}
} else {
// The futex value doesn't match the expected value, so we return failure
// right away without sleeping: -1 and errno set to EAGAIN.
Expand All @@ -83,6 +115,7 @@ pub fn futex<'tcx>(
for _ in 0..val {
if let Some(thread) = this.futex_wake(addr) {
this.unblock_thread(thread);
this.unregister_timeout_callback_if_exists(thread);
n += 1;
} else {
break;
Expand Down
7 changes: 7 additions & 0 deletions src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,4 +430,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let waiters = &mut this.machine.threads.sync.futexes.get_mut(&addr.erase_tag())?.waiters;
waiters.pop_front().map(|waiter| waiter.thread)
}

fn futex_remove_waiter(&mut self, addr: Pointer<stacked_borrows::Tag>, thread: ThreadId) {
let this = self.eval_context_mut();
if let Some(futex) = this.machine.threads.sync.futexes.get_mut(&addr.erase_tag()) {
futex.waiters.retain(|waiter| waiter.thread != thread);
}
}
}
37 changes: 37 additions & 0 deletions tests/run-pass/concurrency/parking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// ignore-windows: Concurrency on Windows is not supported yet.
// compile-flags: -Zmiri-disable-isolation

use std::thread;
use std::time::{Duration, Instant};

// Normally, waiting in park/park_timeout may spuriously wake up early, but we
// know Miri's timed synchronization primitives do not do that.

fn park_timeout() {
let start = Instant::now();

thread::park_timeout(Duration::from_millis(200));

assert!((200..500).contains(&start.elapsed().as_millis()));
m-ou-se marked this conversation as resolved.
Show resolved Hide resolved
}

fn park_unpark() {
let t1 = thread::current();
let t2 = thread::spawn(move || {
thread::park();
thread::sleep(Duration::from_millis(200));
t1.unpark();
});

let start = Instant::now();

t2.thread().unpark();
thread::park();

assert!((200..500).contains(&start.elapsed().as_millis()));
}

fn main() {
park_timeout();
park_unpark();
}
2 changes: 2 additions & 0 deletions tests/run-pass/concurrency/parking.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
warning: thread support is experimental. For example, Miri does not detect data races yet.