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