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