CsLibGuarded  1.4.1
cs_lr_guarded.h
1 /***********************************************************************
2 *
3 * Copyright (c) 2016-2024 Ansel Sermersheim
4 *
5 * This file is part of CsLibGuarded.
6 *
7 * CsLibGuarded is free software, released under the BSD 2-Clause license.
8 * For license details refer to LICENSE provided with this project.
9 *
10 * CsLibGuarded is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 *
14 * https://opensource.org/licenses/BSD-2-Clause
15 *
16 ***********************************************************************/
17 
18 #ifndef CSLIBGUARDED_LR_GUARDED_H
19 #define CSLIBGUARDED_LR_GUARDED_H
20 
21 #include <atomic>
22 #include <memory>
23 #include <mutex>
24 #include <thread>
25 
26 namespace libguarded
27 {
28 
29 // 21 comment line(s) omitted
50 template <typename T, typename Mutex = std::mutex>
51 class lr_guarded
52 {
53  private:
54  class shared_deleter;
55 
56  public:
57  using shared_handle = std::unique_ptr<const T, shared_deleter>;
58 
59  // 4 comment line(s) omitted
63  template <typename... Us>
64  lr_guarded(Us &&... data);
65 
66  // 12 comment line(s) omitted
78  template <typename Func>
79  void modify(Func && f);
80 
81  // 3 comment line(s) omitted
84  [[nodiscard]] shared_handle lock_shared() const;
85 
86  // 3 comment line(s) omitted
89  [[nodiscard]] shared_handle try_lock_shared() const;
90 
91  // 3 comment line(s) omitted
94  template <class Duration>
95  [[nodiscard]] shared_handle try_lock_shared_for(const Duration & duration) const;
96 
97  // 3 comment line(s) omitted
100  template <class TimePoint>
101  [[nodiscard]] shared_handle try_lock_shared_until(const TimePoint & timepoint) const;
102 
103  private:
104  class shared_deleter
105  {
106  public:
107  using pointer = const T *;
108 
109  shared_deleter() : m_readingCount(nullptr) {}
110 
111  shared_deleter(const shared_deleter &) = delete;
112  shared_deleter& operator=(const shared_deleter&) = delete;
113 
114  shared_deleter(shared_deleter && other)
115  : m_readingCount(other.m_readingCount)
116  {
117  other.m_readingCount = nullptr;
118  }
119 
120  shared_deleter& operator=(shared_deleter&& other) & {
121  m_readingCount = other.m_readingCount;
122  other.m_readingCount = nullptr;
123  }
124 
125  shared_deleter(std::atomic<int> & readingCount)
126  : m_readingCount(&readingCount)
127  {
128  }
129 
130  void operator()(const T * ptr) {
131  if (ptr && m_readingCount) {
132  (*m_readingCount)--;
133  }
134  }
135 
136  private:
137  std::atomic<int> * m_readingCount;
138  };
139 
140  T m_left;
141  T m_right;
142  std::atomic<bool> m_readingLeft;
143  std::atomic<bool> m_countingLeft;
144  mutable std::atomic<int> m_leftReadCount;
145  mutable std::atomic<int> m_rightReadCount;
146  mutable Mutex m_writeMutex;
147 };
148 
149 template <typename T, typename M>
150 template <typename... Us>
151 lr_guarded<T, M>::lr_guarded(Us &&... data)
152  : m_left(std::forward<Us>(data)...), m_right(m_left), m_readingLeft(true), m_countingLeft(true),
153  m_leftReadCount(0), m_rightReadCount(0)
154 {
155 }
156 
157 template <typename T, typename M>
158 template <typename Func>
159 void lr_guarded<T, M>::modify(Func && func)
160 {
161  // consider looser memory ordering
162 
163  std::lock_guard<M> lock(m_writeMutex);
164 
165  T *firstWriteLocation;
166  T *secondWriteLocation;
167 
168  bool local_readingLeft = m_readingLeft.load();
169 
170  if (local_readingLeft) {
171  firstWriteLocation = &m_right;
172  secondWriteLocation = &m_left;
173  } else {
174  firstWriteLocation = &m_left;
175  secondWriteLocation = &m_right;
176  }
177 
178  try {
179  func(*firstWriteLocation);
180  } catch (...) {
181  *firstWriteLocation = *secondWriteLocation;
182  throw;
183  }
184 
185  m_readingLeft.store(! local_readingLeft);
186 
187  bool local_countingLeft = m_countingLeft.load();
188 
189  if (local_countingLeft) {
190  while (m_rightReadCount.load() != 0) {
191  std::this_thread::yield();
192  }
193 
194  } else {
195  while (m_leftReadCount.load() != 0) {
196  std::this_thread::yield();
197  }
198  }
199 
200  m_countingLeft.store(!local_countingLeft);
201 
202  if (local_countingLeft) {
203  while (m_leftReadCount.load() != 0) {
204  std::this_thread::yield();
205  }
206 
207  } else {
208  while (m_rightReadCount.load() != 0) {
209  std::this_thread::yield();
210  }
211  }
212 
213  try {
214  func(*secondWriteLocation);
215  } catch (...) {
216  *secondWriteLocation = *firstWriteLocation;
217  throw;
218  }
219 }
220 
221 template <typename T, typename M>
222 auto lr_guarded<T, M>::lock_shared() const -> shared_handle
223 {
224  if (m_countingLeft) {
225  ++m_leftReadCount;
226 
227  if (m_readingLeft) {
228  return shared_handle(&m_left, shared_deleter(m_leftReadCount));
229  } else {
230  return shared_handle(&m_right, shared_deleter(m_leftReadCount));
231  }
232 
233  } else {
234  ++m_rightReadCount;
235 
236  if (m_readingLeft) {
237  return shared_handle(&m_left, shared_deleter(m_rightReadCount));
238  } else {
239  return shared_handle(&m_right, shared_deleter(m_rightReadCount));
240  }
241  }
242 }
243 
244 template <typename T, typename M>
245 auto lr_guarded<T, M>::try_lock_shared() const -> shared_handle
246 {
247  return lock_shared();
248 }
249 
250 template <typename T, typename M>
251 template <typename Duration>
252 auto lr_guarded<T, M>::try_lock_shared_for(const Duration &) const -> shared_handle
253 {
254  return lock_shared();
255 }
256 
257 template <typename T, typename M>
258 template <typename TimePoint>
259 auto lr_guarded<T, M>::try_lock_shared_until(const TimePoint &) const -> shared_handle
260 {
261  return lock_shared();
262 }
263 
264 } // namespace libguarded
265 
266 #endif