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