Condy v1.8
C++ Asynchronous System Call Layer for Linux
Loading...
Searching...
No Matches
provided_buffers.hpp
Go to the documentation of this file.
1
7
8#pragma once
9
10#include "condy/concepts.hpp"
11#include "condy/condy_uring.hpp"
14#include "condy/detail/ring.hpp"
16#include "condy/runtime.hpp"
17#include <algorithm>
18#include <bit>
19#include <cstddef>
20#include <cstdint>
21#include <stdexcept>
22#include <sys/mman.h>
23#include <sys/types.h>
24#include <vector>
25
26namespace condy {
27
37struct BufferInfo {
41 uint16_t bid;
45 uint16_t num_buffers;
46};
47
48namespace detail {
49
50class BundledProvidedBufferQueue {
51protected:
52 BundledProvidedBufferQueue(Runtime *runtime, uint32_t capacity,
53 unsigned int flags)
54 : runtime_(runtime), capacity_(std::bit_ceil(capacity)),
55 mask_(io_uring_buf_ring_mask(capacity_)), buf_lens_(capacity_, 0),
56 br_flags_(flags) {
57 auto &bgid_pool = runtime_->bgid_pool_internal();
58
59 bgid_ = bgid_pool.allocate();
60 auto d = detail::defer([&]() { bgid_pool.recycle(bgid_); });
61
62 int err = 0;
63 br_ = io_uring_setup_buf_ring(runtime_->ring_internal().ring(),
64 capacity_, bgid_, br_flags_, &err);
65 if (br_ == nullptr) [[unlikely]] {
66 throw detail::make_system_error("io_uring_setup_buf_ring", -err);
67 }
68
69 d.dismiss();
70 }
71
72 ~BundledProvidedBufferQueue() {
73 assert(br_ != nullptr);
74 [[maybe_unused]] int r = io_uring_free_buf_ring(
75 runtime_->ring_internal().ring(), br_, capacity_, bgid_);
76 assert(r == 0);
77 if (r == 0) {
78 runtime_->bgid_pool_internal().recycle(bgid_);
79 }
80 }
81
82public:
83 CONDY_DELETE_COPY_MOVE(BundledProvidedBufferQueue);
84
85public:
89 size_t size() const noexcept { return size_; }
90
94 size_t capacity() const noexcept { return capacity_; }
95
106 template <BufferLike Buffer> uint16_t push(const Buffer &buffer) {
107 if (size_ >= capacity_) [[unlikely]] {
108 throw std::logic_error("Capacity exceeded");
109 }
110
111 uint16_t bid = br_->tail & mask_;
112 io_uring_buf_ring_add(br_, buffer.data(), buffer.size(), bid, mask_, 0);
113 buf_lens_[bid] = buffer.size();
114 io_uring_buf_ring_advance(br_, 1);
115 size_++;
116
117 return bid;
118 }
119
120public:
121 uint16_t bgid() const noexcept { return bgid_; }
122
123 BufferInfo handle_finish(io_uring_cqe *cqe) noexcept {
124 assert(cqe != nullptr);
125 int32_t res = cqe->res;
126 uint32_t flags = cqe->flags;
127
128 if (!(flags & IORING_CQE_F_BUFFER)) {
129 return BufferInfo{0, 0};
130 }
131
132 assert(res > 0);
133
134 BufferInfo result = {
135 .bid = static_cast<uint16_t>(flags >> IORING_CQE_BUFFER_SHIFT),
136 .num_buffers = 0,
137 };
138
139 bool is_incr = false;
140#if !IO_URING_CHECK_VERSION(2, 8) // >= 2.8
141 is_incr = br_flags_ & IOU_PBUF_RING_INC;
142#endif
143
144 uint16_t idx = result.bid;
145 int64_t bytes = res;
146 while (bytes > 0) {
147 uint16_t curr_bid = idx & mask_;
148 uint32_t buf_len;
149 if (is_incr) {
150 buf_len = std::min<uint32_t>(bytes, buf_lens_[curr_bid]);
151 buf_lens_[curr_bid] -= buf_len;
152 } else {
153 buf_len = std::exchange(buf_lens_[curr_bid], 0);
154 }
155 bytes -= buf_len;
156 if (buf_lens_[curr_bid] == 0) {
157 result.num_buffers++;
158 }
159 idx++;
160 }
161 assert(size_ >= result.num_buffers);
162 size_ -= result.num_buffers;
163
164 return result;
165 }
166
167private:
168 Runtime *runtime_;
169 io_uring_buf_ring *br_ = nullptr;
170 uint32_t size_ = 0;
171 uint32_t capacity_;
172 int mask_;
173 uint16_t bgid_;
174 std::vector<uint32_t> buf_lens_;
175 unsigned int br_flags_;
176};
177
178} // namespace detail
179
191class ProvidedBufferQueue : public detail::BundledProvidedBufferQueue {
192public:
199 ProvidedBufferQueue(uint32_t capacity, unsigned int flags = 0)
200 : ProvidedBufferQueue(*detail::Context::current().runtime(), capacity,
201 flags) {}
202
211 ProvidedBufferQueue(Runtime &runtime, uint32_t capacity,
212 unsigned int flags = 0)
213 : BundledProvidedBufferQueue(&runtime, capacity, flags) {}
214
215 BufferInfo handle_finish(io_uring_cqe *cqe) noexcept {
216 assert(cqe != nullptr);
217 auto result = BundledProvidedBufferQueue::handle_finish(cqe);
218 assert(result.num_buffers <= 1);
219 return result;
220 }
221};
222
223namespace detail {
224class BundledProvidedBufferPool;
225}
226
236 : public detail::ManagedBuffer<detail::BundledProvidedBufferPool> {
237public:
238 using Base = detail::ManagedBuffer<detail::BundledProvidedBufferPool>;
239 using Base::Base;
240};
241
242namespace detail {
243
244class BundledProvidedBufferPool {
245protected:
246 BundledProvidedBufferPool(Runtime *runtime, void *buffer_data,
247 uint32_t num_buffers, size_t buffer_size,
248 unsigned int flags)
249 : runtime_(runtime), buffers_base_(static_cast<char *>(buffer_data)),
250 num_buffers_(std::bit_ceil(num_buffers)),
251 mask_(io_uring_buf_ring_mask(num_buffers_)),
252 buffer_size_(buffer_size), curr_buf_len_(buffer_size),
253 br_flags_(flags) {
254 auto &bgid_pool = runtime_->bgid_pool_internal();
255
256 bgid_ = bgid_pool.allocate();
257 auto d = detail::defer([&]() { bgid_pool.recycle(bgid_); });
258
259 int err = 0;
260 br_ = io_uring_setup_buf_ring(runtime_->ring_internal().ring(),
261 num_buffers_, bgid_, br_flags_, &err);
262 if (br_ == nullptr) [[unlikely]] {
263 throw detail::make_system_error("io_uring_setup_buf_ring", -err);
264 }
265
266 for (size_t bid = 0; bid < num_buffers_; bid++) {
267 char *ptr = buffers_base_ + bid * buffer_size_;
268 io_uring_buf_ring_add(br_, ptr, buffer_size_, bid, mask_,
269 static_cast<int>(bid));
270 }
271 io_uring_buf_ring_advance(br_, static_cast<int>(num_buffers_));
272
273 d.dismiss();
274 }
275
276 ~BundledProvidedBufferPool() {
277 assert(br_ != nullptr);
278 [[maybe_unused]] int r = io_uring_free_buf_ring(
279 runtime_->ring_internal().ring(), br_, num_buffers_, bgid_);
280 assert(r == 0);
281 if (r == 0) {
282 runtime_->bgid_pool_internal().recycle(bgid_);
283 }
284 }
285
286public:
287 CONDY_DELETE_COPY_MOVE(BundledProvidedBufferPool);
288
289public:
293 size_t capacity() const noexcept { return num_buffers_; }
294
298 size_t buffer_size() const noexcept { return buffer_size_; }
299
300public:
301 uint16_t bgid() const noexcept { return bgid_; }
302
303 std::vector<ProvidedBuffer> handle_finish(io_uring_cqe *cqe) noexcept {
304 assert(cqe != nullptr);
305 int32_t res = cqe->res;
306 uint32_t flags = cqe->flags;
307 std::vector<ProvidedBuffer> buffers;
308
309 if (!(flags & IORING_CQE_F_BUFFER)) {
310 return buffers;
311 }
312
313 assert(res > 0);
314
315 bool is_incr = false;
316#if !IO_URING_CHECK_VERSION(2, 8) // >= 2.8
317 is_incr = br_flags_ & IOU_PBUF_RING_INC;
318#endif
319
320 int64_t bytes = res;
321 while (bytes > 0) {
322 auto *buf_ptr = curr_io_uring_buf_();
323 uint16_t bid = buf_ptr->bid;
324
325 char *data = get_buffer_(bid) + (buffer_size_ - curr_buf_len_);
326 uint32_t buf_len;
327 if (is_incr) {
328 buf_len = std::min<uint32_t>(bytes, curr_buf_len_);
329 curr_buf_len_ -= buf_len;
330 } else {
331 buf_len = std::exchange(curr_buf_len_, 0);
332 }
333 bytes -= buf_len;
334 if (curr_buf_len_ == 0) {
335 buffers.emplace_back(data, buf_len, this);
336 advance_io_uring_buf_();
337 curr_buf_len_ = buffer_size_;
338 } else {
339 buffers.emplace_back(data, buf_len, nullptr);
340 }
341 }
342
343 return buffers;
344 }
345
346 void add_buffer_back(void *ptr, [[maybe_unused]] size_t size) noexcept {
347 assert(size <= buffer_size_);
348 assert(ptr >= buffers_base_);
349 size_t offset = static_cast<char *>(ptr) - buffers_base_;
350 size_t bid = offset / buffer_size_;
351 assert(bid < num_buffers_);
352 char *buffer_ptr = buffers_base_ + bid * buffer_size_;
353 io_uring_buf_ring_add(br_, buffer_ptr, buffer_size_, bid, mask_, 0);
354 io_uring_buf_ring_advance(br_, 1);
355 }
356
357private:
358 char *get_buffer_(uint16_t bid) const noexcept {
359 return buffers_base_ + static_cast<size_t>(bid) * buffer_size_;
360 }
361
362 io_uring_buf *curr_io_uring_buf_() noexcept {
363 return &br_->bufs[br_head_ & mask_];
364 }
365
366 void advance_io_uring_buf_() noexcept { br_head_++; }
367
368protected:
369 Runtime *runtime_;
370 io_uring_buf_ring *br_ = nullptr;
371 char *buffers_base_ = nullptr;
372 uint32_t num_buffers_;
373 int mask_;
374 uint32_t buffer_size_;
375 uint32_t curr_buf_len_;
376 uint16_t bgid_;
377 uint16_t br_head_ = 0;
378 unsigned int br_flags_;
379};
380
381} // namespace detail
382
395class ProvidedBufferPool : public detail::BundledProvidedBufferPool {
396public:
404 ProvidedBufferPool(uint32_t num_buffers, size_t buffer_size,
405 unsigned int flags = 0)
406 : ProvidedBufferPool(*detail::Context::current().runtime(), num_buffers,
407 buffer_size, flags) {}
408
418 ProvidedBufferPool(Runtime &runtime, uint32_t num_buffers,
419 size_t buffer_size, unsigned int flags = 0)
420 : BundledProvidedBufferPool(
421 &runtime,
422 alloc_buffer_data_(std::bit_ceil(num_buffers) * buffer_size),
423 num_buffers, buffer_size, flags),
424 external_memory_(false) {}
425
433 ProvidedBufferPool(void *buffer_data, uint32_t num_buffers,
434 size_t buffer_size, unsigned int flags = 0)
435 : ProvidedBufferPool(*detail::Context::current().runtime(), buffer_data,
436 num_buffers, buffer_size, flags) {}
437
447 ProvidedBufferPool(Runtime &runtime, void *buffer_data,
448 uint32_t num_buffers, size_t buffer_size,
449 unsigned int flags = 0)
450 : BundledProvidedBufferPool(&runtime, buffer_data, num_buffers,
451 buffer_size, flags),
452 external_memory_(true) {}
453
455 if (!external_memory_) {
456 munmap(buffers_base_, capacity() * buffer_size());
457 }
458 }
459
460public:
461 ProvidedBuffer handle_finish(io_uring_cqe *cqe) noexcept {
462 assert(cqe != nullptr);
463 auto buffers = BundledProvidedBufferPool::handle_finish(cqe);
464 if (buffers.empty()) {
465 return ProvidedBuffer();
466 }
467 assert(buffers.size() == 1);
468 return std::move(buffers[0]);
469 }
470
471private:
472 static void *alloc_buffer_data_(size_t buf_size) {
473 void *buf_data = mmap(nullptr, buf_size, PROT_READ | PROT_WRITE,
474 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
475 if (buf_data == MAP_FAILED) [[unlikely]] {
476 throw detail::make_system_error("mmap");
477 }
478 return buf_data;
479 }
480
481 bool external_memory_ = false;
482};
483
494 return static_cast<detail::BundledProvidedBufferPool &>(buffer);
495}
496
504 return static_cast<detail::BundledProvidedBufferQueue &>(buffer);
505}
506
507} // namespace condy
ProvidedBufferPool(uint32_t num_buffers, size_t buffer_size, unsigned int flags=0)
Construct a new ProvidedBufferPool object in current Runtime.
ProvidedBufferPool(Runtime &runtime, uint32_t num_buffers, size_t buffer_size, unsigned int flags=0)
Construct a new Provided Buffer Pool object with specified Runtime.
ProvidedBufferPool(void *buffer_data, uint32_t num_buffers, size_t buffer_size, unsigned int flags=0)
Construct with externally provided buffer memory.
ProvidedBufferPool(Runtime &runtime, void *buffer_data, uint32_t num_buffers, size_t buffer_size, unsigned int flags=0)
Construct with externally provided buffer memory and specified Runtime.
ProvidedBufferQueue(Runtime &runtime, uint32_t capacity, unsigned int flags=0)
Construct a new Provided Buffer Queue object with specified Runtime.
ProvidedBufferQueue(uint32_t capacity, unsigned int flags=0)
Construct a new ProvidedBufferQueue object in current Runtime.
The event loop runtime for executing asynchronous.
Definition runtime.hpp:36
Basic buffer types and conversion utilities.
The main namespace for the Condy library.
Definition condy.hpp:37
auto & bundled(ProvidedBufferPool &buffer)
Get the bundled variant of a provided buffer pool. This will enable buffer bundling feature of io_uri...
MutableBuffer buffer(void *data, size_t size) noexcept
Create a buffer object from various data sources.
Definition buffers.hpp:86
Wrapper classes for liburing interfaces.
Runtime type for running the io_uring event loop.
Information about buffers consumed from a provided buffer queue.
uint16_t bid
Buffer ID of the first buffer consumed.
uint16_t num_buffers
Number of buffers consumed.
Internal utility classes and functions used by Condy.