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}