CsLibGuarded  2.0.0
cs_lock_guards.h
1 /***********************************************************************
2 *
3 * Copyright (c) 2016-2025 Ansel Sermersheim
4 *
5 * This file is part of CsLibGuarded.
6 *
7 * CsLibGuarded is free software which is released under the BSD 2-Clause license.
8 * For license details refer to the 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_LOCK_GUARDS_H
19 #define CSLIBGUARDED_LOCK_GUARDS_H
20 
21 #include <algorithm>
22 #include <array>
23 #include <tuple>
24 #include <utility>
25 
26 namespace libguarded
27 {
28 
29 namespace detail
30 {
31 
32 // type trait to get the address of a guard, specialization below
33 template <typename T>
34 class guard_address
35 {
36  public:
37  constexpr guard_address(const T &object) : value(&object){};
38 
39  const void *const value;
40 };
41 
42 // Base class to sort by address and virtually invoke the actual try_lock method
43 class guard_locker_base
44 {
45  public:
46  guard_locker_base(const void *address) : m_address(address){};
47 
48  virtual ~guard_locker_base() = default;
49 
50  [[nodiscard]] virtual bool do_try_lock() = 0;
51  virtual void reset() = 0;
52 
53  [[nodiscard]] bool operator<(const guard_locker_base &rhs) const noexcept
54  {
55  return m_address < rhs.m_address;
56  }
57 
58  private:
59  const void *const m_address;
60 };
61 
62 // Concrete implementation, stores a reference to a guarded object and a handle from calling
63 // the underlying try_lock() method
64 template <typename T>
65 class guard_locker : public guard_locker_base
66 {
67  public:
68  using lock_type = std::decay_t<decltype(std::declval<T>().try_lock())>;
69 
70  guard_locker(T &guard) : guard_locker_base(guard_address<T>(guard).value), m_guard(guard)
71  {
72  }
73 
74  [[nodiscard]] bool do_try_lock() override
75  {
76  m_lock = m_guard.try_lock();
77  return m_lock != nullptr;
78  }
79 
80  void reset() override
81  {
82  m_lock.reset();
83  }
84 
85  [[nodiscard]] lock_type take_lock() &&
86  {
87  return std::move(m_lock);
88  }
89 
90  private:
91  lock_type m_lock;
92  T &m_guard;
93 };
94 
95 // Adapter class which forwards the try_lock() method to the underlying type's try_lock_shared()
96 // method so we can get a read lock
97 template <typename T>
98 class guard_reader
99 {
100  public:
101  guard_reader(T &guard) : m_guard(guard)
102  {
103  }
104 
105  [[nodiscard]] auto try_lock()
106  {
107  return m_guard.try_lock_shared();
108  }
109 
110  private:
111  T &m_guard;
112 
113  friend class guard_address<guard_reader<T>>;
114 };
115 
116 // Specialization for guard_address which returns the address of the underlying guard instead of the
117 // adapter object
118 template <typename T>
119 class guard_address<guard_reader<T>>
120 {
121  public:
122  constexpr guard_address(const guard_reader<T> &object) : value(&(object.m_guard)){};
123 
124  const void *const value;
125 };
126 
127 } // namespace detail
128 
129 template <typename T>
130 [[nodiscard]] auto as_reader(T &guard)
131 {
132  return detail::guard_reader<T>(guard);
133 }
134 
135 template <typename... Ts>
136 [[nodiscard]] auto lock_guards(Ts &&...Vs)
137 {
138  using namespace libguarded::detail;
139 
140  std::tuple<guard_locker<Ts>...> lockers = {guard_locker<Ts>(Vs)...};
141 
142  auto lock_sequence = std::apply(
143  [](auto &&...args) {
144  std::array<guard_locker_base *, sizeof...(Ts)> retval = {(&args)...};
145  return retval;
146  },
147  lockers);
148 
149  // sort the lock sequence based on the address of the underlying guarded object
150  std::sort(lock_sequence.begin(), lock_sequence.end());
151 
152  auto iter = lock_sequence.begin();
153 
154  // walk through all locks from the beginning
155  while (iter != lock_sequence.end()) {
156  if ((*iter)->do_try_lock()) {
157  // this lock succeeded, go on
158  ++iter;
159  } else {
160  // a lock failed, release as we walk back to the beginning
161  while (iter != lock_sequence.begin()) {
162  --iter;
163  (*iter)->reset();
164  }
165  }
166  }
167 
168  // all locks succeeded
169 
170  return std::apply(
171  [](auto &&...args) {
172  // move locks out of the guard_locker objects
173  return std::make_tuple(std::move(args).take_lock()...);
174  },
175  std::move(lockers));
176 }
177 
178 } // namespace libguarded
179 
180 #endif