forked from lewissbaker/cppcoro
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathasync_mutex.hpp
More file actions
186 lines (151 loc) · 5.41 KB
/
Copy pathasync_mutex.hpp
File metadata and controls
186 lines (151 loc) · 5.41 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
///////////////////////////////////////////////////////////////////////////////
// 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_lock_result;
/// \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 is a value that can be optionallly assigned to an
/// 'async_mutex_lock' variable which will take ownership of the
/// lock and call unlock() when it goes out of scope.
async_mutex_lock_operation 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;
static constexpr std::uintptr_t locked_no_waiters =
reinterpret_cast<std::uintptr_t>(
static_cast<async_mutex_lock_operation*>(nullptr));
// 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
/// A value returned from a lock operation which represents
/// a held lock.
///
/// It can be used to construct an async_mutex_lock object
/// to pass ownership of the lock to the lock object.
/// Otherwise, the caller must manually unlock the mutex.
class async_mutex_lock_result
{
private:
friend class async_mutex_lock;
friend class async_mutex_lock_operation;
explicit async_mutex_lock_result(async_mutex& mutex) noexcept
: m_mutex(mutex)
{}
async_mutex& m_mutex;
};
/// \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:
async_mutex_lock(async_mutex_lock_result result) noexcept
: m_mutex(result.m_mutex)
{}
explicit async_mutex_lock(async_mutex& mutex, std::adopt_lock_t) noexcept
: m_mutex(mutex)
{}
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()
{
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;
auto await_resume() const noexcept { return async_mutex_lock_result{ m_mutex }; }
private:
friend class async_mutex;
async_mutex& m_mutex;
async_mutex_lock_operation* m_next;
std::experimental::coroutine_handle<> m_awaiter;
};
}
#endif