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