CsLibGuarded  1.4.1
cs_deferred_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_DEFERRED_GUARDED_H
19 #define CSLIBGUARDED_DEFERRED_GUARDED_H
20 
21 #include "cs_plain_guarded.h"
22 
23 #include <atomic>
24 #include <future>
25 #include <memory>
26 #include <vector>
27 #include <shared_mutex>
28 
29 namespace libguarded
30 {
31 
32 template <class T>
33 typename std::add_lvalue_reference<T>::type declref();
34 
35 // 13 comment line(s) omitted
48 template <typename T, typename M = std::shared_timed_mutex>
49 class deferred_guarded
50 {
51  private:
52  class shared_deleter;
53 
54  public:
55  using shared_handle = std::unique_ptr<const T, shared_deleter>;
56 
57  template <typename... Us>
58  deferred_guarded(Us &&... data);
59 
60  template <typename Func>
61  void modify_detach(Func && func);
62 
63  template <typename Func>
64  [[nodiscard]] auto modify_async(Func && func) ->
65  typename std::future<decltype(std::declval<Func>()(declref<T>()))>;
66 
67  [[nodiscard]] shared_handle lock_shared() const;
68  [[nodiscard]] shared_handle try_lock_shared() const;
69 
70  template <class Duration>
71  [[nodiscard]] shared_handle try_lock_shared_for(const Duration & duration) const;
72 
73  template <class TimePoint>
74  [[nodiscard]] shared_handle try_lock_shared_until(const TimePoint & timepoint) const;
75 
76  private:
77  class shared_deleter
78  {
79  public:
80  using pointer = const T *;
81 
82  shared_deleter(M & mutex)
83  : m_deleter_mutex(mutex)
84  {
85  }
86 
87  void operator()(const T * ptr)
88  {
89  if (ptr) {
90  m_deleter_mutex.unlock_shared();
91  }
92  }
93 
94  private:
95  M & m_deleter_mutex;
96  };
97 
98  void do_pending_writes() const;
99 
100  mutable T m_obj;
101  mutable M m_mutex;
102 
103  mutable std::atomic<bool> m_pendingWrites;
104 
105  mutable plain_guarded<std::vector<std::packaged_task<void(T &)>>> m_pendingList;
106 };
107 
108 template <typename T, typename M>
109 template <typename... Us>
110 deferred_guarded<T, M>::deferred_guarded(Us &&... data)
111  : m_obj(std::forward<Us>(data)...), m_pendingWrites(false)
112 {
113 }
114 
115 template <typename T, typename M>
116 template <typename Func>
117 void deferred_guarded<T, M>::modify_detach(Func && func)
118 {
119  std::unique_lock<M> lock(m_mutex, std::try_to_lock);
120 
121  if (lock.owns_lock()) {
122  // consider looser memory ordering
123  if (m_pendingWrites.load()) {
124  std::vector<std::packaged_task<void(T &)>> localPending;
125  m_pendingWrites.store(false);
126  swap(localPending, *(m_pendingList.lock()));
127 
128  for (auto & f : localPending) {
129  f(m_obj);
130  }
131  }
132 
133  func(m_obj);
134 
135  } else {
136  m_pendingList.lock()->push_back(std::packaged_task<void(T &)>(std::forward<Func>(func)));
137  m_pendingWrites.store(true);
138  }
139 }
140 
141 template <typename Ret, typename Func, typename T>
142 auto call_returning_future(Func & func, T & data) ->
143  typename std::enable_if<!std::is_same<Ret, void>::value, std::future<Ret>>::type
144 {
145  std::promise<Ret> promise;
146 
147  try {
148  promise.set_value(func(data));
149  } catch (...) {
150  promise.set_exception(std::current_exception());
151  }
152 
153  return promise.get_future();
154 }
155 
156 template <typename Ret, typename Func, typename T>
157 auto call_returning_future(Func & func, T & data) ->
158  typename std::enable_if<std::is_same<Ret, void>::value, std::future<Ret>>::type
159 {
160  std::promise<Ret> promise;
161 
162  try {
163  func(data);
164  promise.set_value();
165  } catch (...) {
166  promise.set_exception(std::current_exception());
167  }
168 
169  return promise.get_future();
170 }
171 
172 template <typename Ret, typename T, typename Func>
173 auto package_task_void(Func && func) ->
174  typename std::enable_if<std::is_same<Ret, void>::value,
175  std::pair<std::packaged_task<void(T &)>, std::future<void>>>::type
176 {
177  std::packaged_task<void(T &)> task(std::forward<Func>(func));
178  std::future<void> task_future(task.get_future());
179 
180  return std::make_pair(std::move(task), std::move(task_future));
181 }
182 
183 template <typename Ret, typename T, typename Func>
184 auto package_task_void(Func && func) ->
185  typename std::enable_if<!std::is_same<Ret, void>::value,
186  std::pair<std::packaged_task<void(T &)>, std::future<T>>>::type
187 {
188  std::packaged_task<Ret(T &)> task(std::forward<Func>(func));
189  std::future<Ret> task_future(task.get_future());
190 
191  return std::make_pair(std::packaged_task<void(T &)>(std::move(task)), std::move(task_future));
192 }
193 
194 template <typename T, typename M>
195 template <typename Func>
196 auto deferred_guarded<T, M>::modify_async(Func && func) ->
197  typename std::future<decltype(std::declval<Func>()(declref<T>()))>
198 {
199  using return_t = decltype(func(m_obj));
200  using future_t = std::future<decltype(func(m_obj))>;
201  future_t retval;
202 
203  std::unique_lock<M> lock(m_mutex, std::try_to_lock);
204 
205  if (lock.owns_lock()) {
206  if (m_pendingWrites.load()) {
207  std::vector<std::packaged_task<void(T &)>> localPending;
208 
209  m_pendingWrites.store(false);
210  swap(localPending, *(m_pendingList.lock()));
211  for (auto & f : localPending) {
212  f(m_obj);
213  }
214  }
215 
216  retval = call_returning_future<return_t>(func, m_obj);
217 
218  } else {
219  std::pair<std::packaged_task<void(T &)>, std::future<return_t>> task_future =
220  package_task_void<return_t, T>(std::forward<Func>(func));
221 
222  retval = std::move(task_future.second);
223 
224  m_pendingList.lock()->push_back(std::move(task_future.first));
225  m_pendingWrites.store(true);
226  }
227 
228  return retval;
229 }
230 
231 template <typename T, typename M>
232 void deferred_guarded<T, M>::do_pending_writes() const
233 {
234  if (m_pendingWrites.load()) {
235 
236  std::unique_lock<M> lock(m_mutex, std::try_to_lock);
237 
238  if (lock.owns_lock()) {
239  if (m_pendingWrites.load()) {
240  std::vector<std::packaged_task<void(T &)>> localPending;
241 
242  m_pendingWrites.store(false);
243  swap(localPending, *(m_pendingList.lock()));
244 
245  for (auto & f : localPending) {
246  f(m_obj);
247  }
248  }
249  }
250  }
251 }
252 
253 template <typename T, typename M>
254 auto deferred_guarded<T, M>::lock_shared() const -> shared_handle
255 {
256  do_pending_writes();
257  m_mutex.lock_shared();
258 
259  return shared_handle(&m_obj, shared_deleter(m_mutex));
260 }
261 
262 template <typename T, typename M>
263 auto deferred_guarded<T, M>::try_lock_shared() const -> shared_handle
264 {
265  do_pending_writes();
266  if (m_mutex.try_lock_shared()) {
267  return shared_handle(&m_obj, shared_deleter(m_mutex));
268  } else {
269  return shared_handle(nullptr, shared_deleter(m_mutex));
270  }
271 }
272 
273 template <typename T, typename M>
274 template <typename Duration>
275 auto deferred_guarded<T, M>::try_lock_shared_for(const Duration & d) const -> shared_handle
276 {
277  do_pending_writes();
278  if (m_mutex.try_lock_shared_for(d)) {
279  return shared_handle(&m_obj, shared_deleter(m_mutex));
280  } else {
281  return shared_handle(nullptr, shared_deleter(m_mutex));
282  }
283 }
284 
285 template <typename T, typename M>
286 template <typename TimePoint>
287 auto deferred_guarded<T, M>::try_lock_shared_until(const TimePoint & tp) const -> shared_handle
288 {
289  do_pending_writes();
290  if (m_mutex.try_lock_shared_until(tp)) {
291  return shared_handle(&m_obj, shared_deleter(m_mutex));
292  } else {
293  return shared_handle(nullptr, shared_deleter(m_mutex));
294  }
295 }
296 
297 } // namespace libguarded
298 
299 #endif