/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License Version 2.0 with LLVM Exceptions * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * https://llvm.org/LICENSE.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #if !UNIFEX_NO_EPOLL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace unifex { namespace linuxos { class io_epoll_context { public: class schedule_sender; class schedule_at_sender; template class schedule_after_sender; class scheduler; class read_sender; class write_sender; class async_reader; class async_writer; io_epoll_context(); ~io_epoll_context(); template void run(StopToken stopToken); scheduler get_scheduler() noexcept; private: struct operation_base { ~operation_base() { UNIFEX_ASSERT(enqueued_.load() == 0); } operation_base() noexcept : enqueued_(0), next_(nullptr), execute_(nullptr) {} std::atomic enqueued_; operation_base* next_; void (*execute_)(operation_base*) noexcept; }; struct completion_base : operation_base { }; struct stop_operation : operation_base { stop_operation() noexcept { this->execute_ = [](operation_base * op) noexcept { static_cast(op)->shouldStop_ = true; }; } bool shouldStop_ = false; }; using time_point = linuxos::monotonic_clock::time_point; struct schedule_at_operation : operation_base { explicit schedule_at_operation( io_epoll_context& context, const time_point& dueTime, bool canBeCancelled) noexcept : context_(context), dueTime_(dueTime), canBeCancelled_(canBeCancelled) {} schedule_at_operation* timerNext_; schedule_at_operation* timerPrev_; io_epoll_context& context_; time_point dueTime_; bool canBeCancelled_; static constexpr std::uint32_t timer_elapsed_flag = 1; static constexpr std::uint32_t cancel_pending_flag = 2; std::atomic state_ = 0; }; using operation_queue = intrusive_queue; using timer_heap = intrusive_heap< schedule_at_operation, &schedule_at_operation::timerNext_, &schedule_at_operation::timerPrev_, time_point, &schedule_at_operation::dueTime_>; bool is_running_on_io_thread() const noexcept; void run_impl(const bool& shouldStop); void schedule_impl(operation_base* op); void schedule_local(operation_base* op) noexcept; void schedule_local(operation_queue ops) noexcept; void schedule_remote(operation_base* op) noexcept; // Insert the timer operation into the queue of timers. // Must be called from the I/O thread. void schedule_at_impl(schedule_at_operation* op) noexcept; // Execute all ready-to-run items on the local queue. // Will not run other items that were enqueued during the execution of the // items that were already enqueued. // This bounds the amount of work to a finite amount. void execute_pending_local() noexcept; // Check if any completion queue items are available and if so add them // to the local queue. void acquire_completion_queue_items(); // collect the contents of the remote queue and pass them to schedule_local // // Returns true if successful. // // Returns false if some other thread concurrently enqueued work to the remote queue. bool try_schedule_local_remote_queue_contents() noexcept; // Signal the remote queue eventfd. // // This should only be called after trying to enqueue() work // to the remoteQueue and being told that the I/O thread is // inactive. void signal_remote_queue(); void remove_timer(schedule_at_operation* op) noexcept; void update_timers() noexcept; bool try_submit_timer_io(const time_point& dueTime) noexcept; void* timer_user_data() const { return const_cast(static_cast(&timers_)); } //////// // Data that does not change once initialised. // Resources safe_file_descriptor epollFd_; safe_file_descriptor timerFd_; safe_file_descriptor remoteQueueEventFd_; /////////////////// // Data that is modified by I/O thread // Local queue for operations that are ready to execute. operation_queue localQueue_; // Set of operations waiting to be executed at a specific time. timer_heap timers_; // The time that the current timer operation submitted to the kernel // is due to elapse. std::optional currentDueTime_; bool remoteQueueReadSubmitted_ = false; bool timersAreDirty_ = false; ////////////////// // Data that is modified by remote threads // Queue of operations enqueued by remote threads. atomic_intrusive_queue remoteQueue_; }; template void io_epoll_context::run(StopToken stopToken) { stop_operation stopOp; auto onStopRequested = [&] { this->schedule_impl(&stopOp); }; typename StopToken::template callback_type stopCallback{std::move(stopToken), std::move(onStopRequested)}; run_impl(stopOp.shouldStop_); } class io_epoll_context::schedule_sender { template class operation : private operation_base { public: void start() noexcept { UNIFEX_TRY { context_.schedule_impl(this); } UNIFEX_CATCH (...) { unifex::set_error( static_cast(receiver_), std::current_exception()); } } private: friend schedule_sender; template explicit operation(io_epoll_context& context, Receiver2&& r) : context_(context), receiver_((Receiver2 &&) r) { this->execute_ = &execute_impl; } static void execute_impl(operation_base* p) noexcept { operation& op = *static_cast(p); if constexpr (!is_stop_never_possible_v>) { if (get_stop_token(op.receiver_).stop_requested()) { unifex::set_done(static_cast(op.receiver_)); return; } } if constexpr (is_nothrow_receiver_of_v) { unifex::set_value(static_cast(op.receiver_)); } else { UNIFEX_TRY { unifex::set_value(static_cast(op.receiver_)); } UNIFEX_CATCH (...) { unifex::set_error( static_cast(op.receiver_), std::current_exception()); } } } io_epoll_context& context_; Receiver receiver_; }; public: template < template class Variant, template class Tuple> using value_types = Variant>; template