-
Notifications
You must be signed in to change notification settings - Fork 192
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
Remove lazy_static dependancy #52
Changes from all commits
5d3482f
8566c39
442fb43
c199187
b44549e
925cf56
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 |
---|---|---|
|
@@ -6,10 +6,26 @@ | |
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
use core::sync::atomic::{AtomicUsize, Ordering}; | ||
use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; | ||
|
||
// This structure represents a laziliy initialized static usize value. Useful | ||
// when it is perferable to just rerun initialization instead of locking. | ||
// Both unsync_init and sync_init will invoke an init() function until it | ||
// succeeds, then return the cached value for future calls. | ||
// | ||
// Both methods support init() "failing". If the init() method returns UNINIT, | ||
// that value will be returned as normal, but will not be cached. | ||
// | ||
// Users should only depend on the _value_ returned by init() functions. | ||
// Specifically, for the following init() function: | ||
// fn init() -> usize { | ||
// a(); | ||
// let v = b(); | ||
// c(); | ||
// v | ||
// } | ||
// the effects of c() or writes to shared memory will not necessarily be | ||
// observed and additional syncronization methods with be needed. | ||
pub struct LazyUsize(AtomicUsize); | ||
|
||
impl LazyUsize { | ||
|
@@ -19,20 +35,44 @@ impl LazyUsize { | |
|
||
// The initialization is not completed. | ||
pub const UNINIT: usize = usize::max_value(); | ||
// The initialization is currently running. | ||
pub const ACTIVE: usize = usize::max_value() - 1; | ||
|
||
// Runs the init() function at least once, returning the value of some run | ||
// of init(). Unlike std::sync::Once, the init() function may be run | ||
// multiple times. If init() returns UNINIT, future calls to unsync_init() | ||
// will always retry. This makes UNINIT ideal for representing failure. | ||
// of init(). Multiple callers can run their init() functions in parallel. | ||
// init() should always return the same value, if it succeeds. | ||
pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { | ||
// Relaxed ordering is fine, as we only have a single atomic variable. | ||
let mut val = self.0.load(Ordering::Relaxed); | ||
let mut val = self.0.load(Relaxed); | ||
if val == Self::UNINIT { | ||
val = init(); | ||
self.0.store(val, Ordering::Relaxed); | ||
self.0.store(val, Relaxed); | ||
} | ||
val | ||
} | ||
|
||
// Synchronously runs the init() function. Only one caller will have their | ||
// init() function running at a time, and exactly one successful call will | ||
// be run. The init() function should never return LazyUsize::ACTIVE. | ||
pub fn sync_init(&self, init: impl FnOnce() -> usize, mut wait: impl FnMut()) -> usize { | ||
// Common and fast path with no contention. Don't wast time on CAS. | ||
match self.0.load(Relaxed) { | ||
Self::UNINIT | Self::ACTIVE => {} | ||
val => return val, | ||
} | ||
// Relaxed ordering is fine, as we only have a single atomic variable. | ||
loop { | ||
match self.0.compare_and_swap(Self::UNINIT, Self::ACTIVE, Relaxed) { | ||
Self::UNINIT => { | ||
let val = init(); | ||
self.0.store(val, Relaxed); | ||
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. IMO it would be a good idea to 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. Oh, seems like the function is actually allowed to return
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.
This is called out, just in the docs for // Both methods support init() "failing". If the init() method returns UNINIT,
// that value will be returned as normal, but will not be cached.
Good idea. Thanks @RalfJung for taking a look at this stuff. 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. I think it may be better to write something like this:
And describe this behavior in docs. |
||
return val; | ||
} | ||
Self::ACTIVE => wait(), | ||
val => return val, | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Identical to LazyUsize except with bool instead of usize. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,3 +36,29 @@ impl Weak { | |
NonNull::new(addr as *mut _) | ||
} | ||
} | ||
|
||
pub struct LazyFd(LazyUsize); | ||
|
||
impl LazyFd { | ||
pub const fn new() -> Self { | ||
Self(LazyUsize::new()) | ||
} | ||
|
||
// If init() returns Some(x), x should be nonnegative. | ||
pub fn init(&self, init: impl FnOnce() -> Option<libc::c_int>) -> Option<libc::c_int> { | ||
let fd = self.0.sync_init( | ||
|| match init() { | ||
// OK as val >= 0 and val <= c_int::MAX < usize::MAX | ||
Some(val) => val as usize, | ||
None => LazyUsize::UNINIT, | ||
}, | ||
|| unsafe { | ||
libc::usleep(1000); | ||
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. Wow that's a long sleep, isn't it? A comment seems always warranted with such a magic number. 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. Ya this sleep should probably be shorter, any recommendations? My guess is that we would want it to be longer than the normal latency for an 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. On my laptop 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. I'm sorry this is outside my range of experience.^^ 1ms just seemed like a long time. 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. It can be a huge latency if system entropy pool has not initialized, IIRC up to several minutes. 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. Sure, but that's not the common case. Ideally |
||
}, | ||
); | ||
match fd { | ||
LazyUsize::UNINIT => None, | ||
val => Some(val as libc::c_int), | ||
} | ||
} | ||
} |
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.
This assumes that a file descriptor consists only of that integer and no other user-space data. I suppose that is accurate on Unixes?
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.
Yup this is correct,
libc::open
always returns a positive int (link is to the Linux docs, but the function is a POSIX standard).So a file descriptor will always fit in a positive int on unix