Add timeout support for futex wait
This commit is contained in:
		
							parent
							
								
									96876b2935
								
							
						
					
					
						commit
						eff91daac9
					
				| @ -3,6 +3,7 @@ use std::collections::hash_map::DefaultHasher; | |||||||
| use std::hash::{Hash, Hasher}; | use std::hash::{Hash, Hasher}; | ||||||
| use std::intrinsics::atomic_load; | use std::intrinsics::atomic_load; | ||||||
| use std::sync::atomic::{AtomicBool, Ordering}; | use std::sync::atomic::{AtomicBool, Ordering}; | ||||||
|  | use time::timespec_t; | ||||||
| 
 | 
 | ||||||
| /// `FutexOp`, `FutexFlags`, and `futex_op_and_flags_from_u32` are helper types and
 | /// `FutexOp`, `FutexFlags`, and `futex_op_and_flags_from_u32` are helper types and
 | ||||||
| /// functions for handling the versatile commands and arguments of futex system
 | /// functions for handling the versatile commands and arguments of futex system
 | ||||||
| @ -68,7 +69,15 @@ pub fn futex_op_and_flags_from_u32(bits: u32) -> Result<(FutexOp, FutexFlags)> { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Do futex wait
 | /// Do futex wait
 | ||||||
| pub fn futex_wait(futex_addr: *const i32, futex_val: i32) -> Result<()> { | pub fn futex_wait( | ||||||
|  |     futex_addr: *const i32, | ||||||
|  |     futex_val: i32, | ||||||
|  |     timeout: &Option<timespec_t>, | ||||||
|  | ) -> Result<()> { | ||||||
|  |     info!( | ||||||
|  |         "futex_wait addr: {:#x}, val: {}, timeout: {:?}", | ||||||
|  |         futex_addr as usize, futex_val, timeout | ||||||
|  |     ); | ||||||
|     // Get and lock the futex bucket
 |     // Get and lock the futex bucket
 | ||||||
|     let futex_key = FutexKey::new(futex_addr); |     let futex_key = FutexKey::new(futex_addr); | ||||||
|     let (_, futex_bucket_ref) = FUTEX_BUCKETS.get_bucket(futex_key); |     let (_, futex_bucket_ref) = FUTEX_BUCKETS.get_bucket(futex_key); | ||||||
| @ -76,7 +85,7 @@ pub fn futex_wait(futex_addr: *const i32, futex_val: i32) -> Result<()> { | |||||||
| 
 | 
 | ||||||
|     // Check the futex value
 |     // Check the futex value
 | ||||||
|     if futex_key.load_val() != futex_val { |     if futex_key.load_val() != futex_val { | ||||||
|         return_errno!(EAGAIN, "futex value does not match") |         return_errno!(EAGAIN, "futex value does not match"); | ||||||
|     } |     } | ||||||
|     // Why we first lock the bucket then check the futex value?
 |     // Why we first lock the bucket then check the futex value?
 | ||||||
|     //
 |     //
 | ||||||
| @ -115,7 +124,7 @@ pub fn futex_wait(futex_addr: *const i32, futex_val: i32) -> Result<()> { | |||||||
| 
 | 
 | ||||||
|     // Must make sure that no locks are holded by this thread before wait
 |     // Must make sure that no locks are holded by this thread before wait
 | ||||||
|     drop(futex_bucket); |     drop(futex_bucket); | ||||||
|     futex_item.wait() |     futex_item.wait_timeout(timeout) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Do futex wake
 | /// Do futex wake
 | ||||||
| @ -202,7 +211,7 @@ impl FutexKey { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone, PartialEq)] | ||||||
| struct FutexItem { | struct FutexItem { | ||||||
|     key: FutexKey, |     key: FutexKey, | ||||||
|     waiter: WaiterRef, |     waiter: WaiterRef, | ||||||
| @ -220,8 +229,19 @@ impl FutexItem { | |||||||
|         self.waiter.wake() |         self.waiter.wake() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn wait(&self) -> Result<()> { |     pub fn wait_timeout(&self, timeout: &Option<timespec_t>) -> Result<()> { | ||||||
|         self.waiter.wait() |         match timeout { | ||||||
|  |             None => self.waiter.wait(), | ||||||
|  |             Some(ts) => { | ||||||
|  |                 if let Err(e) = self.waiter.wait_timeout(&ts) { | ||||||
|  |                     let (_, futex_bucket_ref) = FUTEX_BUCKETS.get_bucket(self.key); | ||||||
|  |                     let mut futex_bucket = futex_bucket_ref.lock().unwrap(); | ||||||
|  |                     futex_bucket.dequeue_item(self); | ||||||
|  |                     return_errno!(e.errno(), "futex wait with timeout error"); | ||||||
|  |                 } | ||||||
|  |                 Ok(()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -242,6 +262,14 @@ impl FutexBucket { | |||||||
|         self.queue.push_back(item); |         self.queue.push_back(item); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn dequeue_item(&mut self, futex_item: &FutexItem) -> Option<FutexItem> { | ||||||
|  |         let item_i = self.queue.iter().position(|item| *item == *futex_item); | ||||||
|  |         if item_i.is_none() { | ||||||
|  |             return None; | ||||||
|  |         } | ||||||
|  |         self.queue.swap_remove_back(item_i.unwrap()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn dequeue_and_wake_items(&mut self, key: FutexKey, max_count: usize) -> usize { |     pub fn dequeue_and_wake_items(&mut self, key: FutexKey, max_count: usize) -> usize { | ||||||
|         let mut count = 0; |         let mut count = 0; | ||||||
|         let mut idx = 0; |         let mut idx = 0; | ||||||
| @ -349,6 +377,22 @@ impl Waiter { | |||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn wait_timeout(&self, timeout: ×pec_t) -> Result<()> { | ||||||
|  |         let current = unsafe { sgx_thread_get_self() }; | ||||||
|  |         if current != self.thread { | ||||||
|  |             return Ok(()); | ||||||
|  |         } | ||||||
|  |         while self.is_woken.load(Ordering::SeqCst) == false { | ||||||
|  |             if let Err(e) = wait_event_timeout(self.thread, timeout) { | ||||||
|  |                 self.is_woken.store(true, Ordering::SeqCst); | ||||||
|  |                 // Do sanity check here, only possible errnos here are ETIMEDOUT, EAGAIN and EINTR
 | ||||||
|  |                 debug_assert!(e.errno() == ETIMEDOUT || e.errno() == EAGAIN || e.errno() == EINTR); | ||||||
|  |                 return_errno!(e.errno(), "wait_timeout error"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn wake(&self) { |     pub fn wake(&self) { | ||||||
|         if self.is_woken.fetch_or(true, Ordering::SeqCst) == false { |         if self.is_woken.fetch_or(true, Ordering::SeqCst) == false { | ||||||
|             set_event(self.thread); |             set_event(self.thread); | ||||||
| @ -356,6 +400,12 @@ impl Waiter { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl PartialEq for Waiter { | ||||||
|  |     fn eq(&self, other: &Self) -> bool { | ||||||
|  |         self.thread == other.thread | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| unsafe impl Send for Waiter {} | unsafe impl Send for Waiter {} | ||||||
| unsafe impl Sync for Waiter {} | unsafe impl Sync for Waiter {} | ||||||
| 
 | 
 | ||||||
| @ -370,6 +420,31 @@ fn wait_event(thread: *const c_void) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | fn wait_event_timeout(thread: *const c_void, timeout: ×pec_t) -> Result<()> { | ||||||
|  |     let mut ret: c_int = 0; | ||||||
|  |     let mut sgx_ret: c_int = 0; | ||||||
|  |     let mut errno: c_int = 0; | ||||||
|  |     unsafe { | ||||||
|  |         sgx_ret = sgx_thread_wait_untrusted_event_timeout_ocall( | ||||||
|  |             &mut ret as *mut c_int, | ||||||
|  |             thread, | ||||||
|  |             timeout.sec(), | ||||||
|  |             timeout.nsec(), | ||||||
|  |             &mut errno as *mut c_int, | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |     if ret != 0 || sgx_ret != 0 { | ||||||
|  |         panic!("ERROR: sgx_thread_wait_untrusted_event_timeout_ocall failed"); | ||||||
|  |     } | ||||||
|  |     if errno != 0 { | ||||||
|  |         return_errno!( | ||||||
|  |             Errno::from(errno as u32), | ||||||
|  |             "sgx_thread_wait_untrusted_event_timeout_ocall error" | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| fn set_event(thread: *const c_void) { | fn set_event(thread: *const c_void) { | ||||||
|     let mut ret: c_int = 0; |     let mut ret: c_int = 0; | ||||||
|     let mut sgx_ret: c_int = 0; |     let mut sgx_ret: c_int = 0; | ||||||
| @ -387,6 +462,14 @@ extern "C" { | |||||||
|     /* Go outside and wait on my untrusted event */ |     /* Go outside and wait on my untrusted event */ | ||||||
|     fn sgx_thread_wait_untrusted_event_ocall(ret: *mut c_int, self_thread: *const c_void) -> c_int; |     fn sgx_thread_wait_untrusted_event_ocall(ret: *mut c_int, self_thread: *const c_void) -> c_int; | ||||||
| 
 | 
 | ||||||
|  |     fn sgx_thread_wait_untrusted_event_timeout_ocall( | ||||||
|  |         ret: *mut c_int, | ||||||
|  |         self_thread: *const c_void, | ||||||
|  |         sec: c_long, | ||||||
|  |         nsec: c_long, | ||||||
|  |         errno: *mut c_int, | ||||||
|  |     ) -> c_int; | ||||||
|  | 
 | ||||||
|     /* Wake a thread waiting on its untrusted event */ |     /* Wake a thread waiting on its untrusted event */ | ||||||
|     fn sgx_thread_set_untrusted_event_ocall(ret: *mut c_int, waiter_thread: *const c_void) |     fn sgx_thread_set_untrusted_event_ocall(ret: *mut c_int, waiter_thread: *const c_void) | ||||||
|         -> c_int; |         -> c_int; | ||||||
|  | |||||||
| @ -183,7 +183,7 @@ pub extern "C" fn dispatch_syscall( | |||||||
|             arg0 as *const i32, |             arg0 as *const i32, | ||||||
|             arg1 as u32, |             arg1 as u32, | ||||||
|             arg2 as i32, |             arg2 as i32, | ||||||
|             arg3 as i32, |             arg3 as u64, | ||||||
|             arg4 as *const i32, |             arg4 as *const i32, | ||||||
|             // Todo: accept other optional arguments
 |             // Todo: accept other optional arguments
 | ||||||
|         ), |         ), | ||||||
| @ -460,7 +460,7 @@ pub fn do_futex( | |||||||
|     futex_addr: *const i32, |     futex_addr: *const i32, | ||||||
|     futex_op: u32, |     futex_op: u32, | ||||||
|     futex_val: i32, |     futex_val: i32, | ||||||
|     timeout: i32, |     timeout: u64, | ||||||
|     futex_new_addr: *const i32, |     futex_new_addr: *const i32, | ||||||
| ) -> Result<isize> { | ) -> Result<isize> { | ||||||
|     check_ptr(futex_addr)?; |     check_ptr(futex_addr)?; | ||||||
| @ -474,7 +474,22 @@ pub fn do_futex( | |||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     match futex_op { |     match futex_op { | ||||||
|         FutexOp::FUTEX_WAIT => process::futex_wait(futex_addr, futex_val).map(|_| 0), |         FutexOp::FUTEX_WAIT => { | ||||||
|  |             let timeout = { | ||||||
|  |                 let timeout = timeout as *const timespec_t; | ||||||
|  |                 if timeout.is_null() { | ||||||
|  |                     None | ||||||
|  |                 } else { | ||||||
|  |                     let ts = timespec_t::from_raw_ptr(timeout)?; | ||||||
|  |                     ts.validate()?; | ||||||
|  |                     if futex_flags.contains(FutexFlags::FUTEX_CLOCK_REALTIME) { | ||||||
|  |                         warn!("CLOCK_REALTIME is not supported yet, use monotonic clock"); | ||||||
|  |                     } | ||||||
|  |                     Some(ts) | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |             process::futex_wait(futex_addr, futex_val, &timeout).map(|_| 0) | ||||||
|  |         } | ||||||
|         FutexOp::FUTEX_WAKE => { |         FutexOp::FUTEX_WAKE => { | ||||||
|             let max_count = get_futex_val(futex_val)?; |             let max_count = get_futex_val(futex_val)?; | ||||||
|             process::futex_wake(futex_addr, max_count).map(|count| count as isize) |             process::futex_wake(futex_addr, max_count).map(|count| count as isize) | ||||||
| @ -482,7 +497,7 @@ pub fn do_futex( | |||||||
|         FutexOp::FUTEX_REQUEUE => { |         FutexOp::FUTEX_REQUEUE => { | ||||||
|             check_ptr(futex_new_addr)?; |             check_ptr(futex_new_addr)?; | ||||||
|             let max_nwakes = get_futex_val(futex_val)?; |             let max_nwakes = get_futex_val(futex_val)?; | ||||||
|             let max_nrequeues = get_futex_val(timeout)?; |             let max_nrequeues = get_futex_val(timeout as i32)?; | ||||||
|             process::futex_requeue(futex_addr, max_nwakes, max_nrequeues, futex_new_addr) |             process::futex_requeue(futex_addr, max_nwakes, max_nrequeues, futex_new_addr) | ||||||
|                 .map(|nwakes| nwakes as isize) |                 .map(|nwakes| nwakes as isize) | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -75,6 +75,14 @@ impl timespec_t { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn sec(&self) -> time_t { | ||||||
|  |         self.sec | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn nsec(&self) -> i64 { | ||||||
|  |         self.nsec | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn as_duration(&self) -> Duration { |     pub fn as_duration(&self) -> Duration { | ||||||
|         Duration::new(self.sec as u64, self.nsec as u32) |         Duration::new(self.sec as u64, self.nsec as u32) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| #include <sys/types.h> | #include <sys/types.h> | ||||||
| #include <pthread.h> | #include <pthread.h> | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
|  | #include <errno.h> | ||||||
| #include "test.h" | #include "test.h" | ||||||
| 
 | 
 | ||||||
| // ============================================================================
 | // ============================================================================
 | ||||||
| @ -162,6 +163,28 @@ static int test_mutex_with_cond_wait(void) { | |||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ============================================================================
 | ||||||
|  | // The test case of timed lock
 | ||||||
|  | // ============================================================================
 | ||||||
|  | 
 | ||||||
|  | static int test_mutex_timedlock() { | ||||||
|  |     int err; | ||||||
|  |     struct timespec ts; | ||||||
|  |     pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; | ||||||
|  | 
 | ||||||
|  |     pthread_mutex_lock(&lock); | ||||||
|  |     clock_gettime(CLOCK_REALTIME, &ts); | ||||||
|  |     ts.tv_sec += 1; | ||||||
|  |     /*
 | ||||||
|  |      * This will cause a deadlock, a timeout error will return | ||||||
|  |      */ | ||||||
|  |     err = pthread_mutex_timedlock(&lock, &ts); | ||||||
|  |     if (err != ETIMEDOUT) { | ||||||
|  |         THROW_ERROR("mutex timed lock failed"); | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // ============================================================================
 | // ============================================================================
 | ||||||
| // Test suite main
 | // Test suite main
 | ||||||
| // ============================================================================
 | // ============================================================================
 | ||||||
| @ -169,6 +192,7 @@ static int test_mutex_with_cond_wait(void) { | |||||||
| static test_case_t test_cases[] = { | static test_case_t test_cases[] = { | ||||||
|     TEST_CASE(test_mutex_with_concurrent_counter), |     TEST_CASE(test_mutex_with_concurrent_counter), | ||||||
|     TEST_CASE(test_mutex_with_cond_wait), |     TEST_CASE(test_mutex_with_cond_wait), | ||||||
|  |     TEST_CASE(test_mutex_timedlock), | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| int main() { | int main() { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user