Condy v1.8
C++ Asynchronous System Call Layer for Linux
Loading...
Searching...
No Matches
channel.hpp
Go to the documentation of this file.
1
7
8#pragma once
9
15#include "condy/runtime.hpp"
16#include <bit>
17#include <cerrno>
18#include <cstddef>
19#include <cstdint>
20#include <new>
21#include <optional>
22#include <type_traits>
23
24namespace condy {
25
38template <typename T, size_t N = 2> class Channel {
39public:
46 : buffer_(capacity ? std::bit_ceil(capacity) : 0) {}
47 ~Channel() {
48 std::lock_guard<std::mutex> lock(mutex_);
49 push_close_inner_();
50 destruct_all_();
51 }
52
53 CONDY_DELETE_COPY_MOVE(Channel);
54
55public:
62 template <typename U>
63 requires std::is_same_v<std::remove_cvref_t<U>, T>
64 int32_t try_push(U &&item) noexcept {
65 std::lock_guard<std::mutex> lock(mutex_);
66 if (closed_) {
67 return -EPIPE;
68 }
69 if (try_push_inner_(std::forward<U>(item))) {
70 return 0;
71 }
72 return -EAGAIN;
73 }
74
81 std::pair<int32_t, T> try_pop() noexcept {
82 std::lock_guard<std::mutex> lock(mutex_);
83 auto item = try_pop_inner_();
84 if (item.has_value()) {
85 return {0, std::move(item.value())};
86 } else if (closed_) {
87 return {-EPIPE, T()};
88 } else {
89 return {-EAGAIN, T()};
90 }
91 }
92
93 void force_push(T item) noexcept {
94 std::lock_guard<std::mutex> lock(mutex_);
95 if (closed_) [[unlikely]] {
96 detail::panic_on("Push to closed channel");
97 }
98 if (try_push_inner_(std::move(item))) [[likely]] {
99 return;
100 }
101 // This is safe because if try_push_inner_ returns false, the item has
102 // not been moved into the channel.
103 // NOLINTBEGIN(bugprone-use-after-move)
104 auto *fake_handle =
105 new (std::nothrow) FakePushFinishHandle(std::move(item));
106 // NOLINTEND(bugprone-use-after-move)
107 if (!fake_handle) {
108 detail::panic_on("Allocation failed for PushFinishHandle");
109 }
110 assert(pop_awaiters_.empty());
111 push_awaiters_.push_back(fake_handle);
112 }
113
114 class [[nodiscard]] MovePushSender;
127 MovePushSender push(T &&item) noexcept { return {*this, std::move(item)}; }
128
129 class [[nodiscard]] CopyPushSender;
137 CopyPushSender push(const T &item) noexcept
138 requires std::copy_constructible<T>
139 {
140 return {*this, item};
141 }
142
143 class [[nodiscard]] PopSender;
150 PopSender pop() noexcept { return {*this}; }
151
155 size_t capacity() const noexcept { return buffer_.capacity(); }
156
161 size_t size() const noexcept {
162 std::lock_guard<std::mutex> lock(mutex_);
163 return size_inner_();
164 }
165
170 bool empty() const noexcept {
171 std::lock_guard<std::mutex> lock(mutex_);
172 return empty_inner_();
173 }
174
179 bool is_closed() const noexcept {
180 std::lock_guard<std::mutex> lock(mutex_);
181 return closed_;
182 }
183
192 void push_close() noexcept {
193 std::lock_guard<std::mutex> lock(mutex_);
194 push_close_inner_();
195 }
196
197private:
198 class PushFinishHandleBase;
199 template <typename Receiver> class PushFinishHandle;
200 class FakePushFinishHandle;
201
202 class PopFinishHandleBase;
203 template <typename Receiver> class PopFinishHandle;
204
205 int32_t request_push_(PushFinishHandleBase *finish_handle) noexcept {
206 std::lock_guard<std::mutex> lock(mutex_);
207 if (closed_) {
208 return -EPIPE;
209 }
210 if (try_push_inner_(std::move(finish_handle->get_item()))) {
211 return 0;
212 }
213 assert(pop_awaiters_.empty());
214 push_awaiters_.push_back(finish_handle);
215 return -EAGAIN;
216 }
217
218 bool cancel_push_(PushFinishHandleBase *finish_handle) noexcept {
219 std::lock_guard<std::mutex> lock(mutex_);
220 return push_awaiters_.remove(finish_handle);
221 }
222
223 std::pair<int32_t, T>
224 request_pop_(PopFinishHandleBase *finish_handle) noexcept {
225 std::lock_guard<std::mutex> lock(mutex_);
226 auto result = try_pop_inner_();
227 if (result.has_value()) {
228 return {0, std::move(result.value())};
229 }
230 assert(push_awaiters_.empty());
231 if (closed_) {
232 return {-EPIPE, T()};
233 }
234 pop_awaiters_.push_back(finish_handle);
235 return {-EAGAIN, T()};
236 }
237
238 bool cancel_pop_(PopFinishHandleBase *finish_handle) noexcept {
239 std::lock_guard<std::mutex> lock(mutex_);
240 return pop_awaiters_.remove(finish_handle);
241 }
242
243private:
244 template <typename U>
245 requires std::is_same_v<std::remove_cvref_t<U>, T>
246 bool try_push_inner_(U &&item) noexcept {
247 if (!pop_awaiters_.empty()) {
248 assert(empty_inner_());
249 auto *pop_handle = pop_awaiters_.pop_front();
250 pop_handle->set_result({0, std::forward<U>(item)});
251 pop_handle->schedule();
252 return true;
253 }
254 if (!full_inner_()) {
255 push_inner_(std::forward<U>(item));
256 return true;
257 }
258 return false;
259 }
260
261 std::optional<T> try_pop_inner_() noexcept {
262 if (!push_awaiters_.empty()) {
263 assert(full_inner_());
264 auto *push_handle = push_awaiters_.pop_front();
265 T item = std::move(push_handle->get_item());
266 push_handle->set_result(0);
267 push_handle->schedule();
268 return pop_and_push_(std::move(item));
269 }
270 if (!empty_inner_()) {
271 T result = pop_inner_();
272 return result;
273 }
274 return std::nullopt;
275 }
276
277 T pop_and_push_(T item) noexcept {
278 if (no_buffer_()) {
279 return item;
280 } else {
281 T result = pop_inner_();
282 push_inner_(std::move(item));
283 return result;
284 }
285 }
286
287 template <typename U>
288 requires std::is_same_v<std::remove_cvref_t<U>, T>
289 void push_inner_(U &&item) noexcept {
290 assert(!full_inner_());
291 auto mask = buffer_.capacity() - 1;
292 buffer_[tail_ & mask].construct(std::forward<U>(item));
293 tail_++;
294 }
295
296 T pop_inner_() noexcept {
297 assert(!empty_inner_());
298 auto mask = buffer_.capacity() - 1;
299 T item = std::move(buffer_[head_ & mask].get());
300 buffer_[head_ & mask].destroy();
301 head_++;
302 return item;
303 }
304
305 bool no_buffer_() const noexcept { return buffer_.capacity() == 0; }
306
307 bool empty_inner_() const noexcept { return size_inner_() == 0; }
308
309 bool full_inner_() const noexcept {
310 return size_inner_() == buffer_.capacity();
311 }
312
313 void push_close_inner_() noexcept {
314 if (closed_) {
315 return;
316 }
317 closed_ = true;
318 // Cancel all pending pop awaiters
319 PopFinishHandleBase *pop_handle = nullptr;
320 while ((pop_handle = pop_awaiters_.pop_front()) != nullptr) {
321 assert(empty_inner_());
322 pop_handle->set_result({-EPIPE, T()});
323 pop_handle->schedule();
324 }
325 // Cancel all pending push awaiters
326 PushFinishHandleBase *push_handle = nullptr;
327 while ((push_handle = push_awaiters_.pop_front()) != nullptr) {
328 assert(full_inner_());
329 push_handle->set_result(-EPIPE);
330 push_handle->schedule();
331 }
332 }
333
334 void destruct_all_() noexcept {
335 while (!empty_inner_()) {
336 pop_inner_();
337 }
338 assert(head_ == tail_);
339 }
340
341 size_t size_inner_() const noexcept { return tail_ - head_; }
342
343private:
344 template <typename Handle>
345 using HandleList =
346 detail::IntrusiveDoubleList<Handle, &Handle::link_entry_>;
347
348 mutable std::mutex mutex_;
349 HandleList<PushFinishHandleBase> push_awaiters_;
350 HandleList<PopFinishHandleBase> pop_awaiters_;
351 size_t head_ = 0;
352 size_t tail_ = 0;
353 detail::SmallArray<detail::RawStorage<T>, N> buffer_;
354 bool closed_ = false;
355};
356
357template <typename T, size_t N>
358class Channel<T, N>::PushFinishHandleBase : public detail::WorkInvoker {
359public:
360 PushFinishHandleBase(T &item) : item_(item) {}
361
362 void schedule() noexcept {
363 if (runtime_ == nullptr) [[unlikely]] {
364 // Fake handle, no need to schedule
365 auto *this_fake = static_cast<FakePushFinishHandle *>(this);
366 delete this_fake;
367 } else {
368 runtime_->schedule_internal(this);
369 }
370 }
371
372 T &get_item() noexcept { return item_; }
373
374 void set_result(int32_t result) noexcept { result_ = result; }
375
376public:
377 detail::DoubleLinkEntry link_entry_;
378
379public:
380 Runtime *runtime_ = nullptr;
381 T &item_;
382 int32_t result_ = -ENOTRECOVERABLE; // Internal error if not set
383};
384
385template <typename T, size_t N>
386template <typename Receiver>
387class Channel<T, N>::PushFinishHandle
388 : public detail::InvokerAdapter<PushFinishHandle<Receiver>,
389 PushFinishHandleBase> {
390public:
391 using Base = detail::InvokerAdapter<PushFinishHandle<Receiver>,
392 PushFinishHandleBase>;
393
394 PushFinishHandle(Channel &channel, T &item, Receiver receiver)
395 : Base(item), channel_(channel), receiver_(std::move(receiver)) {}
396
397 void start(Runtime *runtime) noexcept {
398 this->runtime_ = runtime;
399 int32_t r = channel_.request_push_(this);
400 if (r != -EAGAIN) {
401 std::move(receiver_)(r);
402 return;
403 }
404 runtime->pend_work_internal();
405
406 auto stop_token = receiver_.get_stop_token();
407 if (stop_token.stop_possible()) {
408 stop_callback_.emplace(std::move(stop_token), Cancellation{this});
409 }
410 }
411
412 void invoke() noexcept {
413 stop_callback_.reset();
414 assert(this->runtime_ != nullptr);
415 this->runtime_->resume_work_internal();
416 std::move(receiver_)(this->result_);
417 }
418
419private:
420 void cancel_() noexcept {
421 if (channel_.cancel_push_(this)) {
422 // Successfully canceled
423 assert(this->result_ == -ENOTRECOVERABLE);
424 this->result_ = -ECANCELED;
425 assert(this->runtime_ != nullptr);
426 this->runtime_->schedule_internal(this);
427 }
428 }
429
430 struct Cancellation {
431 PushFinishHandle *self;
432 void operator()() noexcept { self->cancel_(); }
433 };
434
435 using StopCallbackType =
436 detail::stop_callback_t<detail::stop_token_t<Receiver>, Cancellation>;
437
438private:
439 Channel &channel_;
440 Receiver receiver_;
441 std::optional<StopCallbackType> stop_callback_;
442};
443
444template <typename T, size_t N>
445class Channel<T, N>::FakePushFinishHandle : public PushFinishHandleBase {
446public:
447 FakePushFinishHandle(T &&item)
448 : PushFinishHandleBase(item_copy_), item_copy_(std::move(item)) {}
449
450private:
451 T item_copy_;
452};
453
454template <typename T, size_t N>
455class Channel<T, N>::PopFinishHandleBase : public detail::WorkInvoker {
456public:
457 void schedule() noexcept {
458 assert(runtime_ != nullptr);
459 runtime_->schedule_internal(this);
460 }
461
462 void set_result(std::pair<int32_t, T> result) noexcept {
463 result_ = std::move(result);
464 }
465
466public:
467 detail::DoubleLinkEntry link_entry_;
468
469protected:
470 Runtime *runtime_ = nullptr;
471 // Internal error if not set
472 std::pair<int32_t, T> result_ = {-ENOTRECOVERABLE, T()};
473};
474
475template <typename T, size_t N>
476template <typename Receiver>
477class Channel<T, N>::PopFinishHandle
478 : public detail::InvokerAdapter<PopFinishHandle<Receiver>,
479 PopFinishHandleBase> {
480public:
481 PopFinishHandle(Channel &channel, Receiver receiver)
482 : channel_(channel), receiver_(std::move(receiver)) {}
483
484 void start(Runtime *runtime) noexcept {
485 this->runtime_ = runtime;
486 auto item = channel_.request_pop_(this);
487 auto r = item.first;
488 if (r != -EAGAIN) {
489 std::move(receiver_)(std::move(item));
490 return;
491 }
492 runtime->pend_work_internal();
493
494 auto stop_token = receiver_.get_stop_token();
495 if (stop_token.stop_possible()) {
496 stop_callback_.emplace(std::move(stop_token), Cancellation{this});
497 }
498 }
499
500 void invoke() noexcept {
501 stop_callback_.reset();
502 assert(this->runtime_ != nullptr);
503 this->runtime_->resume_work_internal();
504 std::move(receiver_)(std::move(this->result_));
505 }
506
507private:
508 void cancel_() noexcept {
509 if (channel_.cancel_pop_(this)) {
510 // Successfully canceled
511 assert(this->result_.first == -ENOTRECOVERABLE);
512 this->result_.first = -ECANCELED;
513 assert(this->runtime_ != nullptr);
514 this->runtime_->schedule_internal(this);
515 }
516 }
517
518 struct Cancellation {
519 PopFinishHandle *self;
520 void operator()() noexcept { self->cancel_(); }
521 };
522
523 using StopCallbackType =
524 detail::stop_callback_t<detail::stop_token_t<Receiver>, Cancellation>;
525
526private:
527 Channel &channel_;
528 Receiver receiver_;
529 std::optional<StopCallbackType> stop_callback_;
530};
531
532template <typename T, size_t N> class Channel<T, N>::MovePushSender {
533public:
534 using CondySender = void;
535 using ReturnType = int32_t;
536
537 MovePushSender(Channel &channel, T &&item)
538 : channel_(channel), item_(std::move(item)) {}
539
540 template <typename Receiver> auto connect_impl(Receiver receiver) noexcept {
541 return OperationState<Receiver>(channel_, std::move(item_),
542 std::move(receiver));
543 }
544
545private:
546 template <typename Receiver>
547 class OperationState
548 : public Channel<T, N>::template PushFinishHandle<Receiver> {
549 public:
550 using Base =
551 typename Channel<T, N>::template PushFinishHandle<Receiver>;
552 OperationState(Channel &channel, T &&item, Receiver receiver)
553 : Base(channel, item, std::move(receiver)) {}
554
555 void start(unsigned int /*flags*/) noexcept {
556 auto *runtime = detail::Context::current().runtime();
557 Base::start(runtime);
558 }
559 };
560
561 Channel &channel_;
562 T &&item_;
563};
564
565template <typename T, size_t N> class Channel<T, N>::CopyPushSender {
566public:
567 using CondySender = void;
568 using ReturnType = int32_t;
569
570 CopyPushSender(Channel &channel, const T &item)
571 : channel_(channel), item_(item) {}
572
573 template <typename Receiver> auto connect_impl(Receiver receiver) noexcept {
574 return OperationState<Receiver>(channel_, item_, std::move(receiver));
575 }
576
577private:
578 template <typename Receiver>
579 class OperationState
580 : public Channel<T, N>::template PushFinishHandle<Receiver> {
581 public:
582 using Base =
583 typename Channel<T, N>::template PushFinishHandle<Receiver>;
584 OperationState(Channel &channel, const T &item, Receiver receiver)
585 : Base(channel, item_copy_, std::move(receiver)), item_copy_(item) {
586 }
587
588 void start(unsigned int /*flags*/) noexcept {
589 auto *runtime = detail::Context::current().runtime();
590 Base::start(runtime);
591 }
592
593 private:
594 T item_copy_;
595 };
596
597 Channel &channel_;
598 const T &item_;
599};
600
601template <typename T, size_t N> class Channel<T, N>::PopSender {
602public:
603 using CondySender = void;
604 using ReturnType = std::pair<int32_t, T>;
605
606 PopSender(Channel &channel) : channel_(channel) {}
607
608 template <typename Receiver> auto connect_impl(Receiver receiver) noexcept {
609 return OperationState<Receiver>(channel_, std::move(receiver));
610 }
611
612private:
613 template <typename Receiver>
614 class OperationState
615 : public Channel<T, N>::template PopFinishHandle<Receiver> {
616 public:
617 using Base = typename Channel<T, N>::template PopFinishHandle<Receiver>;
618 using Base::Base;
619
620 void start(unsigned int /*flags*/) noexcept {
621 auto *runtime = detail::Context::current().runtime();
622 Base::start(runtime);
623 }
624 };
625
626 Channel &channel_;
627};
628
629} // namespace condy
Thread-safe bounded channel for communication and synchronization.
Definition channel.hpp:38
void push_close() noexcept
Close the channel.
Definition channel.hpp:192
MovePushSender push(T &&item) noexcept
Push an item into the channel, awaiting if necessary.
Definition channel.hpp:127
std::pair< int32_t, T > try_pop() noexcept
Try to pop an item from the channel.
Definition channel.hpp:81
bool empty() const noexcept
Check if the channel is empty.
Definition channel.hpp:170
CopyPushSender push(const T &item) noexcept
Push an item into the channel, awaiting if necessary.
Definition channel.hpp:137
PopSender pop() noexcept
Pop an item from the channel, awaiting if necessary.
Definition channel.hpp:150
size_t size() const noexcept
Get the current size of the channel.
Definition channel.hpp:161
bool is_closed() const noexcept
Check if the channel is closed.
Definition channel.hpp:179
size_t capacity() const noexcept
Get the capacity of the channel.
Definition channel.hpp:155
Channel(size_t capacity)
Construct a new Channel object.
Definition channel.hpp:45
int32_t try_push(U &&item) noexcept
Try to push an item into the channel.
Definition channel.hpp:64
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.