Condy v1.8
C++ Asynchronous System Call Layer for Linux
Loading...
Searching...
No Matches
runtime.hpp
Go to the documentation of this file.
1
5
6#pragma once
7
12#include "condy/detail/ring.hpp"
18#include <algorithm>
19#include <atomic>
20#include <cerrno>
21#include <cstddef>
22#include <cstdint>
23#include <cstring>
24#include <limits>
25#include <mutex>
26#include <thread>
27
28namespace condy {
29
36class Runtime {
37public:
42 Runtime(const RuntimeOptions &options = {})
43 : ring_(create_ring_(options)),
44 event_interval_(options.event_interval_),
45 disable_register_ring_fd_(options.disable_register_ring_fd_),
46 fd_table_(*ring_.ring()), buffer_table_(*ring_.ring()),
47 settings_(*ring_.ring()) {}
48
49 CONDY_DELETE_COPY_MOVE(Runtime);
50
51public:
52 void schedule_internal(detail::WorkInvoker *work) noexcept {
53 auto *curr_runtime = detail::Context::current().runtime();
54 if (curr_runtime == this) {
55 local_queue_.push_back(work);
56 return;
57 }
58
59 if (ring_enabled_.load(std::memory_order_relaxed)) {
60 // Fast path: if the ring is enabled, we can directly schedule the
61 // work
62 detail::tsan_release(work);
63 schedule_msg_ring_(
64 curr_runtime,
65 detail::encode_work(work, detail::WorkType::Schedule));
66 } else {
67 // Slow path: if the ring is not enabled, we need to acquire the
68 // mutex to ensure the work is scheduled before the ring is enabled
69 std::unique_lock<std::mutex> lock(mutex_);
70 if (ring_enabled_.load(std::memory_order_relaxed)) {
71 lock.unlock();
72 detail::tsan_release(work);
73 schedule_msg_ring_(
74 curr_runtime,
75 detail::encode_work(work, detail::WorkType::Schedule));
76 } else {
77 global_queue_.push_back(work);
78 }
79 }
80 }
81
82 void cancel_internal(uintptr_t data) noexcept {
83 auto *curr_runtime = detail::Context::current().runtime();
84 if (curr_runtime == this) {
85 io_uring_sqe *sqe = ring_.get_sqe();
86 prep_cancel_(sqe, data);
87 return;
88 }
89
90 if (!ring_enabled_.load(std::memory_order_relaxed)) {
91 return;
92 }
93
94 detail::CancelRequest request(data);
95 detail::tsan_release(&request);
96 schedule_msg_ring_(
97 curr_runtime,
98 detail::encode_work(&request, detail::WorkType::Cancel));
99 if (curr_runtime != nullptr) {
100 // Ensure the cancel msg is submitted.
101 curr_runtime->ring_.submit();
102 }
103 // Block until the runtime thread has submitted the cancel SQE. This is
104 // important to prevent address reuse of the same data pointer, which
105 // can lead to incorrect cancellation or other bugs.
106 request.wait();
107 }
108
109 void pend_work_internal() noexcept {
110 assert(detail::Context::current().runtime() == this);
111 pending_works_++;
112 }
113
114 void resume_work_internal() noexcept {
115 assert(detail::Context::current().runtime() == this);
116 pending_works_--;
117 }
118
119 auto &bgid_pool_internal() noexcept { return bgid_pool_; }
120
121 auto &ring_internal() noexcept { return ring_; }
122
123public:
131 void allow_exit() noexcept {
132 exit_allowed_.store(true, std::memory_order_release);
133 wakeup_();
134 }
135
144 void run() {
145 detail::Context::current().init(this);
146 auto d = detail::defer([]() { detail::Context::current().reset(); });
147
148 if (run_thread_id_ == std::thread::id()) {
149 int r = io_uring_enable_rings(ring_.ring());
150 if (r < 0) {
151 throw detail::make_system_error("io_uring_enable_rings", -r);
152 }
153
154 run_thread_id_ = std::this_thread::get_id();
155
156 {
157 std::lock_guard<std::mutex> lock(mutex_);
158 flush_global_queue_();
159 ring_enabled_.store(true, std::memory_order_relaxed);
160 }
161
162 if (!disable_register_ring_fd_) {
163 io_uring_register_ring_fd(ring_.ring());
164 }
165 } else if (run_thread_id_ == std::this_thread::get_id()) {
166 flush_ring_();
167 } else {
168 throw std::runtime_error(
169 "Runtime::run() can only be called from the same thread");
170 }
171
172 while (true) {
173 tick_count_++;
174
175 if (tick_count_ >= event_interval_) {
176 tick_count_ = 0;
177 flush_ring_();
178 }
179
180 if (auto *work = local_queue_.pop_front()) {
181 (*work)();
182 continue;
183 }
184
185 if (pending_works_ == 0 &&
186 exit_allowed_.load(std::memory_order_acquire)) {
187 break;
188 }
189 flush_ring_wait_();
190 }
191
192 tick_count_ = 0;
193 exit_allowed_.store(false, std::memory_order_release);
194 }
195
200 auto &fd_table() noexcept { return fd_table_; }
201
206 auto &buffer_table() noexcept { return buffer_table_; }
207
212 auto &settings() noexcept { return settings_; }
213
214private:
215 static detail::Ring create_ring_(const RuntimeOptions &options) {
216 io_uring_params params;
217 std::memset(&params, 0, sizeof(params));
218
219 params.flags |= IORING_SETUP_CLAMP;
220 params.flags |= IORING_SETUP_SINGLE_ISSUER;
221 params.flags |= IORING_SETUP_SUBMIT_ALL;
222 params.flags |= IORING_SETUP_R_DISABLED;
223
224 size_t ring_entries = options.sq_size_;
225 if (options.cq_size_ != 0) { // 0 means default
226 params.flags |= IORING_SETUP_CQSIZE;
227 params.cq_entries = options.cq_size_;
228 }
229
230 if (options.enable_iopoll_) {
231 params.flags |= IORING_SETUP_IOPOLL;
232#if !IO_URING_CHECK_VERSION(2, 9) // >= 2.9
233 if (options.enable_hybrid_iopoll_) {
234 params.flags |= IORING_SETUP_HYBRID_IOPOLL;
235 }
236#endif
237 }
238
239 if (options.enable_sqpoll_) {
240 params.flags |= IORING_SETUP_SQPOLL;
241 params.sq_thread_idle = options.sqpoll_idle_time_ms_;
242 if (options.sqpoll_thread_cpu_.has_value()) {
243 params.flags |= IORING_SETUP_SQ_AFF;
244 params.sq_thread_cpu = *options.sqpoll_thread_cpu_;
245 }
246 }
247
248 if (options.attach_wq_target_ != nullptr) {
249 params.flags |= IORING_SETUP_ATTACH_WQ;
250 params.wq_fd = options.attach_wq_target_->ring_.ring()->ring_fd;
251 }
252
253 if (options.enable_defer_taskrun_) {
254 params.flags |= IORING_SETUP_DEFER_TASKRUN;
255 params.flags |= IORING_SETUP_TASKRUN_FLAG;
256 }
257
258 if (options.enable_coop_taskrun_) {
259 params.flags |= IORING_SETUP_COOP_TASKRUN;
260 params.flags |= IORING_SETUP_TASKRUN_FLAG;
261 }
262
263 if (options.enable_sqe128_) {
264 params.flags |= IORING_SETUP_SQE128;
265 }
266
267 if (options.enable_cqe32_) {
268 params.flags |= IORING_SETUP_CQE32;
269 }
270
271#if !IO_URING_CHECK_VERSION(2, 13) // >= 2.13
272 if (options.enable_sqe_mixed_) {
273 params.flags |= IORING_SETUP_SQE_MIXED;
274 }
275#endif
276
277#if !IO_URING_CHECK_VERSION(2, 13) // >= 2.13
278 if (options.enable_cqe_mixed_) {
279 params.flags |= IORING_SETUP_CQE_MIXED;
280 }
281#endif
282
283 void *buf = nullptr;
284 size_t buf_size = 0;
285#if !IO_URING_CHECK_VERSION(2, 5) // >= 2.5
286 if (options.enable_no_mmap_) {
287 params.flags |= IORING_SETUP_NO_MMAP;
288 buf = options.no_mmap_buf_;
289 buf_size = options.no_mmap_buf_size_;
290 }
291#endif
292
293#if !IO_URING_CHECK_VERSION(2, 14) // >= 2.14
294 if (options.enable_sq_rewind_) {
295 params.flags |= IORING_SETUP_SQ_REWIND;
296 }
297#endif
298
299 size_t submit_batch = options.submit_batch_;
300 if (submit_batch == 0) {
301 if (options.enable_sqpoll_) {
302 submit_batch = std::min<size_t>(32, ring_entries);
303 } else {
304 submit_batch = std::numeric_limits<size_t>::max();
305 }
306 }
307 assert(submit_batch > 0);
308
309 return detail::Ring(ring_entries, &params, buf, buf_size, submit_batch);
310 }
311
312 void schedule_msg_ring_(Runtime *curr_runtime, uintptr_t data) noexcept {
313 int ring_fd = this->ring_.ring()->ring_fd;
314 if (curr_runtime != nullptr) {
315 io_uring_sqe *sqe = curr_runtime->ring_.get_sqe();
316 prep_msg_ring_(ring_fd, sqe, data);
317 curr_runtime->pend_work_internal();
318 } else {
319 io_uring_sqe sqe = {};
320 prep_msg_ring_(ring_fd, &sqe, data);
321 int r = detail::sync_msg_ring(&sqe);
322 if (r < 0) {
323 detail::panic_on(
324 std::format("sync_msg_ring: {}", std::strerror(-r)));
325 }
326 }
327 }
328
329 // Wakeup the runtime if it's blocked in Ring::reap_completions_wait()
330 void wakeup_() noexcept {
331 auto *curr_runtime = detail::Context::current().runtime();
332 if (curr_runtime == this) {
333 return;
334 }
335
336 if (!ring_enabled_.load(std::memory_order_relaxed)) {
337 return;
338 }
339
340 schedule_msg_ring_(
341 curr_runtime,
342 detail::encode_work(nullptr, detail::WorkType::Ignore));
343 }
344
345 void flush_global_queue_() noexcept {
346 local_queue_.push_back(std::move(global_queue_));
347 }
348
349 static void prep_msg_ring_(int ring_fd, io_uring_sqe *sqe,
350 uintptr_t data) noexcept {
351 io_uring_prep_msg_ring(sqe, ring_fd, 0, data, 0);
352 io_uring_sqe_set_data64(
353 sqe, detail::encode_work(nullptr, detail::WorkType::Schedule));
354 }
355
356 static void prep_cancel_(io_uring_sqe *sqe, uintptr_t data) noexcept {
357 io_uring_prep_cancel64(sqe, data, 0);
358 io_uring_sqe_set_data64(
359 sqe, detail::encode_work(nullptr, detail::WorkType::Ignore));
360 io_uring_sqe_set_flags(sqe, IOSQE_CQE_SKIP_SUCCESS);
361 }
362
363 void flush_ring_() noexcept {
364 auto r = ring_.reap_completions(
365 [this](io_uring_cqe *cqe) { process_cqe_(cqe); });
366 if (r < 0) {
367 detail::panic_on(std::format("io_uring_peek_cqe: {}",
368 std::strerror(static_cast<int>(-r))));
369 }
370 }
371
372 void flush_ring_wait_() noexcept {
373 auto r = ring_.reap_completions_wait(
374 [this](io_uring_cqe *cqe) { process_cqe_(cqe); });
375 if (r < 0) {
376 detail::panic_on(std::format("io_uring_submit_and_wait: {}",
377 std::strerror(static_cast<int>(-r))));
378 }
379 }
380
381 void process_cqe_(io_uring_cqe *cqe) noexcept {
382 auto [data, type] = detail::decode_work(io_uring_cqe_get_data64(cqe));
383
384 if (type == detail::WorkType::Ignore) {
385 // No-op
386 assert(cqe->res != -EINVAL); // If EINVAL, something is wrong
387 } else if (type == detail::WorkType::Schedule) {
388 if (data == nullptr) {
389 if (cqe->res < 0) {
390 detail::panic_on(std::format("io_uring_prep_msg_ring: {}",
391 std::strerror(-cqe->res)));
392 }
393 resume_work_internal();
394 } else {
395 auto *work = static_cast<detail::WorkInvoker *>(data);
396 detail::tsan_acquire(work);
397 (*work)();
398 }
399 } else if (type == detail::WorkType::Cancel) {
400 detail::CancelRequest *request =
401 static_cast<detail::CancelRequest *>(data);
402 detail::tsan_acquire(request);
403 io_uring_sqe *sqe = ring_.get_sqe();
404 prep_cancel_(sqe, request->data());
405 request->notify();
406 } else if (type == detail::WorkType::Common) {
407 auto *handle = static_cast<detail::OpFinishHandleBase *>(data);
408 auto op_finish = handle->handle(cqe);
409 if (op_finish) {
410 resume_work_internal();
411 }
412 } else {
413 detail::unreachable();
414 }
415 }
416
417private:
418 using WorkListQueue =
419 detail::IntrusiveSingleList<detail::WorkInvoker,
420 &detail::WorkInvoker::work_queue_entry_>;
421
422 // Global state
423 std::mutex mutex_;
424 WorkListQueue global_queue_;
425 std::atomic_bool exit_allowed_ = false;
426 std::atomic_bool ring_enabled_ = false;
427
428 // Local state
429 WorkListQueue local_queue_;
430 detail::Ring ring_;
431 size_t pending_works_ = 0;
432 size_t tick_count_ = 0;
433 std::thread::id run_thread_id_;
434
435 // Configurable parameters
436 size_t event_interval_ = 61;
437 bool disable_register_ring_fd_ = false;
438
439 FdTable fd_table_;
440 BufferTable buffer_table_;
441 RingSettings settings_;
442 detail::IdPool<uint16_t> bgid_pool_;
443};
444
451inline auto &current_runtime() noexcept {
452 return *detail::Context::current().runtime();
453}
454
455} // namespace condy
void run()
Run the runtime event loop in the current thread.
Definition runtime.hpp:144
auto & buffer_table() noexcept
Get the buffer table of the runtime.
Definition runtime.hpp:206
auto & fd_table() noexcept
Get the file descriptor table of the runtime.
Definition runtime.hpp:200
void allow_exit() noexcept
Allow the runtime to exit when there are no pending works.
Definition runtime.hpp:131
Runtime(const RuntimeOptions &options={})
Construct a new Runtime object.
Definition runtime.hpp:42
auto & settings() noexcept
Get the ring settings of the runtime.
Definition runtime.hpp:212
Runtime type for running the io_uring event loop.
Intrusive single-linked and double-linked list implementations.
Polymorphic invocation utilities.
The main namespace for the Condy library.
Definition condy.hpp:37
auto & current_runtime() noexcept
Get the current runtime.
Definition runtime.hpp:451
Wrapper classes for liburing interfaces.
io_uring settings management classes.
Internal utility classes and functions used by Condy.