| //! Lazily initialized data. |
| //! Used in generated code. |
| |
| // Avoid deprecation warnings when compiling rust-protobuf |
| #![allow(deprecated)] |
| |
| use std::mem; |
| use std::sync; |
| |
| /// Lasily initialized data. |
| #[deprecated( |
| since = "2.16", |
| note = "Please regenerate .rs files from .proto files to use newer APIs" |
| )] |
| pub struct Lazy<T> { |
| #[doc(hidden)] |
| pub lock: sync::Once, |
| #[doc(hidden)] |
| pub ptr: *const T, |
| } |
| |
| impl<T> Lazy<T> { |
| /// Uninitialized `Lazy` object. |
| /// |
| /// The initializer is added in rust-protobuf 2.11, for compatibility with |
| /// previously generated code, existing fields are kept public. |
| pub const INIT: Lazy<T> = Lazy { |
| lock: sync::Once::new(), |
| ptr: 0 as *const T, |
| }; |
| |
| /// Get lazy field value, initialize it with given function if not yet. |
| pub fn get<F>(&'static mut self, init: F) -> &'static T |
| where |
| F: FnOnce() -> T, |
| { |
| // ~ decouple the lifetimes of 'self' and 'self.lock' such we |
| // can initialize self.ptr in the call_once closure (note: we |
| // do have to initialize self.ptr in the closure to guarantee |
| // the ptr is valid for all calling threads at any point in |
| // time) |
| let lock: &sync::Once = unsafe { mem::transmute(&self.lock) }; |
| lock.call_once(|| unsafe { |
| self.ptr = mem::transmute(Box::new(init())); |
| }); |
| unsafe { &*self.ptr } |
| } |
| } |
| |
| /// Used to initialize `lock` field in `Lazy` struct. |
| #[deprecated( |
| since = "2.11", |
| note = "Regenerate .proto files to use safer initializer" |
| )] |
| pub const ONCE_INIT: sync::Once = sync::Once::new(); |
| |
| #[cfg(test)] |
| mod test { |
| use super::Lazy; |
| use std::sync::atomic::AtomicIsize; |
| use std::sync::atomic::Ordering; |
| use std::sync::Arc; |
| use std::sync::Barrier; |
| use std::thread; |
| |
| #[test] |
| fn many_threads_calling_get() { |
| const N_THREADS: usize = 32; |
| const N_ITERS_IN_THREAD: usize = 32; |
| const N_ITERS: usize = 16; |
| |
| static mut LAZY: Lazy<String> = Lazy::INIT; |
| static CALL_COUNT: AtomicIsize = AtomicIsize::new(0); |
| |
| let value = "Hello, world!".to_owned(); |
| |
| for _ in 0..N_ITERS { |
| // Reset mutable state. |
| unsafe { |
| LAZY = Lazy::INIT; |
| } |
| CALL_COUNT.store(0, Ordering::SeqCst); |
| |
| // Create a bunch of threads, all calling .get() at the same time. |
| let mut threads = vec![]; |
| let barrier = Arc::new(Barrier::new(N_THREADS)); |
| |
| for _ in 0..N_THREADS { |
| let cloned_value_thread = value.clone(); |
| let cloned_barrier = barrier.clone(); |
| threads.push(thread::spawn(move || { |
| // Ensure all threads start at once to maximise contention. |
| cloned_barrier.wait(); |
| for _ in 0..N_ITERS_IN_THREAD { |
| assert_eq!(&cloned_value_thread, unsafe { |
| LAZY.get(|| { |
| CALL_COUNT.fetch_add(1, Ordering::SeqCst); |
| cloned_value_thread.clone() |
| }) |
| }); |
| } |
| })); |
| } |
| |
| for thread in threads { |
| thread.join().unwrap(); |
| } |
| |
| assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1); |
| } |
| } |
| } |