cadence_macros/
state.rs

1// Cadence - An extensible Statsd client for Rust!
2//
3// Copyright 2020-2021 Nick Pillitteri
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11use cadence::StatsdClient;
12use std::cell::UnsafeCell;
13use std::error::Error;
14use std::fmt::{self, Display, Formatter};
15use std::sync::atomic::{AtomicUsize, Ordering};
16use std::sync::Arc;
17
18const UNSET: usize = 0;
19const LOADING: usize = 1;
20const COMPLETE: usize = 2;
21
22/// Global default StatsdClient to be used by macros
23static HOLDER: SingletonHolder<StatsdClient> = SingletonHolder::new();
24
25/// Holder to allow global reads of a value from multiple threads while
26/// allowing the value to be written (set) a single time.
27///
28/// This type is public to allow it to be used in integration tests for
29/// this crate but it is not part of the public API and may change at any
30/// time.
31#[doc(hidden)]
32#[derive(Debug, Default)]
33pub struct SingletonHolder<T> {
34    value: UnsafeCell<Option<Arc<T>>>,
35    state: AtomicUsize,
36}
37
38impl<T> SingletonHolder<T> {
39    /// Create a new empty holder
40    pub const fn new() -> Self {
41        SingletonHolder {
42            value: UnsafeCell::new(None),
43            state: AtomicUsize::new(UNSET),
44        }
45    }
46}
47
48impl<T> SingletonHolder<T> {
49    /// Get a pointer to the contained value if set, None otherwise
50    pub fn get(&self) -> Option<Arc<T>> {
51        if !self.is_set() {
52            return None;
53        }
54
55        // SAFETY: We've ensured that the state is "complete" and the
56        // set method has completed and set a value for the UnsafeCell.
57        unsafe { &*self.value.get() }.clone()
58    }
59
60    pub fn is_set(&self) -> bool {
61        COMPLETE == self.state.load(Ordering::Acquire)
62    }
63
64    /// Set the value if it has not already been set, otherwise this is a no-op
65    pub fn set(&self, val: T) {
66        if self
67            .state
68            .compare_exchange(UNSET, LOADING, Ordering::AcqRel, Ordering::Relaxed)
69            .is_err()
70        {
71            return;
72        }
73
74        // SAFETY: There are no readers at this point since we've guaranteed the
75        // state could not have been "complete". There are no other writers since
76        // we've ensured that the state was previously "unset" and we've been able
77        // to compare-and-swap it to "loading".
78        let ptr = self.value.get();
79        unsafe {
80            *ptr = Some(Arc::new(val));
81        }
82
83        self.state.store(COMPLETE, Ordering::Release);
84    }
85}
86
87unsafe impl<T: Send> Send for SingletonHolder<T> {}
88
89unsafe impl<T: Sync> Sync for SingletonHolder<T> {}
90
91/// Error indicating that a global default `StatsdClient` was not set
92/// when a call to `get_global_default` was made.
93#[derive(Debug)]
94pub struct GlobalDefaultNotSet;
95
96impl Display for GlobalDefaultNotSet {
97    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
98        Display::fmt("global default StatsdClient instance not set", f)
99    }
100}
101
102impl Error for GlobalDefaultNotSet {}
103
104/// Set the global default `StatsdClient` instance
105///
106/// If the global default client has already been set, this method does nothing.
107///
108/// # Example
109///
110/// ```
111/// use cadence::{StatsdClient, NopMetricSink};
112/// let client = StatsdClient::from_sink("my.prefix", NopMetricSink);
113///
114/// cadence_macros::set_global_default(client);
115/// ```
116pub fn set_global_default(client: StatsdClient) {
117    HOLDER.set(client);
118}
119
120/// Get a reference to the global default `StatsdClient` instance
121///
122/// # Errors
123///
124/// This method will return an error if the global default has not been
125/// previously set via the `set_global_default` method.
126///
127/// # Example
128///
129/// ```
130/// use cadence::{StatsdClient, NopMetricSink};
131///
132/// let global_client = cadence_macros::get_global_default();
133/// assert!(global_client.is_err());
134///
135/// let client = StatsdClient::from_sink("my.prefix", NopMetricSink);
136/// cadence_macros::set_global_default(client);
137///
138/// let global_client = cadence_macros::get_global_default();
139/// assert!(global_client.is_ok());
140/// ```
141pub fn get_global_default() -> Result<Arc<StatsdClient>, GlobalDefaultNotSet> {
142    HOLDER.get().ok_or(GlobalDefaultNotSet)
143}
144
145/// Return true if the global default `StatsdClient` is set, false otherwise
146///
147/// # Example
148///
149/// ```
150/// use cadence::{StatsdClient, NopMetricSink};
151///
152/// assert!(!cadence_macros::is_global_default_set());
153///
154/// let client = StatsdClient::from_sink("my.prefix", NopMetricSink);
155/// cadence_macros::set_global_default(client);
156///
157/// assert!(cadence_macros::is_global_default_set());
158/// ```
159pub fn is_global_default_set() -> bool {
160    HOLDER.is_set()
161}