protobuf/
lazy.rs

1//! Lazily initialized data.
2//! Used in generated code.
3
4// Avoid deprecation warnings when compiling rust-protobuf
5#![allow(deprecated)]
6
7use std::mem;
8use std::sync;
9
10/// Lasily initialized data.
11#[deprecated(
12    since = "2.16",
13    note = "Please regenerate .rs files from .proto files to use newer APIs"
14)]
15pub struct Lazy<T> {
16    #[doc(hidden)]
17    pub lock: sync::Once,
18    #[doc(hidden)]
19    pub ptr: *const T,
20}
21
22impl<T> Lazy<T> {
23    /// Uninitialized `Lazy` object.
24    ///
25    /// The initializer is added in rust-protobuf 2.11, for compatibility with
26    /// previously generated code, existing fields are kept public.
27    pub const INIT: Lazy<T> = Lazy {
28        lock: sync::Once::new(),
29        ptr: 0 as *const T,
30    };
31
32    /// Get lazy field value, initialize it with given function if not yet.
33    pub fn get<F>(&'static mut self, init: F) -> &'static T
34    where
35        F: FnOnce() -> T,
36    {
37        // ~ decouple the lifetimes of 'self' and 'self.lock' such we
38        // can initialize self.ptr in the call_once closure (note: we
39        // do have to initialize self.ptr in the closure to guarantee
40        // the ptr is valid for all calling threads at any point in
41        // time)
42        let lock: &sync::Once = unsafe { mem::transmute(&self.lock) };
43        lock.call_once(|| unsafe {
44            self.ptr = mem::transmute(Box::new(init()));
45        });
46        unsafe { &*self.ptr }
47    }
48}
49
50/// Used to initialize `lock` field in `Lazy` struct.
51#[deprecated(
52    since = "2.11",
53    note = "Regenerate .proto files to use safer initializer"
54)]
55pub const ONCE_INIT: sync::Once = sync::Once::new();
56
57#[cfg(test)]
58mod test {
59    use std::sync::atomic::AtomicIsize;
60    use std::sync::atomic::Ordering;
61    use std::sync::Arc;
62    use std::sync::Barrier;
63    use std::thread;
64
65    use super::Lazy;
66
67    #[test]
68    fn many_threads_calling_get() {
69        const N_THREADS: usize = 32;
70        const N_ITERS_IN_THREAD: usize = 32;
71        const N_ITERS: usize = 16;
72
73        static mut LAZY: Lazy<String> = Lazy::INIT;
74        static CALL_COUNT: AtomicIsize = AtomicIsize::new(0);
75
76        let value = "Hello, world!".to_owned();
77
78        for _ in 0..N_ITERS {
79            // Reset mutable state.
80            unsafe {
81                LAZY = Lazy::INIT;
82            }
83            CALL_COUNT.store(0, Ordering::SeqCst);
84
85            // Create a bunch of threads, all calling .get() at the same time.
86            let mut threads = vec![];
87            let barrier = Arc::new(Barrier::new(N_THREADS));
88
89            for _ in 0..N_THREADS {
90                let cloned_value_thread = value.clone();
91                let cloned_barrier = barrier.clone();
92                threads.push(thread::spawn(move || {
93                    // Ensure all threads start at once to maximise contention.
94                    cloned_barrier.wait();
95                    for _ in 0..N_ITERS_IN_THREAD {
96                        assert_eq!(&cloned_value_thread, unsafe {
97                            LAZY.get(|| {
98                                CALL_COUNT.fetch_add(1, Ordering::SeqCst);
99                                cloned_value_thread.clone()
100                            })
101                        });
102                    }
103                }));
104            }
105
106            for thread in threads {
107                thread.join().unwrap();
108            }
109
110            assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1);
111        }
112    }
113}