Condy v1.8
C++ Asynchronous System Call Layer for Linux
Loading...
Searching...
No Matches
futex.hpp
Go to the documentation of this file.
1
6
7#pragma once
8
13#include "condy/runtime.hpp"
14#include <atomic>
15#include <cerrno>
16#include <optional>
17
18namespace condy {
19
29template <typename T> class Futex {
30public:
35 Futex(std::atomic<T> &futex) : futex_(futex) {}
36
37 ~Futex() { notify_all_(-EIDRM); }
38
39 CONDY_DELETE_COPY_MOVE(Futex);
40
41public:
42 struct [[nodiscard]] WaitSender;
53 WaitSender wait(T old) noexcept { return {*this, old}; }
54
59 void notify_one() noexcept {
60 WaitFinishHandleBase *handle = nullptr;
61 {
62 std::lock_guard<std::mutex> lock(mutex_);
63 handle = wait_awaiters_.pop_front();
64 }
65 if (handle) {
66 handle->set_result(0);
67 handle->schedule();
68 }
69 }
70
75 void notify_all() noexcept { notify_all_(0); }
76
77private:
78 class WaitFinishHandleBase;
79 template <typename Receiver> class WaitFinishHandle;
80
81 bool cancel_wait_(WaitFinishHandleBase *handle) noexcept {
82 std::lock_guard<std::mutex> lock(mutex_);
83 return wait_awaiters_.remove(handle);
84 }
85
86 int32_t request_wait_(WaitFinishHandleBase *handle, T old) noexcept {
87 std::lock_guard<std::mutex> lock(mutex_);
88 auto val = futex_.load(std::memory_order_relaxed);
89 if (val != old) {
90 return 0; // No need to wait
91 }
92 wait_awaiters_.push_back(handle);
93 return -EAGAIN; // Need to wait
94 }
95
96private:
97 void notify_all_(int32_t result) noexcept {
98 HandleList handles;
99 {
100 std::lock_guard<std::mutex> lock(mutex_);
101 handles = std::move(wait_awaiters_);
102 }
103 while (auto *handle = handles.pop_front()) {
104 handle->set_result(result);
105 handle->schedule();
106 }
107 }
108
109private:
110 using HandleList =
111 detail::IntrusiveDoubleList<WaitFinishHandleBase,
112 &WaitFinishHandleBase::link_entry_>;
113
114 mutable std::mutex mutex_;
115 HandleList wait_awaiters_;
116 std::atomic<T> &futex_;
117};
118
119template <typename T>
120class Futex<T>::WaitFinishHandleBase : public detail::WorkInvoker {
121public:
122 void schedule() noexcept {
123 assert(runtime_ != nullptr);
124 runtime_->schedule_internal(this);
125 }
126
127 void set_result(int32_t result) noexcept { result_ = result; }
128
129public:
130 detail::DoubleLinkEntry link_entry_;
131
132protected:
133 Runtime *runtime_ = nullptr;
134 int32_t result_ = -ENOTRECOVERABLE; // Internal error if not set
135};
136
137template <typename T>
138template <typename Receiver>
139class Futex<T>::WaitFinishHandle
140 : public detail::InvokerAdapter<WaitFinishHandle<Receiver>,
141 WaitFinishHandleBase> {
142public:
143 using Base = detail::InvokerAdapter<WaitFinishHandle, WaitFinishHandleBase>;
144
145 WaitFinishHandle(Futex &futex, Receiver receiver)
146 : futex_(futex), receiver_(std::move(receiver)) {}
147
148 void start(Runtime *runtime, T old) noexcept {
149 this->runtime_ = runtime;
150 int32_t r = futex_.request_wait_(this, old);
151 if (r != -EAGAIN) {
152 std::move(receiver_)(r);
153 return;
154 }
155 runtime->pend_work_internal();
156
157 auto stop_token = receiver_.get_stop_token();
158 if (stop_token.stop_possible()) {
159 stop_callback_.emplace(std::move(stop_token), Cancellation{this});
160 }
161 }
162
163 void invoke() noexcept {
164 stop_callback_.reset();
165 assert(this->runtime_ != nullptr);
166 this->runtime_->resume_work_internal();
167 std::move(receiver_)(this->result_);
168 }
169
170private:
171 void cancel_() noexcept {
172 if (futex_.cancel_wait_(this)) {
173 // Successfully canceled
174 this->result_ = -ECANCELED;
175 assert(this->runtime_ != nullptr);
176 this->runtime_->schedule_internal(this);
177 }
178 }
179
180 struct Cancellation {
181 WaitFinishHandle *self;
182 void operator()() noexcept { self->cancel_(); }
183 };
184
185 using StopCallbackType =
186 detail::stop_callback_t<detail::stop_token_t<Receiver>, Cancellation>;
187
188private:
189 Futex &futex_;
190 Receiver receiver_;
191 std::optional<StopCallbackType> stop_callback_;
192};
193
194template <typename T> struct Futex<T>::WaitSender {
195public:
196 using CondySender = void;
197 using ReturnType = int32_t;
198
199 WaitSender(Futex &futex, T old) : futex_(futex), old_(old) {}
200
201 template <typename Receiver> auto connect_impl(Receiver receiver) noexcept {
202 return OperationState<Receiver>(futex_, old_, std::move(receiver));
203 }
204
205private:
206 template <typename Receiver>
207 class OperationState : public WaitFinishHandle<Receiver> {
208 public:
209 using Base = WaitFinishHandle<Receiver>;
210 OperationState(Futex &futex, T old, Receiver receiver)
211 : Base(futex, std::move(receiver)), old_(old) {}
212
213 void start(unsigned int /*flags*/) noexcept {
214 auto *runtime = detail::Context::current().runtime();
215 Base::start(runtime, old_);
216 }
217
218 private:
219 T old_;
220 };
221
222private:
223 Futex &futex_;
224 T old_;
225};
226
227} // namespace condy
User-space "futex" implementation for efficient synchronization between coroutines.
Definition futex.hpp:29
WaitSender wait(T old) noexcept
Wait if the futex value equals to the specified old value. The awaiting coroutine will be suspended u...
Definition futex.hpp:53
Futex(std::atomic< T > &futex)
Construct a new Futex object.
Definition futex.hpp:35
void notify_all() noexcept
Notify all awaiting coroutines.
Definition futex.hpp:75
void notify_one() noexcept
Notify one awaiting coroutine, if any.
Definition futex.hpp:59
Intrusive single-linked and double-linked list implementations.
Polymorphic invocation utilities.
The main namespace for the Condy library.
Definition condy.hpp:37
Runtime type for running the io_uring event loop.
Internal utility classes and functions used by Condy.