diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 5852a1247240393a85020fa1d484e06eea2673cb..2a82ffff21537aaa2d2d09eb2cd7b7896da18ef5 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -235,15 +235,32 @@ impl Pal for Sys { e(unsafe { syscall!(FTRUNCATE, fildes, length) }) as c_int } - fn futex( - addr: *mut c_int, - op: c_int, - val: c_int, - val2: usize, - ) -> Result<c_long, crate::pthread::Errno> { - e_raw(unsafe { syscall!(FUTEX, addr, op, val, val2, 0, 0) }) - .map(|r| r as c_long) - .map_err(|e| crate::pthread::Errno(e as c_int)) + #[inline] + unsafe fn futex_wait( + addr: *mut u32, + val: u32, + deadline: *const timespec, + ) -> Result<(), crate::pthread::Errno> { + e_raw(unsafe { + syscall!( + FUTEX, addr, // uaddr + 9, // futex_op: FUTEX_WAIT_BITSET + val, // val + deadline, // timeout: deadline + 0, // uaddr2/val2: 0/NULL + 0xffffffff // val3: FUTEX_BITSET_MATCH_ANY + ) + }) + .map_err(|e| crate::pthread::Errno(e as c_int)) + .map(|_| ()) + } + #[inline] + unsafe fn futex_wake(addr: *mut u32, num: u32) -> Result<c_int, crate::pthread::Errno> { + e_raw(unsafe { + syscall!(FUTEX, addr, 1 /* FUTEX_WAKE */, num) + }) + .map_err(|e| crate::pthread::Errno(e as c_int)) + .map(|n| n as c_int) } fn futimens(fd: c_int, times: *const timespec) -> c_int { diff --git a/src/platform/pal/mod.rs b/src/platform/pal/mod.rs index 566de802c3bd6d03d1e2178208e0694f511e33d2..e8a6b21fd2bd5bb0349796aedfd3b2e953ef11e8 100644 --- a/src/platform/pal/mod.rs +++ b/src/platform/pal/mod.rs @@ -10,6 +10,7 @@ use crate::{ sys_utsname::utsname, time::timespec, }, + pthread, }; pub use self::epoll::PalEpoll; @@ -78,12 +79,12 @@ pub trait Pal { fn ftruncate(fildes: c_int, length: off_t) -> c_int; - fn futex( - addr: *mut c_int, - op: c_int, - val: c_int, - val2: usize, - ) -> Result<c_long, crate::pthread::Errno>; + unsafe fn futex_wait( + addr: *mut u32, + val: u32, + deadline: *const timespec, + ) -> Result<(), pthread::Errno>; + unsafe fn futex_wake(addr: *mut u32, num: u32) -> Result<c_int, pthread::Errno>; fn futimens(fd: c_int, times: *const timespec) -> c_int; diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index 811ef9e6d99b771d451bb5536333fe61e0c31c61..69001a0dbf47754d2eb5849cc3bcb294b34a4f98 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -24,6 +24,7 @@ use crate::{ unistd::{F_OK, R_OK, W_OK, X_OK}, }, io::{self, prelude::*, BufReader}, + pthread, }; pub use redox_exec::FdGuard; @@ -300,25 +301,35 @@ impl Pal for Sys { e(syscall::ftruncate(fd as usize, len as usize)) as c_int } - // FIXME: unsound - fn futex( - addr: *mut c_int, - op: c_int, - val: c_int, - val2: usize, - ) -> Result<c_long, crate::pthread::Errno> { - match unsafe { - syscall::futex( - addr as *mut i32, - op as usize, - val as i32, - val2, - ptr::null_mut(), - ) - } { - Ok(success) => Ok(success as c_long), - Err(err) => Err(crate::pthread::Errno(err.errno)), - } + #[inline] + unsafe fn futex_wait( + addr: *mut u32, + val: u32, + deadline: *const timespec, + ) -> CoreResult<(), pthread::Errno> { + syscall::syscall5( + syscall::SYS_FUTEX, + addr as usize, + syscall::FUTEX_WAIT, + val as usize, + deadline as usize, + 0, + ) + .map_err(|s| pthread::Errno(s.errno)) + .map(|_| ()) + } + #[inline] + unsafe fn futex_wake(addr: *mut u32, num: u32) -> Result<c_int, pthread::Errno> { + syscall::syscall5( + syscall::SYS_FUTEX, + addr as usize, + syscall::FUTEX_WAKE, + num as usize, + 0, + 0, + ) + .map_err(|s| pthread::Errno(s.errno)) + .map(|n| n as c_int) } // FIXME: unsound @@ -1106,7 +1117,7 @@ impl Pal for Sys { fn verify() -> bool { // GETPID on Redox is 20, which is WRITEV on Linux - e(unsafe { syscall::syscall5(syscall::number::SYS_GETPID, !0, !0, !0, !0, !0) }) != !0 + (unsafe { syscall::syscall5(syscall::number::SYS_GETPID, !0, !0, !0, !0, !0) }).is_ok() } fn exit_thread() -> ! { diff --git a/src/sync/cond.rs b/src/sync/cond.rs index ee7ff97bd2eefaf8e3252d96faaa6f08cdf8a4b3..aa3b6247acb86d585274ca0164b1164e7741f036 100644 --- a/src/sync/cond.rs +++ b/src/sync/cond.rs @@ -74,7 +74,7 @@ impl Cond { unlock: impl FnOnce() -> Result<()>, lock: impl FnOnce() -> Result<()>, lock_with_timeout: impl FnOnce(×pec) -> Result<()>, - timeout: Option<×pec>, + deadline: Option<×pec>, ) -> Result<(), Errno> { // TODO: Error checking for certain types (i.e. robust and errorcheck) of mutexes, e.g. if the // mutex is not locked. @@ -83,14 +83,10 @@ impl Cond { unlock(); - match timeout { - Some(timeout) => { - crate::sync::futex_wait( - &self.cur, - current, - timespec::subtract(*timeout, crate::sync::rttime()).as_ref(), - ); - lock_with_timeout(timeout); + match deadline { + Some(deadline) => { + crate::sync::futex_wait(&self.cur, current, Some(&deadline)); + lock_with_timeout(deadline); } None => { crate::sync::futex_wait(&self.cur, current, None); diff --git a/src/sync/mod.rs b/src/sync/mod.rs index e3cd32c73461f87cf9e823ceb857c9b05735b248..d851f925cad5e63d247ab4458cd9a54e171063f7 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs @@ -16,8 +16,12 @@ pub use self::{ }; use crate::{ - header::time::timespec, + header::{ + errno::{EAGAIN, ETIMEDOUT}, + time::timespec, + }, platform::{types::*, Pal, Sys}, + pthread::Errno, }; use core::{ mem::MaybeUninit, @@ -36,7 +40,7 @@ pub enum AttemptStatus { } pub trait FutexTy { - fn conv(self) -> i32; + fn conv(self) -> u32; } pub trait FutexAtomicTy { type Ty: FutexTy; @@ -44,13 +48,13 @@ pub trait FutexAtomicTy { fn ptr(&self) -> *mut Self::Ty; } impl FutexTy for u32 { - fn conv(self) -> i32 { - self as i32 + fn conv(self) -> u32 { + self } } impl FutexTy for i32 { - fn conv(self) -> i32 { - self + fn conv(self) -> u32 { + self as u32 } } impl FutexAtomicTy for AtomicU32 { @@ -91,20 +95,26 @@ impl FutexAtomicTy for AtomicI32 { pub unsafe fn futex_wake_ptr(ptr: *mut impl FutexTy, n: i32) -> usize { // TODO: unwrap_unchecked? - Sys::futex(ptr.cast(), FUTEX_WAKE, n, 0).unwrap() as usize + Sys::futex_wake(ptr.cast(), n as u32).unwrap() as usize } pub unsafe fn futex_wait_ptr<T: FutexTy>( ptr: *mut T, value: T, - timeout_opt: Option<×pec>, -) -> bool { - // TODO: unwrap_unchecked? - Sys::futex( + deadline_opt: Option<×pec>, +) -> FutexWaitResult { + match Sys::futex_wait( ptr.cast(), - FUTEX_WAIT, value.conv(), - timeout_opt.map_or(0, |t| t as *const _ as usize), - ) == Ok(0) + deadline_opt.map_or(core::ptr::null(), |t| t as *const _), + ) { + Ok(()) => FutexWaitResult::Waited, + Err(Errno(EAGAIN)) => FutexWaitResult::Stale, + Err(Errno(ETIMEDOUT)) if deadline_opt.is_some() => FutexWaitResult::TimedOut, + Err(other) => { + eprintln!("futex failed: {}", other.0); + FutexWaitResult::Waited + } + } } pub fn futex_wake(atomic: &impl FutexAtomicTy, n: i32) -> usize { unsafe { futex_wake_ptr(atomic.ptr(), n) } @@ -112,9 +122,16 @@ pub fn futex_wake(atomic: &impl FutexAtomicTy, n: i32) -> usize { pub fn futex_wait<T: FutexAtomicTy>( atomic: &T, value: T::Ty, - timeout_opt: Option<×pec>, -) -> bool { - unsafe { futex_wait_ptr(atomic.ptr(), value, timeout_opt) } + deadline_opt: Option<×pec>, +) -> FutexWaitResult { + unsafe { futex_wait_ptr(atomic.ptr(), value, deadline_opt) } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FutexWaitResult { + Waited, // possibly spurious + Stale, // outdated value + TimedOut, } pub fn rttime() -> timespec { @@ -187,7 +204,7 @@ impl AtomicLock { pub fn wait_if(&self, value: c_int, timeout_opt: Option<×pec>) { self.wait_if_raw(value, timeout_opt); } - pub fn wait_if_raw(&self, value: c_int, timeout_opt: Option<×pec>) -> bool { + pub fn wait_if_raw(&self, value: c_int, timeout_opt: Option<×pec>) -> FutexWaitResult { futex_wait(&self.atomic, value, timeout_opt) } diff --git a/src/sync/pthread_mutex.rs b/src/sync/pthread_mutex.rs index cb08dde8cc2ca5da7a7f665bf20f61fb4b4ba74a..030cac85830d8beb0641db19d26d8d6cf8d04308 100644 --- a/src/sync/pthread_mutex.rs +++ b/src/sync/pthread_mutex.rs @@ -10,6 +10,8 @@ use crate::{ use crate::platform::{types::*, Pal, Sys}; +use super::FutexWaitResult; + pub struct RlctMutex { // Actual locking word. inner: AtomicUint, @@ -123,7 +125,11 @@ impl RlctMutex { // If the mutex is not robust, simply futex_wait until unblocked. //crate::sync::futex_wait(&self.inner, inner | WAITING_BIT, None); - crate::sync::futex_wait(&self.inner, thread, None); + if crate::sync::futex_wait(&self.inner, thread, deadline) + == FutexWaitResult::TimedOut + { + return Err(Errno(ETIMEDOUT)); + } } } } diff --git a/src/sync/rwlock.rs b/src/sync/rwlock.rs index fe066a1798c57a18c2e09f238e202a2f153f572d..f5ecaf12fe601ecdfaf8d022b8ec1d6d21ae2394 100644 --- a/src/sync/rwlock.rs +++ b/src/sync/rwlock.rs @@ -21,7 +21,7 @@ impl Rwlock { state: AtomicU32::new(0), } } - pub fn acquire_write_lock(&self, _timeout: Option<×pec>) { + pub fn acquire_write_lock(&self, deadline: Option<×pec>) { let mut waiting_wr = self.state.load(Ordering::Relaxed) & WAITING_WR; loop { @@ -45,16 +45,15 @@ impl Rwlock { }; waiting_wr = expected & WAITING_WR; - // TODO: timeout - let _ = crate::sync::futex_wait(&self.state, expected, None); + let _ = crate::sync::futex_wait(&self.state, expected, deadline); } } } } - pub fn acquire_read_lock(&self, _timeout: Option<×pec>) { + pub fn acquire_read_lock(&self, deadline: Option<×pec>) { // TODO: timeout while let Err(old) = self.try_acquire_read_lock() { - crate::sync::futex_wait(&self.state, old, None); + crate::sync::futex_wait(&self.state, old, deadline); } } pub fn try_acquire_read_lock(&self) -> Result<(), u32> { diff --git a/tests/Makefile b/tests/Makefile index e1682dd0bdddefaafdf5a87fdee7fb09bdc1509c..46690fd8f2904e30a8a8a18a7bf15b859a7665a6 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -186,6 +186,7 @@ NAMES=\ pthread/rwlock_trylock \ pthread/rwlock_randtest \ pthread/mutex_recursive \ + pthread/timeout \ grp/getgrouplist \ grp/getgrgid_r \ grp/getgrnam_r \ diff --git a/tests/pthread/timeout.c b/tests/pthread/timeout.c new file mode 100644 index 0000000000000000000000000000000000000000..eea85e1380b2b83a51d24a08589f1cc91b4b2ccf --- /dev/null +++ b/tests/pthread/timeout.c @@ -0,0 +1,91 @@ +#include "../test_helpers.h" +#include "common.h" + +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +#include <pthread.h> +#include <time.h> + +struct arg { + int status; + bool completed; + pthread_barrier_t barrier; + pthread_mutex_t mutex; +}; + +void *routine(void *arg_raw) { + struct arg *arg = (struct arg *)arg_raw; + + int barrier_status = pthread_barrier_wait(&arg->barrier); + + if (barrier_status != 0 && barrier_status != PTHREAD_BARRIER_SERIAL_THREAD) { + arg->status = barrier_status; + fputs("failed to wait for barrier\n", stderr); + return NULL; + } + puts("thread waited"); + + struct timespec abstime; + if ((arg->status = clock_gettime(CLOCK_MONOTONIC, &abstime)) != 0) { + fputs("failed to get current time\n", stderr); + return NULL; + } + abstime.tv_sec += 1; + + if ((arg->status = pthread_mutex_timedlock(&arg->mutex, &abstime)) != ETIMEDOUT) { + fputs("failed to fail at locking mutex\n", stderr); + return NULL; + } + arg->status = 0; + + return NULL; +} + +int main(void) { + int status; + + struct arg arg; + arg.completed = false; + + status = pthread_barrier_init(&arg.barrier, NULL, 2); + ERROR_IF2(pthread_barrier_init, status, != 0); + + status = pthread_mutex_init(&arg.mutex, NULL); + ERROR_IF2(pthread_mutex_init, status, != 0); + + status = pthread_mutex_trylock(&arg.mutex); + ERROR_IF2(pthread_mutex_trylock, status, != 0); + + pthread_t thread; + status = pthread_create(&thread, NULL, routine, &arg); + ERROR_IF2(pthread_create, status, != 0); + + status = pthread_barrier_wait(&arg.barrier); + if (status != PTHREAD_BARRIER_SERIAL_THREAD) { + ERROR_IF2(pthread_barrier_wait, status, != 0); + } + puts("main waited"); + + status = pthread_join(thread, NULL); + ERROR_IF2(pthread_join, status, != 0); + + status = pthread_mutex_unlock(&arg.mutex); + ERROR_IF2(pthread_mutex_unlock, status, != 0); + + if (arg.status != 0) { + fprintf(stderr, "thread failed: %s\n", strerror(arg.status)); + return EXIT_FAILURE; + } + + status = pthread_mutex_destroy(&arg.mutex); + ERROR_IF2(pthread_mutex_destroy, status, != 0); + + status = pthread_barrier_destroy(&arg.barrier); + ERROR_IF2(pthread_barrier_destroy, status, != 0); + + return EXIT_SUCCESS; +} diff --git a/tests/test_helpers.h b/tests/test_helpers.h index 5d7cd802a2372d970419c239a58a011481087cff..2f9ed80cbae1ff1c6d4a1b9ad220ecb2fe7ea3be 100644 --- a/tests/test_helpers.h +++ b/tests/test_helpers.h @@ -46,6 +46,15 @@ } \ } while(0) +#define ERROR_IF2(func, status, condition) \ + do { \ + if (status condition) { \ + fprintf(stderr, "%s:%s:%d: '%s' failed: %s (%d)\n", \ + __FILE__, __func__, __LINE__, #func, strerror(status), status); \ + _exit(EXIT_FAILURE); \ + } \ + } while(0) + // Throws errors on API return values not defined by the standards. // // Do not pass functions as the status or condition arguments, they might be