libcoro  1.0
Coroutine support library for C++20
mutex.h
1 #pragma once
2 #include "prepared_coro.h"
3 #include "future.h"
4 
5 #include <atomic>
6 #include <memory>
7 #include <variant>
8 
9 
10 namespace coro {
11 
13 
30 class mutex {
31 public:
32 
34  class ownership {
35  public:
37  ownership() = default;
39  ownership(ownership &&x):_inst(x._inst) {x._inst = nullptr;}
42  if (this != &x) {
43  release();
44  _inst = x._inst;
45  x._inst = nullptr;
46  }
47  return *this;
48  }
52  void release() {
53  if (_inst) {
54  auto tmp = _inst;
55  _inst = nullptr;
56  tmp->unlock();
57  }
58  }
60 
65  operator bool() const {return _inst != nullptr;}
66 
67 
68  protected:
69  mutex *_inst = nullptr;
70 
71  friend class mutex;
72 
73  ownership(mutex *inst):_inst(inst) {}
74  };
75 
77 
82  return ownership(try_acquire()?this:nullptr);
83  }
84 
85 
88  return [&](auto promise) {
90  };
91  }
92 
94  future<ownership> operator co_await() {
95  return lock();
96  }
97 
100  return std::move(lock().get());
101  }
102 
103 protected:
104  class awaiter : public future<ownership> {
105  public:
107  static awaiter *from_future(future<ownership> *x) {
108  return static_cast<awaiter *>(x);
109  }
110  };
111 
113  static awaiter *locked() {return reinterpret_cast<awaiter *>(0x1);}
114 
119  std::atomic<awaiter *> _req = {nullptr};
121 
122  awaiter * _que = nullptr;
123 
124 
126 
131  bool try_acquire() {
132  awaiter *need = nullptr;
133  return _req.compare_exchange_strong(need,locked(), std::memory_order_acquire);
134  }
135 
137 
147  awaiter *awt = awaiter::from_future(fut_awt);
148  awaiter *nx = nullptr;
149  while (!_req.compare_exchange_weak(nx, awt, std::memory_order_relaxed)) {
150  awt->_chain = nx;
151  }
152 
153  if (nx == nullptr) {
154  build_queue(_req.exchange(locked(), std::memory_order_acquire), awt);
155  return promise<ownership>(fut_awt)(make_ownership());
156  }
157  return {};
158  }
159 
161 
169  void build_queue(awaiter *r, awaiter *stop) {
170  while (r != stop) {
171  auto n = r->_chain;
172  r->_chain = _que;
173  _que = r;
174  r = awaiter::from_future(n);
175  }
176  }
177 
179 
187  if (!_que) {
188  auto lk = locked();
189  auto need = lk;
190  if (_req.compare_exchange_strong(need, nullptr, std::memory_order_release)) return {};
191  build_queue(_req.exchange(lk, std::memory_order_relaxed), lk);
192  }
194  _que = static_cast<awaiter *>(_que->_chain);
195  return ret(make_ownership());
196  }
197 
198 
200  ownership make_ownership() {return this;}
201 
202 };
203 
204 namespace _details {
205  template<typename T, typename Tuple> struct tuple_add;
206  template<typename T, typename ... Us> struct tuple_add<T, std::tuple<Us...> > {
207  using type = std::tuple<T, Us...>;
208  };
209 
210  template <typename Tuple> struct make_unique_types;
211  template <typename T, typename... Us> struct make_unique_types<std::tuple<T, Us...> > {
212  using type = std::conditional_t<
213  (std::is_same_v<T, Us> || ...),
214  typename make_unique_types<std::tuple<Us ...> >::type,
215  typename tuple_add<T, typename make_unique_types<std::tuple<Us...> >::type>::type>;
216  };
217 
218  template <> struct make_unique_types< std::tuple<> > {
219  using type = std::tuple<>;
220  };
221 
222  template<typename Tuple> struct make_variant_from_tuple;
223  template<typename ... Ts> struct make_variant_from_tuple<std::tuple<Ts...> > {
224  using type = std::variant<Ts...>;
225  };
226  template<typename ... Ts> using variant_of_t = typename make_variant_from_tuple<
227  typename make_unique_types<std::tuple<Ts...> >::type>::type;
228 }
229 
230 
231 template<typename ... Mutexes>
232 class lock: public future<std::tuple<decltype(std::declval<Mutexes>().try_lock())...> > {
233 public:
234 
235  using ownership = std::tuple<decltype(std::declval<Mutexes>().try_lock())...>;
236  using future_variant = _details::variant_of_t<std::monostate,
237  decltype(std::declval<Mutexes>().lock())...>;
238 
239  lock(Mutexes & ... lst):_mxlist(lst...),_prom(this->get_promise()) {
240  ownership ownlist;
241  if (finish_lock(-1, ownlist)) {
242  _prom(std::move(ownlist));
243  } //if failed, ownership is released and so all held locks
244  }
245 
246 
247 protected:
248  std::tuple<Mutexes &...> _mxlist;
249  promise<ownership> _prom;
250  future_variant _fut;
251 
253 
256  template<int idx>
257  void on_lock() {
258  auto &mx = std::get<idx>(_mxlist);
259  using LkType = decltype(mx.lock());
260  ownership ownlist;
261  std::get<idx>(ownlist) = std::get<LkType>(_fut).await_resume();
262  if (finish_lock(idx,ownlist)) {
263  _prom(std::move(ownlist));
264  }
265  }
266 
268 
275  template<int idx = 0>
276  bool finish_lock(int skip, ownership &ownlist) {
277  //idx out of range? success
278  if constexpr(idx >= std::tuple_size_v<ownership>) {
279  return true;
280  } else {
281  //idx == skip - skip it
282  if (idx == skip) {
283  return finish_lock<idx+1>(skip,ownlist);
284  } else {
285  //retrieve ownership
286  auto &own = std::get<idx>(ownlist);
287  //retrieve lock
288  auto &mx = std::get<idx>(_mxlist);
289  //try to lock
290  own = std::get<idx>(_mxlist).try_lock();
291  //we success
292  if (own) {
293  //continue by next lock
294  return finish_lock<idx+1>(skip,ownlist);
295  } else {
296  //failure
297  using LkType = decltype(mx.lock());
298  //construct future object
299  _fut.template emplace<LkType>();
300  LkType &fut = std::get<LkType>(_fut);
301  //acquire lock
302  fut << [&]{return mx.lock();};
303  //install callback, if failed
304  if (fut.set_callback([this]{ on_lock<idx>();}) == false) {
305  //we got ownership
306  own = fut.get();
307  //continue locking
308  return finish_lock<idx+1>(skip,ownlist);
309  }
310  //return failure
311  return false;
312  }
313  }
314  }
315  }
316 };
317 
318 
319 }
320 
321 
322 
323 
promise_t get_promise()
Retrieve promise and begin evaluation.
Definition: future.h:567
~ownership()
dtor releases ownership
Definition: mutex.h:50
ownership()=default
ownership can be default constructed
void release()
releases ownership explicitly (unlock)
Definition: mutex.h:52
ownership(ownership &&x)
ownership can be moved
Definition: mutex.h:39
ownership & operator=(ownership &&x)
ownership can be assigned by move
Definition: mutex.h:41
tracks ownership
Definition: mutex.h:34
ownership lock_sync()
lock synchronously
Definition: mutex.h:99
future< ownership > lock()
lock the mutex, retrieve future ownership
Definition: mutex.h:87
ownership try_lock()
try to lock
Definition: mutex.h:81
awaiter * _que
contains queue of requests already registered by the lock
Definition: mutex.h:122
promise< ownership >::notify do_lock(future< ownership > *fut_awt)
initiate lock operation
Definition: mutex.h:146
void build_queue(awaiter *r, awaiter *stop)
builds internal queue
Definition: mutex.h:169
std::atomic< awaiter * > _req
Definition: mutex.h:119
static awaiter * locked()
generates special pointer, which is used as locked flag (value 0x00000001)
Definition: mutex.h:113
promise< ownership >::notify unlock()
unlock the lock
Definition: mutex.h:186
ownership make_ownership()
creates ownership object
Definition: mutex.h:200
bool try_acquire()
tries to acquire
Definition: mutex.h:131
Mutex which allows locking across co_await and co_yield suspend points.
Definition: mutex.h:30
FutureType * release()
Release the future pointer from the promise object.
Definition: future.h:273
Carries reference to future<T>, callable, sets value of an associated future<T>
Definition: future.h:73
main namespace
Definition: aggregator.h:8