-
Notifications
You must be signed in to change notification settings - Fork 3
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
Fix unsoundness #6
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,111 +25,54 @@ | |
//! ``` | ||
//! | ||
|
||
use std::cell::Cell; | ||
use std::future::Future; | ||
use std::pin::Pin; | ||
use std::ptr::null; | ||
use std::sync::Arc; | ||
use std::sync::Mutex; | ||
use std::task::Context; | ||
use std::task::Poll; | ||
use std::task::Wake; | ||
use std::task::Waker; | ||
|
||
type Fut<T> = Mutex<Result<T, Pin<Box<dyn Future<Output = T>>>>>; | ||
pub struct AsyncOnce<T: 'static> { | ||
ptr: Cell<*const T>, | ||
fut: Fut<T>, | ||
waker: Arc<MyWaker>, | ||
fut: parking_lot::Mutex<Pin<Box<dyn Future<Output = T> + Send + Sync>>>, | ||
value: once_cell::sync::OnceCell<T>, | ||
} | ||
|
||
unsafe impl<T: 'static> Sync for AsyncOnce<T> {} | ||
|
||
impl<T> AsyncOnce<T> { | ||
pub fn new<F>(fut: F) -> AsyncOnce<T> | ||
where | ||
F: Future<Output = T> + 'static, | ||
F: Future<Output = T> + Send + Sync + 'static, | ||
{ | ||
AsyncOnce { | ||
ptr: Cell::new(null()), | ||
fut: Mutex::new(Err(Box::pin(fut))), | ||
waker: Arc::new(MyWaker { | ||
wakers: Mutex::new(Vec::with_capacity(16)), | ||
}), | ||
Self { | ||
fut: parking_lot::Mutex::new(Box::pin(fut)), | ||
value: once_cell::sync::OnceCell::new(), | ||
} | ||
} | ||
#[inline(always)] | ||
pub fn get(&'static self) -> &'static Self { | ||
self | ||
} | ||
} | ||
|
||
struct MyWaker { | ||
wakers: Mutex<Vec<Waker>>, | ||
} | ||
|
||
impl Wake for MyWaker { | ||
fn wake_by_ref(self: &std::sync::Arc<Self>) { | ||
self.clone().wake(); | ||
} | ||
|
||
fn wake(self: std::sync::Arc<Self>) { | ||
let mut wakers = self.wakers.lock().unwrap(); | ||
while let Some(waker) = wakers.pop() { | ||
waker.wake(); | ||
} | ||
drop(wakers); | ||
fn set_value(&'static self, value: T) -> &'static T { | ||
self | ||
.value | ||
.try_insert(value) | ||
.map_err(|_| ()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why mapping the error to a unit and not just expecting/unwrapping the original error? Just curious. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error in this case is the |
||
.expect("The value was already set before") | ||
} | ||
} | ||
|
||
impl<T> Future for &'static AsyncOnce<T> { | ||
impl<T: Send + Sync + 'static> Future for &'static AsyncOnce<T> { | ||
type Output = &'static T; | ||
|
||
#[inline(always)] | ||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<&'static T> { | ||
if let Some(ptr) = unsafe { self.ptr.get().as_ref() } { | ||
return Poll::Ready(ptr); | ||
if let Some(value) = self.value.get() { | ||
return Poll::Ready(value); | ||
} | ||
let cxwaker = cx.waker().clone(); | ||
let mut wakers = self.waker.wakers.lock().unwrap(); | ||
let is_first = wakers.is_empty(); | ||
if !wakers.iter().any(|wk| wk.will_wake(&cxwaker)) { | ||
wakers.push(cxwaker); | ||
} | ||
drop(wakers); | ||
let mut result = None; | ||
let mut fut = self.fut.lock().unwrap(); | ||
match (is_first, fut.as_mut()) { | ||
(true, Err(fut)) => { | ||
let waker = Waker::from(self.waker.clone()); | ||
let mut ctx = Context::from_waker(&waker); | ||
match Pin::new(fut).poll(&mut ctx) { | ||
Poll::Ready(res) => { | ||
result = Some(res); | ||
} | ||
Poll::Pending => { | ||
return Poll::Pending; | ||
} | ||
} | ||
} | ||
(true, Ok(res)) => { | ||
return Poll::Ready(unsafe { (res as *const T).as_ref().unwrap() }); | ||
} | ||
_ => (), | ||
} | ||
if let Some(res) = result { | ||
*fut = Ok(res); | ||
let ptr = fut.as_ref().ok().unwrap() as *const T; | ||
self.ptr.set(ptr); | ||
drop(fut); | ||
let mut wakers = self.waker.wakers.lock().unwrap(); | ||
while let Some(waker) = wakers.pop() { | ||
waker.wake(); | ||
} | ||
drop(wakers); | ||
return Poll::Ready(unsafe { &*ptr }); | ||
|
||
let mut fut = self.fut.lock(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just noticed, that a second |
||
match Pin::new(&mut *fut).poll(cx) { | ||
Poll::Ready(value) => Poll::Ready((&**self).set_value(value)), | ||
Poll::Pending => Poll::Pending, | ||
} | ||
drop(fut); | ||
Poll::Pending | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point in time,
parking_lot
is no longer recommended over thestd
Mutex
, sincestd
now usesfutex
on Linux, which performs better in most cases.