forked from lewissbaker/cppcoro
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathasync_mutex.hpp
More file actions
200 lines (162 loc) · 5.82 KB
/
async_mutex.hpp
File metadata and controls
200 lines (162 loc) · 5.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_ASYNC_MUTEX_HPP_INCLUDED
#define CPPCORO_ASYNC_MUTEX_HPP_INCLUDED
#include <experimental/coroutine>
#include <atomic>
#include <cstdint>
#include <mutex> // for std::adopt_lock_t
namespace cppcoro
{
class async_mutex_lock;
class async_mutex_lock_operation;
class async_mutex_scoped_lock_operation;
/// \brief
/// A mutex that can be locked asynchronously using 'co_await'.
///
/// Ownership of the mutex is not tied to any particular thread.
/// This allows the coroutine owning the lock to transition from
/// one thread to another while holding a lock.
///
/// Implementation is lock-free, using only std::atomic values for
/// synchronisation. Awaiting coroutines are suspended without blocking
/// the current thread if the lock could not be acquired synchronously.
class async_mutex
{
public:
/// \brief
/// Construct to a mutex that is not currently locked.
async_mutex() noexcept;
/// Destroys the mutex.
///
/// Behaviour is undefined if there are any outstanding coroutines
/// still waiting to acquire the lock.
~async_mutex();
/// \brief
/// Attempt to acquire a lock on the mutex without blocking.
///
/// \return
/// true if the lock was acquired, false if the mutex was already locked.
/// The caller is responsible for ensuring unlock() is called on the mutex
/// to release the lock if the lock was acquired by this call.
bool try_lock() noexcept;
/// \brief
/// Acquire a lock on the mutex asynchronously.
///
/// If the lock could not be acquired synchronously then the awaiting
/// coroutine will be suspended and later resumed when the lock becomes
/// available. If suspended, the coroutine will be resumed inside the
/// call to unlock() from the previous lock owner.
///
/// \return
/// An operation object that must be 'co_await'ed to wait until the
/// lock is acquired. The result of the 'co_await m.lock_async()'
/// expression has type 'void'.
async_mutex_lock_operation lock_async() noexcept;
/// \brief
/// Acquire a lock on the mutex asynchronously, returning an object that
/// will call unlock() automatically when it goes out of scope.
///
/// If the lock could not be acquired synchronously then the awaiting
/// coroutine will be suspended and later resumed when the lock becomes
/// available. If suspended, the coroutine will be resumed inside the
/// call to unlock() from the previous lock owner.
///
/// \return
/// An operation object that must be 'co_await'ed to wait until the
/// lock is acquired. The result of the 'co_await m.scoped_lock_async()'
/// expression returns an 'async_mutex_lock' object that will call
/// this->mutex() when it destructs.
async_mutex_scoped_lock_operation scoped_lock_async() noexcept;
/// \brief
/// Unlock the mutex.
///
/// Must only be called by the current lock-holder.
///
/// If there are lock operations waiting to acquire the
/// mutex then the next lock operation in the queue will
/// be resumed inside this call.
void unlock();
private:
friend class async_mutex_lock_operation;
static constexpr std::uintptr_t not_locked = 1;
// assume == reinterpret_cast<std::uintptr_t>(static_cast<void*>(nullptr))
static constexpr std::uintptr_t locked_no_waiters = 0;
// This field provides synchronisation for the mutex.
//
// It can have three kinds of values:
// - not_locked
// - locked_no_waiters
// - a pointer to the head of a singly linked list of recently
// queued async_mutex_lock_operation objects. This list is
// in most-recently-queued order as new items are pushed onto
// the front of the list.
std::atomic<std::uintptr_t> m_state;
// Linked list of async lock operations that are waiting to acquire
// the mutex. These operations will acquire the lock in the order
// they appear in this list. Waiters in this list will acquire the
// mutex before waiters added to the m_newWaiters list.
async_mutex_lock_operation* m_waiters;
};
/// \brief
/// An object that holds onto a mutex lock for its lifetime and
/// ensures that the mutex is unlocked when it is destructed.
///
/// It is equivalent to a std::lock_guard object but requires
/// that the result of co_await async_mutex::lock_async() is
/// passed to the constructor rather than passing the async_mutex
/// object itself.
class async_mutex_lock
{
public:
explicit async_mutex_lock(async_mutex& mutex, std::adopt_lock_t) noexcept
: m_mutex(&mutex)
{}
async_mutex_lock(async_mutex_lock&& other) noexcept
: m_mutex(other.m_mutex)
{
other.m_mutex = nullptr;
}
async_mutex_lock(const async_mutex_lock& other) = delete;
async_mutex_lock& operator=(const async_mutex_lock& other) = delete;
// Releases the lock.
~async_mutex_lock()
{
if (m_mutex != nullptr)
{
m_mutex->unlock();
}
}
private:
async_mutex* m_mutex;
};
class async_mutex_lock_operation
{
public:
explicit async_mutex_lock_operation(async_mutex& mutex) noexcept
: m_mutex(mutex)
{}
bool await_ready() const noexcept { return false; }
bool await_suspend(std::experimental::coroutine_handle<> awaiter) noexcept;
void await_resume() const noexcept {}
protected:
friend class async_mutex;
async_mutex& m_mutex;
private:
async_mutex_lock_operation* m_next;
std::experimental::coroutine_handle<> m_awaiter;
};
class async_mutex_scoped_lock_operation : public async_mutex_lock_operation
{
public:
using async_mutex_lock_operation::async_mutex_lock_operation;
[[nodiscard]]
async_mutex_lock await_resume() const noexcept
{
return async_mutex_lock{ m_mutex, std::adopt_lock };
}
};
}
#endif