3
3
#include " coro/detail/awaiter_list.hpp"
4
4
#include " coro/expected.hpp"
5
5
#include " coro/export.hpp"
6
+ #include " coro/mutex.hpp"
6
7
7
8
#include < atomic>
8
9
#include < coroutine>
9
- #include < mutex>
10
10
#include < string>
11
11
12
12
namespace coro
@@ -38,29 +38,33 @@ class acquire_operation
38
38
public:
39
39
explicit acquire_operation (semaphore<max_value>& s) : m_semaphore(s) { }
40
40
41
- auto await_ready () const noexcept -> bool
41
+ [[nodiscard]] auto await_ready () const noexcept -> bool
42
42
{
43
- if (m_semaphore.m_shutdown .load (std::memory_order::acquire))
43
+ // If the semaphore is shutdown or a resources can be acquired without suspending release the lock and resume execution.
44
+ if (m_semaphore.m_shutdown .load (std::memory_order::acquire) || m_semaphore.try_acquire ())
44
45
{
46
+ m_semaphore.m_mutex .unlock ();
45
47
return true ;
46
48
}
47
- return m_semaphore.try_acquire ();
49
+
50
+ return false ;
48
51
}
49
52
50
- auto await_suspend (std::coroutine_handle<> awaiting_coroutine) noexcept -> bool
53
+ auto await_suspend (const std::coroutine_handle<> awaiting_coroutine) noexcept -> bool
51
54
{
52
- // Check again now that we've setup the coroutine frame, the state could have changed.
55
+ // Check again now that we've set up the coroutine frame, the state could have changed.
53
56
if (await_ready ())
54
57
{
55
58
return false ;
56
59
}
57
60
58
61
m_awaiting_coroutine = awaiting_coroutine;
59
62
detail::awaiter_list_push (m_semaphore.m_acquire_waiters , this );
63
+ m_semaphore.m_mutex .unlock ();
60
64
return true ;
61
65
}
62
66
63
- auto await_resume () const -> semaphore_acquire_result
67
+ [[nodiscard]] auto await_resume () const -> semaphore_acquire_result
64
68
{
65
69
if (m_semaphore.m_shutdown .load (std::memory_order::acquire))
66
70
{
@@ -69,9 +73,9 @@ class acquire_operation
69
73
return semaphore_acquire_result::acquired;
70
74
}
71
75
72
- acquire_operation<max_value>* m_next{nullptr };
73
- semaphore<max_value>& m_semaphore;
74
- std::coroutine_handle<> m_awaiting_coroutine;
76
+ acquire_operation<max_value>* m_next{nullptr };
77
+ semaphore<max_value>& m_semaphore;
78
+ std::coroutine_handle<> m_awaiting_coroutine;
75
79
};
76
80
77
81
} // namespace detail
@@ -80,7 +84,7 @@ template<std::ptrdiff_t max_value>
80
84
class semaphore
81
85
{
82
86
public:
83
- explicit semaphore (std::ptrdiff_t starting_value)
87
+ explicit semaphore (const std::ptrdiff_t starting_value)
84
88
: m_counter(starting_value)
85
89
{ }
86
90
@@ -92,36 +96,47 @@ class semaphore
92
96
auto operator =(const semaphore&) noexcept -> semaphore& = delete ;
93
97
auto operator =(semaphore&&) noexcept -> semaphore& = delete ;
94
98
95
- auto release () -> void
99
+ /* *
100
+ * @brief Acquires a resource from the semaphore, if the semaphore has no resources available then
101
+ * this will suspend and wait until a resource becomes available.
102
+ */
103
+ [[nodiscard]] auto acquire () -> coro::task<semaphore_acquire_result>
104
+ {
105
+ co_await m_mutex.lock ();
106
+ co_return co_await detail::acquire_operation<max_value>{*this };
107
+ }
108
+
109
+ /* *
110
+ * @brief Releases a resources back to the semaphore, if the semaphore is already at value() == max() this does nothing.
111
+ * @return
112
+ */
113
+ [[nodiscard]] auto release () -> coro::task<void>
96
114
{
97
- // If there are any waiters just transfer ownership to the waiter.
115
+ co_await m_mutex.lock ();
116
+ // Do not resume or increment resources past the max_value.
117
+ if (value () == max ())
118
+ {
119
+ m_mutex.unlock ();
120
+ co_return ;
121
+ }
122
+
123
+ // If there are any waiters just transfer resource ownership to the waiter.
98
124
auto * waiter = detail::awaiter_list_pop (m_acquire_waiters);
99
125
if (waiter != nullptr )
100
126
{
127
+ m_mutex.unlock ();
101
128
waiter->m_awaiting_coroutine .resume ();
102
129
}
103
130
else
104
131
{
105
- // Attempt to increment the counter only up to max_value.
106
- auto current = m_counter.load (std::memory_order::acquire);
107
- do
108
- {
109
- if (current >= max_value)
110
- {
111
- return ;
112
- }
113
- } while (!m_counter.compare_exchange_weak (current, current + 1 , std::memory_order::acq_rel, std::memory_order::acquire));
132
+ // Release the resource.
133
+ m_counter.fetch_add (1 , std::memory_order::release);
134
+ m_mutex.unlock ();
114
135
}
115
136
}
116
137
117
138
/* *
118
- * Acquires a resource from the semaphore, if the semaphore has no resources available then
119
- * this will wait until a resource becomes available.
120
- */
121
- [[nodiscard]] auto acquire () -> detail::acquire_operation<max_value> { return detail::acquire_operation<max_value>{*this }; }
122
-
123
- /* *
124
- * Attemtps to acquire a resource if there is any resources available.
139
+ * @brief Attempts to acquire a resource if there are any resources available.
125
140
* @return True if the acquire operation was able to acquire a resource.
126
141
*/
127
142
auto try_acquire () -> bool
@@ -144,7 +159,7 @@ class semaphore
144
159
[[nodiscard]] static constexpr auto max () noexcept -> std::ptrdiff_t { return max_value; }
145
160
146
161
/* *
147
- * The current number of resources available in this semaphore.
162
+ * @return The current number of resources available to acquire for this semaphore.
148
163
*/
149
164
[[nodiscard]] auto value () const noexcept -> std::ptrdiff_t { return m_counter.load (std::memory_order::acquire); }
150
165
@@ -167,14 +182,20 @@ class semaphore
167
182
}
168
183
}
169
184
185
+ /* *
186
+ * @return True if this semaphore has been shutdown.
187
+ */
170
188
[[nodiscard]] auto is_shutdown () const -> bool { return m_shutdown.load (std::memory_order::acquire); }
171
189
172
190
private:
173
191
friend class detail ::acquire_operation<max_value>;
174
192
193
+ // / @brief The current number of resources that are available to acquire.
175
194
std::atomic<std::ptrdiff_t > m_counter;
176
195
// / @brief The current list of awaiters attempting to acquire the semaphore.
177
196
std::atomic<detail::acquire_operation<max_value>*> m_acquire_waiters{nullptr };
197
+ // / @brief mutex used to do acquire and release operations
198
+ coro::mutex m_mutex;
178
199
// / @brief Flag to denote that all waiters should be woken up with the shutdown result.
179
200
std::atomic<bool > m_shutdown{false };
180
201
};
0 commit comments