38template <
typename T,
size_t N = 2>
class Channel {
48 std::lock_guard<std::mutex> lock(mutex_);
53 CONDY_DELETE_COPY_MOVE(Channel);
63 requires std::is_same_v<std::remove_cvref_t<U>, T>
65 std::lock_guard<std::mutex> lock(mutex_);
69 if (try_push_inner_(std::forward<U>(item))) {
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())};
89 return {-EAGAIN, T()};
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");
98 if (try_push_inner_(std::move(item))) [[likely]] {
105 new (std::nothrow) FakePushFinishHandle(std::move(item));
108 detail::panic_on(
"Allocation failed for PushFinishHandle");
110 assert(pop_awaiters_.empty());
111 push_awaiters_.push_back(fake_handle);
114 class [[nodiscard]] MovePushSender;
127 MovePushSender
push(T &&item)
noexcept {
return {*
this, std::move(item)}; }
129 class [[nodiscard]] CopyPushSender;
137 CopyPushSender
push(
const T &item)
noexcept
138 requires std::copy_constructible<T>
140 return {*
this, item};
143 class [[nodiscard]] PopSender;
150 PopSender
pop() noexcept {
return {*
this}; }
155 size_t capacity() const noexcept {
return buffer_.capacity(); }
162 std::lock_guard<std::mutex> lock(mutex_);
163 return size_inner_();
171 std::lock_guard<std::mutex> lock(mutex_);
172 return empty_inner_();
180 std::lock_guard<std::mutex> lock(mutex_);
193 std::lock_guard<std::mutex> lock(mutex_);
198 class PushFinishHandleBase;
199 template <
typename Receiver>
class PushFinishHandle;
200 class FakePushFinishHandle;
202 class PopFinishHandleBase;
203 template <
typename Receiver>
class PopFinishHandle;
205 int32_t request_push_(PushFinishHandleBase *finish_handle)
noexcept {
206 std::lock_guard<std::mutex> lock(mutex_);
210 if (try_push_inner_(std::move(finish_handle->get_item()))) {
213 assert(pop_awaiters_.empty());
214 push_awaiters_.push_back(finish_handle);
218 bool cancel_push_(PushFinishHandleBase *finish_handle)
noexcept {
219 std::lock_guard<std::mutex> lock(mutex_);
220 return push_awaiters_.remove(finish_handle);
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())};
230 assert(push_awaiters_.empty());
232 return {-EPIPE, T()};
234 pop_awaiters_.push_back(finish_handle);
235 return {-EAGAIN, T()};
238 bool cancel_pop_(PopFinishHandleBase *finish_handle)
noexcept {
239 std::lock_guard<std::mutex> lock(mutex_);
240 return pop_awaiters_.remove(finish_handle);
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();
254 if (!full_inner_()) {
255 push_inner_(std::forward<U>(item));
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));
270 if (!empty_inner_()) {
271 T result = pop_inner_();
277 T pop_and_push_(T item)
noexcept {
281 T result = pop_inner_();
282 push_inner_(std::move(item));
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));
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();
305 bool no_buffer_() const noexcept {
return buffer_.capacity() == 0; }
307 bool empty_inner_() const noexcept {
return size_inner_() == 0; }
309 bool full_inner_() const noexcept {
310 return size_inner_() == buffer_.capacity();
313 void push_close_inner_() noexcept {
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();
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();
334 void destruct_all_() noexcept {
335 while (!empty_inner_()) {
338 assert(head_ == tail_);
341 size_t size_inner_() const noexcept {
return tail_ - head_; }
344 template <
typename Handle>
346 detail::IntrusiveDoubleList<Handle, &Handle::link_entry_>;
348 mutable std::mutex mutex_;
349 HandleList<PushFinishHandleBase> push_awaiters_;
350 HandleList<PopFinishHandleBase> pop_awaiters_;
353 detail::SmallArray<detail::RawStorage<T>, N> buffer_;
354 bool closed_ =
false;
357template <
typename T,
size_t N>
358class Channel<T, N>::PushFinishHandleBase :
public detail::WorkInvoker {
360 PushFinishHandleBase(T &item) : item_(item) {}
362 void schedule() noexcept {
363 if (runtime_ ==
nullptr) [[unlikely]] {
365 auto *this_fake =
static_cast<FakePushFinishHandle *
>(
this);
368 runtime_->schedule_internal(
this);
372 T &get_item() noexcept {
return item_; }
374 void set_result(int32_t result)
noexcept { result_ = result; }
377 detail::DoubleLinkEntry link_entry_;
380 Runtime *runtime_ =
nullptr;
382 int32_t result_ = -ENOTRECOVERABLE;
385template <
typename T,
size_t N>
386template <
typename Receiver>
387class Channel<T, N>::PushFinishHandle
388 :
public detail::InvokerAdapter<PushFinishHandle<Receiver>,
389 PushFinishHandleBase> {
391 using Base = detail::InvokerAdapter<PushFinishHandle<Receiver>,
392 PushFinishHandleBase>;
394 PushFinishHandle(
Channel &channel, T &item, Receiver receiver)
395 : Base(item), channel_(channel), receiver_(std::move(receiver)) {}
397 void start(Runtime *runtime)
noexcept {
398 this->runtime_ = runtime;
399 int32_t r = channel_.request_push_(
this);
401 std::move(receiver_)(r);
404 runtime->pend_work_internal();
406 auto stop_token = receiver_.get_stop_token();
407 if (stop_token.stop_possible()) {
408 stop_callback_.emplace(std::move(stop_token), Cancellation{
this});
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_);
420 void cancel_() noexcept {
421 if (channel_.cancel_push_(
this)) {
423 assert(this->result_ == -ENOTRECOVERABLE);
424 this->result_ = -ECANCELED;
425 assert(this->runtime_ !=
nullptr);
426 this->runtime_->schedule_internal(
this);
430 struct Cancellation {
431 PushFinishHandle *self;
432 void operator()() noexcept { self->cancel_(); }
435 using StopCallbackType =
436 detail::stop_callback_t<detail::stop_token_t<Receiver>, Cancellation>;
441 std::optional<StopCallbackType> stop_callback_;
444template <
typename T,
size_t N>
445class Channel<T, N>::FakePushFinishHandle :
public PushFinishHandleBase {
447 FakePushFinishHandle(T &&item)
448 : PushFinishHandleBase(item_copy_), item_copy_(std::move(item)) {}
454template <
typename T,
size_t N>
455class Channel<T, N>::PopFinishHandleBase :
public detail::WorkInvoker {
457 void schedule() noexcept {
458 assert(runtime_ !=
nullptr);
459 runtime_->schedule_internal(
this);
462 void set_result(std::pair<int32_t, T> result)
noexcept {
463 result_ = std::move(result);
467 detail::DoubleLinkEntry link_entry_;
470 Runtime *runtime_ =
nullptr;
472 std::pair<int32_t, T> result_ = {-ENOTRECOVERABLE, T()};
475template <
typename T,
size_t N>
476template <
typename Receiver>
477class Channel<T, N>::PopFinishHandle
478 :
public detail::InvokerAdapter<PopFinishHandle<Receiver>,
479 PopFinishHandleBase> {
481 PopFinishHandle(
Channel &channel, Receiver receiver)
482 : channel_(channel), receiver_(std::move(receiver)) {}
484 void start(Runtime *runtime)
noexcept {
485 this->runtime_ = runtime;
486 auto item = channel_.request_pop_(
this);
489 std::move(receiver_)(std::move(item));
492 runtime->pend_work_internal();
494 auto stop_token = receiver_.get_stop_token();
495 if (stop_token.stop_possible()) {
496 stop_callback_.emplace(std::move(stop_token), Cancellation{
this});
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_));
508 void cancel_() noexcept {
509 if (channel_.cancel_pop_(
this)) {
511 assert(this->result_.first == -ENOTRECOVERABLE);
512 this->result_.first = -ECANCELED;
513 assert(this->runtime_ !=
nullptr);
514 this->runtime_->schedule_internal(
this);
518 struct Cancellation {
519 PopFinishHandle *self;
520 void operator()() noexcept { self->cancel_(); }
523 using StopCallbackType =
524 detail::stop_callback_t<detail::stop_token_t<Receiver>, Cancellation>;
529 std::optional<StopCallbackType> stop_callback_;
532template <
typename T,
size_t N>
class Channel<T, N>::MovePushSender {
534 using CondySender = void;
535 using ReturnType = int32_t;
537 MovePushSender(
Channel &channel, T &&item)
538 : channel_(channel), item_(std::move(item)) {}
540 template <
typename Receiver>
auto connect_impl(Receiver receiver)
noexcept {
541 return OperationState<Receiver>(channel_, std::move(item_),
542 std::move(receiver));
546 template <
typename Receiver>
548 :
public Channel<T, N>::template PushFinishHandle<Receiver> {
551 typename Channel<T, N>::template PushFinishHandle<Receiver>;
552 OperationState(Channel &channel, T &&item, Receiver receiver)
553 : Base(channel, item, std::move(receiver)) {}
555 void start(
unsigned int )
noexcept {
556 auto *runtime = detail::Context::current().runtime();
557 Base::start(runtime);
565template <
typename T,
size_t N>
class Channel<T, N>::CopyPushSender {
567 using CondySender = void;
568 using ReturnType = int32_t;
570 CopyPushSender(
Channel &channel,
const T &item)
571 : channel_(channel), item_(item) {}
573 template <
typename Receiver>
auto connect_impl(Receiver receiver)
noexcept {
574 return OperationState<Receiver>(channel_, item_, std::move(receiver));
578 template <
typename Receiver>
580 :
public Channel<T, N>::template PushFinishHandle<Receiver> {
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) {
588 void start(
unsigned int )
noexcept {
589 auto *runtime = detail::Context::current().runtime();
590 Base::start(runtime);
601template <
typename T,
size_t N>
class Channel<T, N>::PopSender {
603 using CondySender = void;
604 using ReturnType = std::pair<int32_t, T>;
606 PopSender(
Channel &channel) : channel_(channel) {}
608 template <
typename Receiver>
auto connect_impl(Receiver receiver)
noexcept {
609 return OperationState<Receiver>(channel_, std::move(receiver));
613 template <
typename Receiver>
615 :
public Channel<T, N>::template PopFinishHandle<Receiver> {
617 using Base =
typename Channel<T, N>::template PopFinishHandle<Receiver>;
620 void start(
unsigned int )
noexcept {
621 auto *runtime = detail::Context::current().runtime();
622 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.
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.