Condy v1.9
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 // Potential address reuse problem?
95 schedule_msg_ring_(curr_runtime, detail::encode_work_ptr(
96 data, detail::WorkType::Cancel));
97 if (curr_runtime != nullptr) {
98 // Ensure the cancel msg is submitted.
99 curr_runtime->ring_.submit();
100 }
101 }
102
103 void pend_work_internal() noexcept {
104 assert(detail::Context::current().runtime() == this);
105 pending_works_++;
106 }
107
108 void resume_work_internal() noexcept {
109 assert(detail::Context::current().runtime() == this);
110 pending_works_--;
111 }
112
113 auto &bgid_pool_internal() noexcept { return bgid_pool_; }
114
115 auto &ring_internal() noexcept { return ring_; }
116
117public:
125 void allow_exit() noexcept {
126 exit_allowed_.store(true, std::memory_order_release);
127 wakeup_();
128 }
129
138 void run() {
139 detail::Context::current().init(this);
140 auto d = detail::defer([] { detail::Context::current().reset(); });
141
142 if (run_thread_id_ == std::thread::id()) {
143 int r = io_uring_enable_rings(ring_.ring());
144 if (r < 0) {
145 throw detail::make_system_error("io_uring_enable_rings", -r);
146 }
147
148 run_thread_id_ = std::this_thread::get_id();
149
150 {
151 std::lock_guard<std::mutex> lock(mutex_);
152 flush_global_queue_();
153 ring_enabled_.store(true, std::memory_order_relaxed);
154 }
155 } else if (run_thread_id_ == std::this_thread::get_id()) {
156 flush_ring_();
157 } else {
158 throw std::runtime_error(
159 "Runtime::run() can only be called from the same thread");
160 }
161
162 if (!disable_register_ring_fd_) {
163 io_uring_register_ring_fd(ring_.ring());
164 }
165 auto d2 = detail::defer([&] {
166 if (!disable_register_ring_fd_) {
167 io_uring_unregister_ring_fd(ring_.ring());
168 }
169 });
170
171 auto d3 = detail::defer([&] {
172 tick_count_ = 0;
173 exit_allowed_.store(false, std::memory_order_release);
174 });
175 while (true) {
176 tick_count_++;
177
178 if (tick_count_ >= event_interval_) {
179 tick_count_ = 0;
180 flush_ring_();
181 }
182
183 if (auto *work = local_queue_.pop_front()) {
184 (*work)();
185 continue;
186 }
187
188 if (pending_works_ == 0 &&
189 exit_allowed_.load(std::memory_order_acquire)) {
190 break;
191 }
192 flush_ring_wait_();
193 }
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 CONDY_URING_VERSION_GE(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 CONDY_URING_VERSION_GE(2, 13) // >= 2.13
272 if (options.enable_sqe_mixed_) {
273 params.flags |= IORING_SETUP_SQE_MIXED;
274 }
275#endif
276
277#if CONDY_URING_VERSION_GE(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 CONDY_URING_VERSION_GE(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 CONDY_URING_VERSION_GE(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 if (curr_runtime != nullptr) {
344 // Ensure the wakeup msg is submitted. Since user may call
345 // thread.join() after allow_exit()
346 curr_runtime->ring_.submit();
347 }
348 }
349
350 void flush_global_queue_() noexcept {
351 local_queue_.push_back(std::move(global_queue_));
352 }
353
354 static void prep_msg_ring_(int ring_fd, io_uring_sqe *sqe,
355 uintptr_t data) noexcept {
356 io_uring_prep_msg_ring(sqe, ring_fd, 0, data, 0);
357 io_uring_sqe_set_data64(
358 sqe, detail::encode_work(nullptr, detail::WorkType::Schedule));
359 }
360
361 static void prep_cancel_(io_uring_sqe *sqe, uintptr_t data) noexcept {
362 io_uring_prep_cancel64(sqe, data, 0);
363 io_uring_sqe_set_data64(
364 sqe, detail::encode_work(nullptr, detail::WorkType::Ignore));
365 io_uring_sqe_set_flags(sqe, IOSQE_CQE_SKIP_SUCCESS);
366 }
367
368 void flush_ring_() noexcept {
369 auto r = ring_.reap_completions(
370 [this](io_uring_cqe *cqe) { process_cqe_(cqe); });
371 if (r < 0) {
372 detail::panic_on(std::format("io_uring_peek_cqe: {}",
373 std::strerror(static_cast<int>(-r))));
374 }
375 }
376
377 void flush_ring_wait_() noexcept {
378 auto r = ring_.reap_completions_wait(
379 [this](io_uring_cqe *cqe) { process_cqe_(cqe); });
380 if (r < 0) {
381 detail::panic_on(std::format("io_uring_submit_and_wait: {}",
382 std::strerror(static_cast<int>(-r))));
383 }
384 }
385
386 void process_cqe_(io_uring_cqe *cqe) noexcept {
387 auto [data, type] = detail::decode_work(io_uring_cqe_get_data64(cqe));
388
389 if (type == detail::WorkType::Ignore) {
390 // No-op
391 assert(cqe->res != -EINVAL); // If EINVAL, something is wrong
392 } else if (type == detail::WorkType::Schedule) {
393 if (data == nullptr) {
394 if (cqe->res < 0) {
395 detail::panic_on(std::format("io_uring_prep_msg_ring: {}",
396 std::strerror(-cqe->res)));
397 }
398 resume_work_internal();
399 } else {
400 auto *work = static_cast<detail::WorkInvoker *>(data);
401 detail::tsan_acquire(work);
402 (*work)();
403 }
404 } else if (type == detail::WorkType::Cancel) {
405 io_uring_sqe *sqe = ring_.get_sqe();
406 prep_cancel_(sqe, reinterpret_cast<uintptr_t>(data));
407 } else if (type == detail::WorkType::Common) {
408 auto *handle = static_cast<detail::OpFinishHandleBase *>(data);
409 auto op_finish = handle->handle(cqe);
410 if (op_finish) {
411 resume_work_internal();
412 }
413 } else {
414 detail::unreachable();
415 }
416 }
417
418private:
419 using WorkListQueue =
420 detail::IntrusiveSingleList<detail::WorkInvoker,
421 &detail::WorkInvoker::work_queue_entry_>;
422
423 // Global state
424 std::mutex mutex_;
425 WorkListQueue global_queue_;
426 std::atomic_bool exit_allowed_ = false;
427 std::atomic_bool ring_enabled_ = false;
428
429 // Local state
430 WorkListQueue local_queue_;
431 detail::Ring ring_;
432 size_t pending_works_ = 0;
433 size_t tick_count_ = 0;
434 std::thread::id run_thread_id_;
435
436 // Configurable parameters
437 size_t event_interval_ = 61;
438 bool disable_register_ring_fd_ = false;
439
440 FdTable fd_table_;
441 BufferTable buffer_table_;
442 RingSettings settings_;
443 detail::IdPool<uint16_t> bgid_pool_;
444};
445
452inline auto &current_runtime() noexcept {
453 return *detail::Context::current().runtime();
454}
455
456} // namespace condy
void run()
Run the runtime event loop in the current thread.
Definition runtime.hpp:138
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:125
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:38
auto & current_runtime() noexcept
Get the current runtime.
Definition runtime.hpp:452
Wrapper classes for liburing interfaces.
io_uring settings management classes.
Internal utility classes and functions used by Condy.