Condy v1.9
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 <limits>
22#include <stdexcept>
23#include <sys/mman.h>
24#include <sys/types.h>
25#include <vector>
26
27namespace condy {
28
38struct BufferInfo {
42 uint16_t bid;
46 uint16_t num_buffers;
47};
48
49namespace detail {
50
51class BundledProvidedBufferQueue {
52protected:
53 BundledProvidedBufferQueue(Runtime *runtime, uint32_t capacity,
54 unsigned int flags)
55 : runtime_(runtime), capacity_(std::bit_ceil(capacity)),
56 mask_(io_uring_buf_ring_mask(capacity_)), buf_lens_(capacity_, 0),
57 br_flags_(flags) {
58 bool ok = false;
59 auto d = detail::defer([&]() {
60 if (!ok) {
61 cleanup_();
62 }
63 });
64
65 auto &bgid_pool = runtime_->bgid_pool_internal();
66
67 bgid_ = bgid_pool.allocate();
68
69 int err = 0;
70 br_ = io_uring_setup_buf_ring(runtime_->ring_internal().ring(),
71 capacity_, bgid_, br_flags_, &err);
72 if (br_ == nullptr) [[unlikely]] {
73 throw detail::make_system_error("io_uring_setup_buf_ring", -err);
74 }
75
76 ok = true;
77 }
78
79 ~BundledProvidedBufferQueue() { cleanup_(); }
80
81public:
82 CONDY_DELETE_COPY_MOVE(BundledProvidedBufferQueue);
83
84public:
88 size_t size() const noexcept { return size_; }
89
93 size_t capacity() const noexcept { return capacity_; }
94
105 template <BufferLike Buffer> uint16_t push(const Buffer &buffer) {
106 if (size_ >= capacity_) [[unlikely]] {
107 throw std::logic_error("Capacity exceeded");
108 }
109
110 uint16_t bid = br_->tail & mask_;
111 io_uring_buf_ring_add(br_, buffer.data(), buffer.size(), bid, mask_, 0);
112 buf_lens_[bid] = buffer.size();
113 io_uring_buf_ring_advance(br_, 1);
114 size_++;
115
116 return bid;
117 }
118
119public:
120 uint16_t bgid() const noexcept { return bgid_; }
121
122 BufferInfo handle_finish(io_uring_cqe *cqe) noexcept {
123 assert(cqe != nullptr);
124 int32_t res = cqe->res;
125 uint32_t flags = cqe->flags;
126
127 if (!(flags & IORING_CQE_F_BUFFER)) {
128 return BufferInfo{0, 0};
129 }
130
131 assert(res > 0);
132
133 BufferInfo result = {
134 .bid = static_cast<uint16_t>(flags >> IORING_CQE_BUFFER_SHIFT),
135 .num_buffers = 0,
136 };
137
138 bool is_incr = false;
139#if CONDY_URING_VERSION_GE(2, 8) // >= 2.8
140 is_incr = br_flags_ & IOU_PBUF_RING_INC;
141#endif
142
143 uint16_t idx = result.bid;
144 int64_t bytes = res;
145 while (bytes > 0) {
146 uint16_t curr_bid = idx & mask_;
147 uint32_t buf_len;
148 if (is_incr) {
149 buf_len = std::min<uint32_t>(bytes, buf_lens_[curr_bid]);
150 buf_lens_[curr_bid] -= buf_len;
151 } else {
152 buf_len = std::exchange(buf_lens_[curr_bid], 0);
153 }
154 bytes -= buf_len;
155 if (buf_lens_[curr_bid] == 0) {
156 result.num_buffers++;
157 }
158 idx++;
159 }
160 assert(size_ >= result.num_buffers);
161 size_ -= result.num_buffers;
162
163 return result;
164 }
165
166private:
167 void cleanup_() noexcept {
168 if (br_ != nullptr) {
169 [[maybe_unused]] int r = io_uring_free_buf_ring(
170 runtime_->ring_internal().ring(), br_, capacity_, bgid_);
171 assert(r == 0);
172 br_ = nullptr;
173 }
174 if (bgid_ != std::numeric_limits<uint16_t>::max()) {
175 runtime_->bgid_pool_internal().recycle(bgid_);
176 }
177 }
178
179private:
180 Runtime *runtime_;
181 io_uring_buf_ring *br_ = nullptr;
182 uint32_t size_ = 0;
183 uint32_t capacity_;
184 int mask_;
185 uint16_t bgid_ = std::numeric_limits<uint16_t>::max();
186 std::vector<uint32_t> buf_lens_;
187 unsigned int br_flags_;
188};
189
190} // namespace detail
191
203class ProvidedBufferQueue : public detail::BundledProvidedBufferQueue {
204public:
211 ProvidedBufferQueue(uint32_t capacity, unsigned int flags = 0)
212 : ProvidedBufferQueue(*detail::Context::current().runtime(), capacity,
213 flags) {}
214
223 ProvidedBufferQueue(Runtime &runtime, uint32_t capacity,
224 unsigned int flags = 0)
225 : BundledProvidedBufferQueue(&runtime, capacity, flags) {}
226
227 BufferInfo handle_finish(io_uring_cqe *cqe) noexcept {
228 assert(cqe != nullptr);
229 auto result = BundledProvidedBufferQueue::handle_finish(cqe);
230 assert(result.num_buffers <= 1);
231 return result;
232 }
233};
234
235namespace detail {
236class BundledProvidedBufferPool;
237}
238
248 : public detail::ManagedBuffer<detail::BundledProvidedBufferPool> {
249public:
250 using Base = detail::ManagedBuffer<detail::BundledProvidedBufferPool>;
251 using Base::Base;
252};
253
254namespace detail {
255
256class BundledProvidedBufferPool {
257protected:
258 BundledProvidedBufferPool(Runtime *runtime, void *buffer_data,
259 uint32_t num_buffers, size_t buffer_size,
260 unsigned int flags, bool owns_buffers)
261 : runtime_(runtime), buffers_base_(static_cast<char *>(buffer_data)),
262 num_buffers_(std::bit_ceil(num_buffers)),
263 mask_(io_uring_buf_ring_mask(num_buffers_)),
264 buffer_size_(buffer_size), curr_buf_len_(buffer_size),
265 br_flags_(flags), owns_buffers_(owns_buffers) {
266 bool ok = false;
267 auto d = detail::defer([&]() {
268 if (!ok) {
269 cleanup_();
270 }
271 });
272
273 auto &bgid_pool = runtime_->bgid_pool_internal();
274
275 bgid_ = bgid_pool.allocate();
276
277 int err = 0;
278 br_ = io_uring_setup_buf_ring(runtime_->ring_internal().ring(),
279 num_buffers_, bgid_, br_flags_, &err);
280 if (br_ == nullptr) [[unlikely]] {
281 throw detail::make_system_error("io_uring_setup_buf_ring", -err);
282 }
283
284 for (size_t bid = 0; bid < num_buffers_; bid++) {
285 char *ptr = buffers_base_ + bid * buffer_size_;
286 io_uring_buf_ring_add(br_, ptr, buffer_size_, bid, mask_,
287 static_cast<int>(bid));
288 }
289 io_uring_buf_ring_advance(br_, static_cast<int>(num_buffers_));
290
291 ok = true;
292 }
293
294 ~BundledProvidedBufferPool() { cleanup_(); }
295
296public:
297 CONDY_DELETE_COPY_MOVE(BundledProvidedBufferPool);
298
299public:
303 size_t capacity() const noexcept { return num_buffers_; }
304
308 size_t buffer_size() const noexcept { return buffer_size_; }
309
310public:
311 uint16_t bgid() const noexcept { return bgid_; }
312
313 std::vector<ProvidedBuffer> handle_finish(io_uring_cqe *cqe) noexcept {
314 assert(cqe != nullptr);
315 int32_t res = cqe->res;
316 uint32_t flags = cqe->flags;
317 std::vector<ProvidedBuffer> buffers;
318
319 if (!(flags & IORING_CQE_F_BUFFER)) {
320 return buffers;
321 }
322
323 assert(res > 0);
324
325 bool is_incr = false;
326#if CONDY_URING_VERSION_GE(2, 8) // >= 2.8
327 is_incr = br_flags_ & IOU_PBUF_RING_INC;
328#endif
329
330 int64_t bytes = res;
331 while (bytes > 0) {
332 auto *buf_ptr = curr_io_uring_buf_();
333 uint16_t bid = buf_ptr->bid;
334
335 char *data = get_buffer_(bid) + (buffer_size_ - curr_buf_len_);
336 uint32_t buf_len;
337 if (is_incr) {
338 buf_len = std::min<uint32_t>(bytes, curr_buf_len_);
339 curr_buf_len_ -= buf_len;
340 } else {
341 buf_len = std::exchange(curr_buf_len_, 0);
342 }
343 bytes -= buf_len;
344 if (curr_buf_len_ == 0) {
345 buffers.emplace_back(data, buf_len, this);
346 advance_io_uring_buf_();
347 curr_buf_len_ = buffer_size_;
348 } else {
349 buffers.emplace_back(data, buf_len, nullptr);
350 }
351 }
352
353 return buffers;
354 }
355
356 void add_buffer_back(void *ptr, [[maybe_unused]] size_t size) noexcept {
357 assert(size <= buffer_size_);
358 assert(ptr >= buffers_base_);
359 size_t offset = static_cast<char *>(ptr) - buffers_base_;
360 size_t bid = offset / buffer_size_;
361 assert(bid < num_buffers_);
362 char *buffer_ptr = buffers_base_ + bid * buffer_size_;
363 io_uring_buf_ring_add(br_, buffer_ptr, buffer_size_, bid, mask_, 0);
364 io_uring_buf_ring_advance(br_, 1);
365 }
366
367private:
368 char *get_buffer_(uint16_t bid) const noexcept {
369 return buffers_base_ + static_cast<size_t>(bid) * buffer_size_;
370 }
371
372 io_uring_buf *curr_io_uring_buf_() noexcept {
373 return &br_->bufs[br_head_ & mask_];
374 }
375
376 void advance_io_uring_buf_() noexcept { br_head_++; }
377
378 void cleanup_() noexcept {
379 [[maybe_unused]] int r;
380 if (br_ != nullptr) {
381 r = io_uring_free_buf_ring(runtime_->ring_internal().ring(), br_,
382 num_buffers_, bgid_);
383 assert(r == 0);
384 br_ = nullptr;
385 }
386 if (bgid_ != std::numeric_limits<uint16_t>::max()) {
387 runtime_->bgid_pool_internal().recycle(bgid_);
388 }
389 if (owns_buffers_) {
390 r = munmap(buffers_base_,
391 static_cast<size_t>(num_buffers_) * buffer_size_);
392 assert(r == 0);
393 owns_buffers_ = false;
394 buffers_base_ = nullptr;
395 }
396 }
397
398private:
399 Runtime *runtime_;
400 io_uring_buf_ring *br_ = nullptr;
401 char *buffers_base_ = nullptr;
402 uint32_t num_buffers_;
403 int mask_;
404 uint32_t buffer_size_;
405 uint32_t curr_buf_len_;
406 uint16_t bgid_ = std::numeric_limits<uint16_t>::max();
407 uint16_t br_head_ = 0;
408 unsigned int br_flags_;
409 bool owns_buffers_;
410};
411
412} // namespace detail
413
426class ProvidedBufferPool : public detail::BundledProvidedBufferPool {
427public:
435 ProvidedBufferPool(uint32_t num_buffers, size_t buffer_size,
436 unsigned int flags = 0)
437 : ProvidedBufferPool(*detail::Context::current().runtime(), num_buffers,
438 buffer_size, flags) {}
439
449 ProvidedBufferPool(Runtime &runtime, uint32_t num_buffers,
450 size_t buffer_size, unsigned int flags = 0)
451 : BundledProvidedBufferPool(
452 &runtime,
453 alloc_buffer_data_(std::bit_ceil(num_buffers) * buffer_size),
454 num_buffers, buffer_size, flags, true) {}
455
463 ProvidedBufferPool(void *buffer_data, uint32_t num_buffers,
464 size_t buffer_size, unsigned int flags = 0)
465 : ProvidedBufferPool(*detail::Context::current().runtime(), buffer_data,
466 num_buffers, buffer_size, flags) {}
467
477 ProvidedBufferPool(Runtime &runtime, void *buffer_data,
478 uint32_t num_buffers, size_t buffer_size,
479 unsigned int flags = 0)
480 : BundledProvidedBufferPool(&runtime, buffer_data, num_buffers,
481 buffer_size, flags, false) {}
482
483public:
484 ProvidedBuffer handle_finish(io_uring_cqe *cqe) noexcept {
485 assert(cqe != nullptr);
486 auto buffers = BundledProvidedBufferPool::handle_finish(cqe);
487 if (buffers.empty()) {
488 return ProvidedBuffer();
489 }
490 assert(buffers.size() == 1);
491 return std::move(buffers[0]);
492 }
493
494private:
495 static void *alloc_buffer_data_(size_t buf_size) {
496 void *buf_data = mmap(nullptr, buf_size, PROT_READ | PROT_WRITE,
497 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
498 if (buf_data == MAP_FAILED) [[unlikely]] {
499 throw detail::make_system_error("mmap");
500 }
501 return buf_data;
502 }
503};
504
515 return static_cast<detail::BundledProvidedBufferPool &>(buffer);
516}
517
525 return static_cast<detail::BundledProvidedBufferQueue &>(buffer);
526}
527
528} // 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:38
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.