ATLAS Offline Software
CachedValue.icc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
3 */
4 /**
5  * @file CxxUtils/CachedValue.icc
6  * @author scott snyder <snyder@bnl.gov>
7  * @date Sep, 2017
8  * @brief Cached value with atomic update.
9  */
10 
11 
12 #include "CxxUtils/AthUnlikelyMacros.h"
13 
14 
15 namespace CxxUtils {
16 
17 
18 /**
19  * @brief Default constructor. Sets the value to invalid.
20  */
21 template <class T>
22 inline
23 CachedValue<T>::CachedValue()
24  : m_val (),
25  m_cacheValid (INVALID)
26 {
27 }
28 
29 
30 /**
31  * @brief Constructor from a value.
32  */
33 template <class T>
34 inline
35 CachedValue<T>::CachedValue (const T& val)
36  : m_val (val),
37  m_cacheValid (VALID)
38 {
39 }
40 
41 
42 /**
43  * @brief Move constructor from a value.
44  */
45 template <class T>
46 inline
47 CachedValue<T>::CachedValue (T&& val) noexcept
48  : m_val (std::move (val)),
49  m_cacheValid (VALID)
50 {
51 }
52 
53 
54 /**
55  * @brief Copy constructor.
56  */
57 template <class T>
58 inline
59 CachedValue<T>::CachedValue (const CachedValue& other)
60  : m_val(),
61  m_cacheValid (INVALID)
62 {
63  // If the source is in the middle of an update, set this value
64  // to invalid. Don't copy the value itself unless the source is valid.
65  if (other.m_cacheValid == VALID) {
66  m_cacheValid = VALID;
67  m_val = other.m_val;
68  }
69 }
70 
71 
72 /**
73  * @brief Move constructor.
74  * No concurrent operations on OTHER are possible.
75  */
76 template <class T>
77 inline
78 CachedValue<T>::CachedValue (CachedValue&& other) noexcept
79  : m_val(),
80  m_cacheValid (INVALID)
81 {
82  if (other.m_cacheValid == VALID) {
83  // If we get here, m_val should be properly initialized.
84  // But sometimes gcc12 has trouble proving that.
85  // Suppress a maybe-uninitialized warning here.
86 #if __GNUC__ >= 12
87 #pragma GCC diagnostic push
88 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
89 #endif
90  m_val = std::move (other.m_val);
91 #if __GNUC__ >= 12
92 #pragma GCC diagnostic pop
93 #endif
94  other.m_cacheValid = INVALID;
95  m_cacheValid = VALID;
96  }
97 }
98 
99 
100 /**
101  * @brief Copy constructor from other type.
102  */
103 template <class T>
104 template <class U>
105 inline
106 CachedValue<T>::CachedValue (const CachedValue<U>& other)
107  : m_cacheValid (INVALID)
108 {
109  // If the source is in the middle of an update, set this value
110  // to invalid. Don't copy the value itself unless the source is valid.
111  if (other.m_cacheValid == VALID) {
112  m_cacheValid = VALID;
113  m_val = other.m_val;
114  }
115  else {
116  m_val = T();
117  }
118 }
119 
120 
121 /**
122  * @brief Assignment.
123  */
124 template <class T>
125 inline
126 CachedValue<T>& CachedValue<T>::operator= (const CachedValue& other)
127 {
128  if (this != &other) {
129  if (other.m_cacheValid == VALID) {
130  m_cacheValid = VALID;
131  m_val = other.m_val;
132  }
133  else {
134  m_cacheValid = INVALID;
135  }
136  }
137  return *this;
138 }
139 
140 
141 /**
142  * @brief Move.
143  * No concurrent operations on OTHER are possible.
144  */
145 template <class T>
146 inline
147 CachedValue<T>& CachedValue<T>::operator= (CachedValue&& other) noexcept
148 {
149  if (this != &other) {
150  if (other.m_cacheValid == VALID) {
151  m_val = std::move (other.m_val);
152  other.m_cacheValid = INVALID;
153  m_cacheValid = VALID;
154  }
155  else {
156  m_cacheValid = INVALID;
157  }
158  }
159  return *this;
160 }
161 
162 
163 /// Helper for setting the value in an exception-safe manner.
164 struct CacheLock
165 {
166  /// If the cache is currently INVALID, then switch to UPDATING.
167  /// In that case, then this object will evaluate as true, and the
168  /// state will be changed to VALID when this object is destroyed.
169  /// Otherwise, this object will evaluate as false, and the cache state
170  /// will not be changed.
171  CacheLock (std::atomic<CacheState>& flag)
172  : m_flag (flag),
173  m_vflag (INVALID)
174  {
175  flag.compare_exchange_strong (m_vflag, UPDATING);
176  }
177  operator bool() const { return m_vflag == INVALID; }
178  ~CacheLock() { if (m_vflag == INVALID) m_flag = VALID; }
179 
180  std::atomic<CacheState>& m_flag;
181  CacheState m_vflag;
182 };
183 
184 
185 /**
186  * @brief Set the value, assuming it is currently invalid.
187  * Otherwise, this method will do nothing.
188  * The value will be valid once this returns.
189  */
190 template <class T>
191 inline
192 void CachedValue<T>::set (const T& val) const
193 {
194  // Interlocked assignment of the cached value,
195  // only if the current state is INVALID.
196  {
197  CacheLock lock (m_cacheValid);
198  if (lock) {
199  m_val = val;
200  }
201  else {
202  // Spin if another thread is currently updating.
203  while (m_cacheValid == UPDATING) {
204  CxxUtils::stall();
205  }
206  }
207  }
208 }
209 
210 
211 /**
212  * @brief Set the value by move, assuming it is currently invalid.
213  * Otherwise, this method will do nothing.
214  * The value will be valid once this returns.
215  */
216 template <class T>
217 inline
218 void CachedValue<T>::set (T&& val) const noexcept
219 {
220  // Interlocked assignment of the cached value,
221  // only if the current state is INVALID.
222  {
223  CacheLock lock (m_cacheValid);
224  if (lock) {
225  m_val = std::move (val);
226  }
227  else {
228  // Spin if another thread is currently updating.
229  while (m_cacheValid == UPDATING) {
230  CxxUtils::stall();
231  }
232  }
233  }
234 }
235 
236 
237 /**
238  * @brief Test to see if the value is valid.
239  * May spin if another thread is currently updating the value.
240  */
241 template <class T>
242 inline
243 bool CachedValue<T>::isValid() const
244 {
245  CacheState stat = m_cacheValid;
246  if (ATH_LIKELY(stat == VALID)) return true;
247  // Get the state flag.
248  // Spin if another thread is currently updating.
249  while ((stat = m_cacheValid) == UPDATING) {
250  CxxUtils::stall();
251  }
252  return stat == VALID;
253 }
254 
255 
256 /**
257  * @brief Return a pointer to the cached value.
258  * Should not be called unless isValid() has returned true or set()
259  * has returned.
260  */
261 template <class T>
262 inline
263 const T* CachedValue<T>::ptr() const
264 {
265  return &m_val;
266 }
267 
268 
269 /**
270  * @brief Store a new value to the value.
271  */
272 template <class T>
273 inline
274 void CachedValue<T>::store (const T& val)
275 {
276  m_cacheValid = VALID;
277  m_val = val;
278 }
279 
280 /**
281  * @brief Store a new value to the value, by move.
282  */
283 template <class T>
284 inline
285 void CachedValue<T>::store (T&& val) noexcept
286 {
287  m_cacheValid = VALID;
288  m_val = std::move (val);
289 }
290 
291 /**
292  * @brief Reset the value to invalid.
293  */
294 template <class T>
295 inline
296 void CachedValue<T>::reset()
297 {
298  m_cacheValid = INVALID;
299 }
300 
301 
302 } // namespace CxxUtils