1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
use std::sync::atomic::{AtomicBool, Ordering}; pub struct Init { init_started: AtomicBool, init_done: AtomicBool } impl Init { const_if_enabled! { pub fn new() -> Init { Init { init_started: AtomicBool::new(false), init_done: AtomicBool::new(false) } } } /// Returns true if initialization has completed without blocking. If this /// function returns false, it may be the case that initialization is /// currently in progress. If this function returns `true`, intialization is /// guaranteed to be completed. #[inline(always)] pub fn has_completed(&self) -> bool { self.init_done.load(Ordering::Acquire) } /// Mark this initialization as complete, unblocking all threads that may be /// waiting. #[inline(always)] pub fn mark_complete(&self) { // If this is being called from outside of a `needed` block, we need to // ensure that initialization is marked as started to avoid racing with // future `needed` calls. if !self.init_started.load(Ordering::Relaxed) { self.init_started.store(true, Ordering::SeqCst); } self.init_done.store(true, Ordering::SeqCst); } #[cold] #[inline(always)] fn try_to_need_init(&self) -> bool { // Quickly check if initialization has already started elsewhere. if self.init_started.load(Ordering::Relaxed) { // If it has, wait until it's finished before returning. Finishing // is marked by calling `mark_complete`. while !self.init_done.load(Ordering::Acquire) { } return false; } // Try to be the first. If we lose (init_started is true), we wait. if self.init_started.compare_and_swap(false, true, Ordering::AcqRel) { // Another compare_and_swap won. Wait until they're done. while !self.init_done.load(Ordering::Acquire) { } return false; } true } // Returns `true` if the caller needs to be be initialized. `false` // otherwise. This function returns true to exactly one thread. If this // function is called from multiple threads simulatenously, then the call // blocks until `true` is returned to one thread. All other threads receive // `false`. // // Blocking ends when the `mark_complete` function is called. That function // _must_ be called by the thread that received `true` as a return value. #[inline(always)] pub fn needed(&self) -> bool { // Quickly check if initialization has finished, and return if so. if self.init_done.load(Ordering::Relaxed) { return false; } // We call a different function to attempt the intialiaztion to use // Rust's `cold` attribute to try let LLVM know that this is unlikely. self.try_to_need_init() } }