]> git.lizzy.rs Git - rust.git/commitdiff
Implement timeouts for FUTEX_WAIT.
authorMara Bos <m-ou.se@m-ou.se>
Fri, 2 Oct 2020 21:34:14 +0000 (23:34 +0200)
committerMara Bos <m-ou.se@m-ou.se>
Fri, 2 Oct 2020 21:34:14 +0000 (23:34 +0200)
src/shims/posix/linux/sync.rs
src/sync.rs

index 1cfcb65bdc1910cb6521cfccd72526de23e9dab2..4201ef3f4790faf9c1e4a5cefe1ad1c8c41aaf99 100644 (file)
@@ -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>(
@@ -38,6 +40,7 @@ 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.
@@ -45,16 +48,29 @@ pub fn futex<'tcx>(
         // 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)? {
+                    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())?;
@@ -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.
@@ -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;
index f8b6f99f1e0332324b3ce2cdf228791107a6a52d..0c12da8d684562a51af5a8e50d1390ec032b5bc1 100644 (file)
@@ -430,4 +430,11 @@ fn futex_wake(&mut self, addr: Pointer<stacked_borrows::Tag>) -> Option<ThreadId
         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);
+        }
+    }
 }