24#ifdef CONDY_DETAIL_HAS_EXECUTION
42template <
typename T,
size_t N = 2>
class Channel {
52 std::lock_guard<std::mutex> lock(mutex_);
57 CONDY_DELETE_COPY_MOVE(Channel);
60 class [[nodiscard]] MovePushSenderImpl;
61 class [[nodiscard]] CopyPushSenderImpl;
62 class [[nodiscard]] PopSenderImpl;
72 requires std::is_same_v<std::remove_cvref_t<U>, T>
74 std::lock_guard<std::mutex> lock(mutex_);
78 if (try_push_inner_(std::forward<U>(item))) {
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())};
98 return {-EAGAIN, T()};
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");
107 if (try_push_inner_(std::move(item))) [[likely]] {
114 new (std::nothrow) FakePushFinishHandle(std::move(item));
117 detail::panic_on(
"Allocation failed for PushFinishHandle");
119 assert(pop_awaiters_.empty());
120 push_awaiters_.push_back(fake_handle);
123#ifdef CONDY_DETAIL_HAS_EXECUTION
124 using MovePushSender = detail::StandardSender<MovePushSenderImpl>;
126 using MovePushSender = MovePushSenderImpl;
140 MovePushSender
push(T &&item)
noexcept {
return {*
this, std::move(item)}; }
142#ifdef CONDY_DETAIL_HAS_EXECUTION
143 using CopyPushSender = detail::StandardSender<CopyPushSenderImpl>;
145 using CopyPushSender = CopyPushSenderImpl;
154 CopyPushSender
push(
const T &item)
noexcept
155 requires std::copy_constructible<T>
157 return {*
this, item};
160#ifdef CONDY_DETAIL_HAS_EXECUTION
161 using PopSender = detail::StandardSender<PopSenderImpl>;
163 using PopSender = PopSenderImpl;
171 PopSender
pop() noexcept {
return {*
this}; }
176 size_t capacity() const noexcept {
return buffer_.capacity(); }
183 std::lock_guard<std::mutex> lock(mutex_);
184 return size_inner_();
192 std::lock_guard<std::mutex> lock(mutex_);
193 return empty_inner_();
201 std::lock_guard<std::mutex> lock(mutex_);
214 std::lock_guard<std::mutex> lock(mutex_);
219 class PushFinishHandleBase;
220 template <
typename Receiver>
class PushFinishHandle;
221 class FakePushFinishHandle;
223 class PopFinishHandleBase;
224 template <
typename Receiver>
class PopFinishHandle;
226 int32_t request_push_(PushFinishHandleBase *finish_handle)
noexcept {
227 std::lock_guard<std::mutex> lock(mutex_);
231 if (try_push_inner_(std::move(finish_handle->get_item()))) {
234 assert(pop_awaiters_.empty());
235 push_awaiters_.push_back(finish_handle);
239 bool cancel_push_(PushFinishHandleBase *finish_handle)
noexcept {
240 std::lock_guard<std::mutex> lock(mutex_);
241 return push_awaiters_.remove(finish_handle);
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())};
251 assert(push_awaiters_.empty());
253 return {-EPIPE, T()};
255 pop_awaiters_.push_back(finish_handle);
256 return {-EAGAIN, T()};
259 bool cancel_pop_(PopFinishHandleBase *finish_handle)
noexcept {
260 std::lock_guard<std::mutex> lock(mutex_);
261 return pop_awaiters_.remove(finish_handle);
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();
275 if (!full_inner_()) {
276 push_inner_(std::forward<U>(item));
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));
291 if (!empty_inner_()) {
292 T result = pop_inner_();
298 T pop_and_push_(T item)
noexcept {
302 T result = pop_inner_();
303 push_inner_(std::move(item));
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));
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();
326 bool no_buffer_() const noexcept {
return buffer_.capacity() == 0; }
328 bool empty_inner_() const noexcept {
return size_inner_() == 0; }
330 bool full_inner_() const noexcept {
331 return size_inner_() == buffer_.capacity();
334 void push_close_inner_() noexcept {
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();
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();
355 void destruct_all_() noexcept {
356 while (!empty_inner_()) {
359 assert(head_ == tail_);
362 size_t size_inner_() const noexcept {
return tail_ - head_; }
365 template <
typename Handle>
367 detail::IntrusiveDoubleList<Handle, &Handle::link_entry_>;
369 mutable std::mutex mutex_;
370 HandleList<PushFinishHandleBase> push_awaiters_;
371 HandleList<PopFinishHandleBase> pop_awaiters_;
374 detail::SmallArray<detail::RawStorage<T>, N> buffer_;
375 bool closed_ =
false;
378template <
typename T,
size_t N>
379class Channel<T, N>::PushFinishHandleBase :
public detail::WorkInvoker {
381 PushFinishHandleBase(T &item) : item_(item) {}
383 void schedule() noexcept {
384 if (runtime_ ==
nullptr) [[unlikely]] {
386 auto *this_fake =
static_cast<FakePushFinishHandle *
>(
this);
389 runtime_->schedule_internal(
this);
393 T &get_item() noexcept {
return item_; }
395 void set_result(int32_t result)
noexcept { result_ = result; }
398 detail::DoubleLinkEntry link_entry_;
401 Runtime *runtime_ =
nullptr;
403 int32_t result_ = -ENOTRECOVERABLE;
406template <
typename T,
size_t N>
407template <
typename Receiver>
408class Channel<T, N>::PushFinishHandle
409 :
public detail::InvokerAdapter<PushFinishHandle<Receiver>,
410 PushFinishHandleBase> {
412 using Base = detail::InvokerAdapter<PushFinishHandle<Receiver>,
413 PushFinishHandleBase>;
415 PushFinishHandle(
Channel &channel, T &item, Receiver receiver)
416 : Base(item), channel_(channel), receiver_(std::move(receiver)) {}
418 void start(Runtime *runtime)
noexcept {
419 this->runtime_ = runtime;
420 int32_t r = channel_.request_push_(
this);
422 std::move(receiver_)(r);
425 runtime->pend_work_internal();
427 auto stop_token = receiver_.get_stop_token();
428 if (stop_token.stop_possible()) {
429 stop_callback_.emplace(std::move(stop_token), Cancellation{
this});
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_);
441 void cancel_() noexcept {
442 if (channel_.cancel_push_(
this)) {
444 assert(this->result_ == -ENOTRECOVERABLE);
445 this->result_ = -ECANCELED;
446 assert(this->runtime_ !=
nullptr);
447 this->runtime_->schedule_internal(
this);
451 struct Cancellation {
452 PushFinishHandle *self;
453 void operator()() noexcept { self->cancel_(); }
456 using StopCallbackType =
457 detail::stop_callback_t<detail::stop_token_t<Receiver>, Cancellation>;
462 std::optional<StopCallbackType> stop_callback_;
465template <
typename T,
size_t N>
466class Channel<T, N>::FakePushFinishHandle :
public PushFinishHandleBase {
468 FakePushFinishHandle(T &&item)
469 : PushFinishHandleBase(item_copy_), item_copy_(std::move(item)) {}
475template <
typename T,
size_t N>
476class Channel<T, N>::PopFinishHandleBase :
public detail::WorkInvoker {
478 void schedule() noexcept {
479 assert(runtime_ !=
nullptr);
480 runtime_->schedule_internal(
this);
483 void set_result(std::pair<int32_t, T> result)
noexcept {
484 result_ = std::move(result);
488 detail::DoubleLinkEntry link_entry_;
491 Runtime *runtime_ =
nullptr;
493 std::pair<int32_t, T> result_ = {-ENOTRECOVERABLE, T()};
496template <
typename T,
size_t N>
497template <
typename Receiver>
498class Channel<T, N>::PopFinishHandle
499 :
public detail::InvokerAdapter<PopFinishHandle<Receiver>,
500 PopFinishHandleBase> {
502 PopFinishHandle(
Channel &channel, Receiver receiver)
503 : channel_(channel), receiver_(std::move(receiver)) {}
505 void start(Runtime *runtime)
noexcept {
506 this->runtime_ = runtime;
507 auto item = channel_.request_pop_(
this);
510 std::move(receiver_)(std::move(item));
513 runtime->pend_work_internal();
515 auto stop_token = receiver_.get_stop_token();
516 if (stop_token.stop_possible()) {
517 stop_callback_.emplace(std::move(stop_token), Cancellation{
this});
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_));
529 void cancel_() noexcept {
530 if (channel_.cancel_pop_(
this)) {
532 assert(this->result_.first == -ENOTRECOVERABLE);
533 this->result_.first = -ECANCELED;
534 assert(this->runtime_ !=
nullptr);
535 this->runtime_->schedule_internal(
this);
539 struct Cancellation {
540 PopFinishHandle *self;
541 void operator()() noexcept { self->cancel_(); }
544 using StopCallbackType =
545 detail::stop_callback_t<detail::stop_token_t<Receiver>, Cancellation>;
550 std::optional<StopCallbackType> stop_callback_;
553template <
typename T,
size_t N>
class Channel<T, N>::MovePushSenderImpl {
555 using CondySender = void;
556 using ReturnType = int32_t;
558 MovePushSenderImpl(
Channel &channel, T &&item)
559 : channel_(channel), item_(std::move(item)) {}
561 template <
typename Receiver>
auto connect_impl(Receiver receiver)
noexcept {
562 return OperationState<Receiver>(channel_, std::move(item_),
563 std::move(receiver));
567 template <
typename Receiver>
569 :
public Channel<T, N>::template PushFinishHandle<Receiver> {
572 typename Channel<T, N>::template PushFinishHandle<Receiver>;
573 OperationState(Channel &channel, T &&item, Receiver receiver)
574 : Base(channel, item, std::move(receiver)) {}
576 void start(
unsigned int )
noexcept {
577 auto *runtime = detail::Context::current().runtime();
578 Base::start(runtime);
586template <
typename T,
size_t N>
class Channel<T, N>::CopyPushSenderImpl {
588 using CondySender = void;
589 using ReturnType = int32_t;
591 CopyPushSenderImpl(
Channel &channel,
const T &item)
592 : channel_(channel), item_(item) {}
594 template <
typename Receiver>
auto connect_impl(Receiver receiver)
noexcept {
595 return OperationState<Receiver>(channel_, item_, std::move(receiver));
599 template <
typename Receiver>
601 :
public Channel<T, N>::template PushFinishHandle<Receiver> {
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) {
609 void start(
unsigned int )
noexcept {
610 auto *runtime = detail::Context::current().runtime();
611 Base::start(runtime);
622template <
typename T,
size_t N>
class Channel<T, N>::PopSenderImpl {
624 using CondySender = void;
625 using ReturnType = std::pair<int32_t, T>;
627 PopSenderImpl(
Channel &channel) : channel_(channel) {}
629 template <
typename Receiver>
auto connect_impl(Receiver receiver)
noexcept {
630 return OperationState<Receiver>(channel_, std::move(receiver));
634 template <
typename Receiver>
636 :
public Channel<T, N>::template PopFinishHandle<Receiver> {
638 using Base =
typename Channel<T, N>::template PopFinishHandle<Receiver>;
641 void start(
unsigned int )
noexcept {
642 auto *runtime = detail::Context::current().runtime();
643 Base::start(runtime);
Thread-safe bounded channel for communication and synchronization.
void push_close() noexcept
Close the channel.
MovePushSender push(T &&item) noexcept
Push an item into the channel, awaiting if necessary.
std::pair< int32_t, T > try_pop() noexcept
Try to pop an item from the channel.
bool empty() const noexcept
Check if the channel is empty.
CopyPushSender push(const T &item) noexcept
Push an item into the channel, awaiting if necessary.
PopSender pop() noexcept
Pop an item from the channel, awaiting if necessary.
size_t size() const noexcept
Get the current size of the channel.
bool is_closed() const noexcept
Check if the channel is closed.
size_t capacity() const noexcept
Get the capacity of the channel.
Channel(size_t capacity)
Construct a new Channel object.
int32_t try_push(U &&item) noexcept
Try to push an item into the channel.
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.
Runtime type for running the io_uring event loop.
Internal utility classes and functions used by Condy.