LCOV - code coverage report
Current view: top level - corosio/native/detail/reactor - reactor_scheduler.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 98.5 % 388 382 6
Test Date: 2026-10-22 14:28:44 Functions: 98.1 % 53 52 1

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Steve Gerbino
       3                 : //
       4                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6                 : //
       7                 : // Official repository: https://github.com/cppalliance/corosio
       8                 : //
       9                 : 
      10                 : #ifndef BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_SCHEDULER_HPP
      11                 : #define BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_SCHEDULER_HPP
      12                 : 
      13                 : #include <boost/corosio/detail/config.hpp>
      14                 : #include <boost/corosio/detail/object_ref.hpp>
      15                 : #include <boost/capy/ex/execution_context.hpp>
      16                 : #include <boost/capy/ex/abandon.hpp>
      17                 : 
      18                 : #include <boost/corosio/detail/ready_queue.hpp>
      19                 : #include <boost/corosio/detail/scheduler.hpp>
      20                 : #include <boost/corosio/detail/scheduler_op.hpp>
      21                 : #include <boost/corosio/detail/thread_local_ptr.hpp>
      22                 : #include <boost/corosio/detail/timer_service.hpp>
      23                 : 
      24                 : #include <atomic>
      25                 : #include <chrono>
      26                 : #include <coroutine>
      27                 : #include <cstddef>
      28                 : #include <cstdint>
      29                 : #include <limits>
      30                 : #include <memory>
      31                 : #include <stdexcept>
      32                 : 
      33                 : #include <boost/corosio/detail/conditionally_enabled_mutex.hpp>
      34                 : #include <boost/corosio/detail/conditionally_enabled_event.hpp>
      35                 : 
      36                 : namespace boost::corosio::detail {
      37                 : 
      38                 : // Forward declarations
      39                 : class reactor_scheduler;
      40                 : 
      41                 : /** A deregistered descriptor whose owner must outlive the reactor pass
      42                 :     that may still hold it.
      43                 : 
      44                 :     An event the reactor has already read from the kernel can name a
      45                 :     descriptor that another thread closes before the reactor queues it.
      46                 :     The closing thread hands over a reference instead of dropping it, so
      47                 :     the owner is neither freed nor recycled until that pass ends.
      48                 : */
      49                 : struct reactor_retirable
      50                 : {
      51                 :     // All four fields are guarded by the scheduler's mutex.
      52                 : 
      53                 :     /// Next in the scheduler's retired list.
      54                 :     reactor_retirable* retired_next_ = nullptr;
      55                 : 
      56                 :     /// Owner reference held until the pass ends.
      57                 :     object_ref retired_ref_;
      58                 : 
      59                 :     /// Next in a settling thread's list of references to drop.
      60                 :     reactor_retirable* dropped_next_ = nullptr;
      61                 : 
      62                 :     /// Owner reference waiting to be dropped outside the mutex.
      63                 :     object_ref dropped_ref_;
      64                 : 
      65                 :     /// Move @p keep into a queued completion, if one is pending.
      66                 :     virtual void hand_off(object_ref& keep) noexcept = 0;
      67                 : 
      68                 : protected:
      69 HIT       11998 :     ~reactor_retirable() = default;
      70                 : };
      71                 : 
      72                 : /** Per-thread state for a reactor scheduler.
      73                 : 
      74                 :     Each thread running a scheduler's event loop has one of these
      75                 :     on a thread-local stack. It holds a private work queue and
      76                 :     inline completion budget for speculative I/O fast paths.
      77                 : */
      78                 : struct BOOST_COROSIO_SYMBOL_VISIBLE reactor_scheduler_context
      79                 : {
      80                 :     /// Scheduler this context belongs to.
      81                 :     reactor_scheduler const* key;
      82                 : 
      83                 :     /// Next context frame on this thread's stack.
      84                 :     reactor_scheduler_context* next;
      85                 : 
      86                 :     /// Private work queue for reduced contention.
      87                 :     ready_queue private_queue;
      88                 : 
      89                 :     /// Unflushed work count for the private queue.
      90                 :     std::int64_t private_outstanding_work;
      91                 : 
      92                 :     /// Remaining inline completions allowed this cycle.
      93                 :     int inline_budget;
      94                 : 
      95                 :     /// Maximum inline budget (adaptive, 2-16).
      96                 :     int inline_budget_max;
      97                 : 
      98                 :     /// True if no other thread absorbed queued work last cycle.
      99                 :     bool unassisted;
     100                 : 
     101                 :     /// Construct a context frame linked to @a n.
     102                 :     reactor_scheduler_context(
     103                 :         reactor_scheduler const* k, reactor_scheduler_context* n);
     104                 : };
     105                 : 
     106                 : /// Thread-local context stack for reactor schedulers.
     107                 : inline thread_local_ptr<reactor_scheduler_context> reactor_context_stack;
     108                 : 
     109                 : /// Find the context frame for a scheduler on this thread.
     110                 : inline reactor_scheduler_context*
     111          962057 : reactor_find_context(reactor_scheduler const* self) noexcept
     112                 : {
     113          962650 :     for (auto* c = reactor_context_stack.get(); c != nullptr; c = c->next)
     114                 :     {
     115          899775 :         if (c->key == self)
     116          899182 :             return c;
     117                 :     }
     118           62875 :     return nullptr;
     119                 : }
     120                 : 
     121                 : /** Non-template base for reactor-backed scheduler implementations.
     122                 : 
     123                 :     Provides the complete threading model shared by epoll, kqueue,
     124                 :     and select schedulers: signal state machine, inline completion
     125                 :     budget, work counting, run/poll methods, and the do_one event
     126                 :     loop.
     127                 : 
     128                 :     Derived classes provide platform-specific hooks by overriding:
     129                 :     - `run_task(lock, ctx)` to run the reactor poll
     130                 :     - `interrupt_reactor()` to wake a blocked reactor
     131                 : 
     132                 :     De-templated from the original CRTP design to eliminate
     133                 :     duplicate instantiations when multiple backends are compiled
     134                 :     into the same binary. Virtual dispatch for run_task (called
     135                 :     once per reactor cycle, before a blocking syscall) has
     136                 :     negligible overhead.
     137                 : 
     138                 :     @par Thread Safety
     139                 :     All public member functions are thread-safe.
     140                 : */
     141                 : class reactor_scheduler
     142                 :     : public scheduler
     143                 : {
     144                 : public:
     145                 :     using context_type = reactor_scheduler_context;
     146                 :     using mutex_type   = conditionally_enabled_mutex;
     147                 :     using lock_type    = mutex_type::scoped_lock;
     148                 :     using event_type   = conditionally_enabled_event;
     149                 : 
     150                 :     /// Post a coroutine for deferred execution.
     151                 :     void post(std::coroutine_handle<> h) const override;
     152                 : 
     153                 :     /// Post a scheduler operation for deferred execution.
     154                 :     void post(scheduler_op* h) const override;
     155                 : 
     156                 :     /** Release an owner reference once no reactor pass can hold `d`.
     157                 : 
     158                 :         Called after a descriptor is deregistered. If a reactor pass is
     159                 :         running, `keep` is parked on `d` until that pass ends. Then, or
     160                 :         at once if no pass is running, it moves to `d`'s queued
     161                 :         invocation if `d` is queued, and is dropped otherwise.
     162                 : 
     163                 :         @param d The deregistered descriptor.
     164                 :         @param keep A reference to the descriptor's owner.
     165                 :     */
     166                 :     void retire_descriptor(reactor_retirable& d, object_ref keep) const;
     167                 : 
     168                 :     /// Post a continuation for deferred execution.
     169                 :     void post(capy::continuation&) const override;
     170                 : 
     171                 :     /// Return true if called from a thread running this scheduler.
     172                 :     bool running_in_this_thread() const noexcept override;
     173                 : 
     174                 :     /// Request the scheduler to stop dispatching handlers.
     175                 :     void stop() override;
     176                 : 
     177                 :     /// Return true if the scheduler has been stopped.
     178                 :     bool stopped() const noexcept override;
     179                 : 
     180                 :     /// Reset the stopped state so `run()` can resume.
     181                 :     void restart() override;
     182                 : 
     183                 :     /// Run the event loop until no work remains.
     184                 :     std::size_t run() override;
     185                 : 
     186                 :     /// Run until one handler completes or no work remains.
     187                 :     std::size_t run_one() override;
     188                 : 
     189                 :     /// Run until one handler completes or @a usec elapses.
     190                 :     std::size_t wait_one(long usec) override;
     191                 : 
     192                 :     /// Run ready handlers without blocking.
     193                 :     std::size_t poll() override;
     194                 : 
     195                 :     /// Run at most one ready handler without blocking.
     196                 :     std::size_t poll_one() override;
     197                 : 
     198                 :     /// Increment the outstanding work count.
     199                 :     void work_started() noexcept override;
     200                 : 
     201                 :     /// Decrement the outstanding work count, stopping on zero.
     202                 :     void work_finished() noexcept override;
     203                 : 
     204                 :     /** Reset the thread's inline completion budget.
     205                 : 
     206                 :         Called at the start of each posted completion handler to
     207                 :         grant a fresh budget for speculative inline completions.
     208                 :     */
     209                 :     void reset_inline_budget() const noexcept;
     210                 : 
     211                 :     /** Consume one unit of inline budget if available.
     212                 : 
     213                 :         @return True if budget was available and consumed.
     214                 :     */
     215                 :     bool try_consume_inline_budget() const noexcept;
     216                 : 
     217                 :     /** Offset a forthcoming work_finished from work_cleanup.
     218                 : 
     219                 :         Called by descriptor_state when all I/O returned EAGAIN and
     220                 :         no handler will be executed. Must be called from a scheduler
     221                 :         thread.
     222                 :     */
     223                 :     /** Note that an op was parked outside the usual registration path.
     224                 : 
     225                 :         A reactor that builds its poll set from parked ops must rebuild
     226                 :         it; one the kernel tracks needs nothing.
     227                 :     */
     228             366 :     virtual void notify_reparked() const {}
     229                 : 
     230                 :     void compensating_work_started() const noexcept;
     231                 : 
     232                 :     /** Post an operation whose work is already counted.
     233                 : 
     234                 :         The posting thread touches nothing of the scheduler after this
     235                 :         returns, unlike a @ref post followed by @ref work_finished: once
     236                 :         posted, the operation can complete and let its context be
     237                 :         destroyed.
     238                 : 
     239                 :         @pre work_started() was called for @p op.
     240                 : 
     241                 :         @param op The operation to post.
     242                 :     */
     243                 :     void post_counted(scheduler_op* op) const;
     244                 : 
     245                 :     /** Post completed operations for deferred invocation.
     246                 : 
     247                 :         If called from a thread running this scheduler, operations
     248                 :         go to the thread's private queue (fast path). Otherwise,
     249                 :         operations are added to the global queue under mutex and a
     250                 :         waiter is signaled.
     251                 : 
     252                 :         @pre work_started() must have been called for each operation.
     253                 : 
     254                 :         @param ops Queue of operations to post.
     255                 :     */
     256                 :     void post_deferred_completions(ready_queue& ops) const;
     257                 : 
     258                 :     /** Apply runtime configuration to the scheduler.
     259                 : 
     260                 :         Called by `io_context` after construction. Values that do
     261                 :         not apply to this backend are silently ignored.
     262                 : 
     263                 :         @param max_events  Event buffer size for epoll/kqueue.
     264                 :         @param budget_init Starting inline completion budget.
     265                 :         @param budget_max  Hard ceiling on adaptive budget ramp-up.
     266                 :         @param unassisted  Budget when single-threaded.
     267                 :     */
     268                 :     virtual void configure_reactor(
     269                 :         unsigned max_events,
     270                 :         unsigned budget_init,
     271                 :         unsigned budget_max,
     272                 :         unsigned unassisted);
     273                 : 
     274                 :     /// Return the configured initial inline budget.
     275           13011 :     unsigned inline_budget_initial() const noexcept
     276                 :     {
     277           13011 :         return inline_budget_initial_;
     278                 :     }
     279                 : 
     280                 :     /// Return true when scheduler locking is disabled (fully-lockless tier).
     281            1450 :     bool scheduler_locking_disabled() const noexcept override
     282                 :     {
     283            1450 :         return scheduler_locking_disabled_;
     284                 :     }
     285                 : 
     286            4426 :     void configure_threading(threading_config cfg) noexcept override
     287                 :     {
     288            4426 :         scheduler_locking_disabled_ = !cfg.scheduler_locking;
     289                 :         // reactor_io_locking takes effect at descriptor registration (see the
     290                 :         // register_descriptor overrides), not here.
     291            4426 :         reactor_io_locking_ = cfg.reactor_io_locking;
     292            4426 :         one_thread_         = cfg.one_thread;
     293            4426 :         mutex_.set_enabled(cfg.scheduler_locking);
     294            4426 :         cond_.set_enabled(cfg.scheduler_locking);
     295            4426 :     }
     296                 : 
     297                 : protected:
     298                 :     bool scheduler_locking_disabled_ = false;
     299                 :     bool reactor_io_locking_         = true;
     300                 :     bool one_thread_                 = false;
     301                 : 
     302            4442 :     reactor_scheduler() = default;
     303                 : 
     304                 :     /// Destroy, destroying whatever was posted after the drain.
     305                 :     ~reactor_scheduler() override;
     306                 : 
     307                 :     /** Drain completed_ops during shutdown.
     308                 : 
     309                 :         Pops all operations from the global queue and destroys them,
     310                 :         skipping the task sentinel. Signals all waiting threads.
     311                 :         Derived classes call this from their shutdown() override
     312                 :         before performing platform-specific cleanup.
     313                 :     */
     314                 :     void shutdown_drain();
     315                 : 
     316                 :     /// Destroy every queued operation without running it.
     317                 :     void destroy_queued(lock_type& lock) noexcept;
     318                 : 
     319                 :     /// RAII guard that re-inserts the task sentinel after `run_task`.
     320                 :     struct task_cleanup
     321                 :     {
     322                 :         reactor_scheduler const* sched;
     323                 :         lock_type* lock;
     324                 :         context_type& ctx;
     325                 :         ~task_cleanup();
     326                 :     };
     327                 : 
     328                 :     mutable mutex_type mutex_{true};
     329                 :     mutable event_type cond_{true};
     330                 :     mutable ready_queue completed_ops_;
     331                 :     mutable std::atomic<std::int64_t> outstanding_work_{0};
     332                 :     std::atomic<bool> stopped_{false};
     333                 :     mutable std::atomic<bool> task_running_{false};
     334                 :     mutable bool task_interrupted_ = false;
     335                 : 
     336                 :     // Descriptors retired during the running reactor pass (guarded by
     337                 :     // mutex_).
     338                 :     mutable reactor_retirable* retired_ = nullptr;
     339                 : 
     340                 :     /// Settle every descriptor retired before the current pass ended.
     341                 :     /// @pre `lock` holds `mutex_`; it is held again on return.
     342                 :     void settle_retired(lock_type& lock) const;
     343                 : 
     344                 :     // Runtime-configurable reactor tuning parameters.
     345                 :     // Defaults match the library's built-in values.
     346                 :     unsigned max_events_per_poll_   = 128;
     347                 :     unsigned inline_budget_initial_ = 2;
     348                 :     unsigned inline_budget_max_     = 16;
     349                 :     unsigned unassisted_budget_     = 4;
     350                 : 
     351                 :     /// Bit 0 of `state_`: set when the condvar should be signaled.
     352                 :     static constexpr std::size_t signaled_bit = 1;
     353                 : 
     354                 :     /// Increment per waiting thread in `state_`.
     355                 :     static constexpr std::size_t waiter_increment = 2;
     356                 :     mutable std::size_t state_                    = 0;
     357                 : 
     358                 :     /// Sentinel op that triggers a reactor poll when dequeued.
     359                 :     struct task_op final : scheduler_op
     360                 :     {
     361                 :         // LCOV_EXCL_START: the sentinel is intercepted by pointer
     362                 :         // identity; its virtuals exist for vtable completeness.
     363                 :         void operator()() override {}
     364                 :         void destroy() override {}
     365                 :         // LCOV_EXCL_STOP
     366                 :     };
     367                 :     task_op task_op_;
     368                 : 
     369                 :     /** Run the platform-specific reactor poll.
     370                 : 
     371                 :         @par Postconditions
     372                 :         `lock` is owned on return, however the poll ended. An
     373                 :         implementation that unlocks around the blocking call owes the
     374                 :         caller a matching re-acquire on every path out, including the
     375                 :         errors it retries rather than reports.
     376                 :     */
     377                 :     virtual void
     378                 :     run_task(lock_type& lock, context_type& ctx, long timeout_us) = 0;
     379                 : 
     380                 :     /// Wake a blocked reactor (e.g. write to eventfd or pipe).
     381                 :     virtual void interrupt_reactor() const = 0;
     382                 : 
     383                 :     /// Wake the reactor whenever the earliest timer changes.
     384                 :     void on_timer_service_attached(timer_service& svc) override;
     385                 : 
     386                 : private:
     387                 :     struct work_cleanup
     388                 :     {
     389                 :         reactor_scheduler* sched;
     390                 :         lock_type* lock;
     391                 :         context_type& ctx;
     392                 :         ~work_cleanup();
     393                 :     };
     394                 : 
     395                 :     std::size_t do_one(lock_type& lock, long timeout_us, context_type& ctx);
     396                 : 
     397                 :     void signal_all(lock_type& lock) const;
     398                 :     bool maybe_unlock_and_signal_one(lock_type& lock) const;
     399                 :     bool unlock_and_signal_one(lock_type& lock) const;
     400                 :     void clear_signal() const;
     401                 :     void wait_for_signal(lock_type& lock) const;
     402                 :     void wait_for_signal_for(lock_type& lock, long timeout_us) const;
     403                 :     void wake_one_thread_and_unlock(lock_type& lock) const;
     404                 : };
     405                 : 
     406                 : /** RAII guard that pushes/pops a scheduler context frame.
     407                 : 
     408                 :     On construction, pushes a new context frame onto the
     409                 :     thread-local stack. On destruction, drains any remaining
     410                 :     private queue items to the global queue and pops the frame.
     411                 : */
     412                 : struct reactor_thread_context_guard
     413                 : {
     414                 :     /// The context frame managed by this guard.
     415                 :     reactor_scheduler_context frame_;
     416                 : 
     417                 :     /// Marks the scheduler innermost for the executor's fast check.
     418                 :     running_scheduler_guard running_;
     419                 : 
     420                 :     /// Construct the guard, pushing a frame for @a sched.
     421           13011 :     explicit reactor_thread_context_guard(
     422                 :         reactor_scheduler const* sched) noexcept
     423           13011 :         : frame_(sched, reactor_context_stack.get())
     424           13011 :         , running_(sched)
     425                 :     {
     426           13011 :         reactor_context_stack.set(&frame_);
     427           13011 :     }
     428                 : 
     429                 :     /** Destroy the guard, popping the frame.
     430                 : 
     431                 :         The private queue is empty here by invariant: work_cleanup and
     432                 :         task_cleanup splice it to the global queue after every handler
     433                 :         and every reactor pass.
     434                 :     */
     435           13011 :     ~reactor_thread_context_guard() noexcept
     436                 :     {
     437           13011 :         reactor_context_stack.set(frame_.next);
     438           13011 :     }
     439                 : };
     440                 : 
     441                 : // ---- Inline implementations ------------------------------------------------
     442                 : 
     443           13011 : inline reactor_scheduler_context::reactor_scheduler_context(
     444           13011 :     reactor_scheduler const* k, reactor_scheduler_context* n)
     445           13011 :     : key(k)
     446           13011 :     , next(n)
     447           13011 :     , private_outstanding_work(0)
     448           13011 :     , inline_budget(0)
     449           13011 :     , inline_budget_max(static_cast<int>(k->inline_budget_initial()))
     450           13011 :     , unassisted(false)
     451                 : {
     452           13011 : }
     453                 : 
     454                 : inline void
     455              54 : reactor_scheduler::configure_reactor(
     456                 :     unsigned max_events,
     457                 :     unsigned budget_init,
     458                 :     unsigned budget_max,
     459                 :     unsigned unassisted)
     460                 : {
     461             106 :     if (max_events < 1 ||
     462              52 :         max_events > static_cast<unsigned>(std::numeric_limits<int>::max()))
     463               2 :         throw std::out_of_range("max_events_per_poll must be in [1, INT_MAX]");
     464              52 :     if (budget_max > static_cast<unsigned>(std::numeric_limits<int>::max()))
     465               2 :         throw std::out_of_range("inline_budget_max must be in [0, INT_MAX]");
     466                 : 
     467                 :     // Clamp initial and unassisted to budget_max.
     468              50 :     if (budget_init > budget_max)
     469              20 :         budget_init = budget_max;
     470              50 :     if (unassisted > budget_max)
     471              20 :         unassisted = budget_max;
     472                 : 
     473              50 :     max_events_per_poll_   = max_events;
     474              50 :     inline_budget_initial_ = budget_init;
     475              50 :     inline_budget_max_     = budget_max;
     476              50 :     unassisted_budget_     = unassisted;
     477              50 : }
     478                 : 
     479                 : inline void
     480          104726 : reactor_scheduler::reset_inline_budget() const noexcept
     481                 : {
     482                 :     // When budget is disabled (max==0), all paths below would no-op
     483                 :     // (inline_budget stays 0). Skip the TLS lookup entirely.
     484          104726 :     if (inline_budget_max_ == 0)
     485              58 :         return;
     486          104668 :     if (auto* ctx = reactor_find_context(this))
     487                 :     {
     488                 :         // Cap when no other thread absorbed queued work
     489          104668 :         if (ctx->unassisted)
     490                 :         {
     491          103690 :             ctx->inline_budget_max = static_cast<int>(unassisted_budget_);
     492          103690 :             ctx->inline_budget     = static_cast<int>(unassisted_budget_);
     493          103690 :             return;
     494                 :         }
     495                 :         // Ramp up when previous cycle fully consumed budget.
     496                 :         // max(1, ...) ensures the doubling escapes zero.
     497             978 :         if (ctx->inline_budget == 0)
     498             294 :             ctx->inline_budget_max =
     499             294 :                 (std::min)((std::max)(1, ctx->inline_budget_max) * 2,
     500             588 :                            static_cast<int>(inline_budget_max_));
     501             684 :         else if (ctx->inline_budget < ctx->inline_budget_max)
     502             208 :             ctx->inline_budget_max = static_cast<int>(inline_budget_initial_);
     503             978 :         ctx->inline_budget = ctx->inline_budget_max;
     504                 :     }
     505                 : }
     506                 : 
     507                 : inline bool
     508          441619 : reactor_scheduler::try_consume_inline_budget() const noexcept
     509                 : {
     510          441619 :     if (inline_budget_max_ == 0)
     511              44 :         return false;
     512          441575 :     if (auto* ctx = reactor_find_context(this))
     513                 :     {
     514          441537 :         if (ctx->inline_budget > 0)
     515                 :         {
     516          352948 :             --ctx->inline_budget;
     517          352948 :             return true;
     518                 :         }
     519                 :     }
     520           88627 :     return false;
     521                 : }
     522                 : 
     523                 : inline void
     524            3672 : reactor_scheduler::post(std::coroutine_handle<> h) const
     525                 : {
     526                 :     struct post_handler final : scheduler_op
     527                 :     {
     528                 :         std::coroutine_handle<> h_;
     529                 : 
     530            3672 :         explicit post_handler(std::coroutine_handle<> h) : h_(h) {}
     531            7344 :         ~post_handler() override = default;
     532                 : 
     533            3656 :         void operator()() override
     534                 :         {
     535            3656 :             auto saved = h_;
     536            3656 :             delete this;
     537            3656 :             saved.resume();
     538            3656 :         }
     539                 : 
     540              16 :         void destroy() override
     541                 :         {
     542                 :             // A bare handle carries no owner to unwind from: it is
     543                 :             // destroyed directly.
     544              16 :             auto saved = h_;
     545              16 :             delete this;
     546              16 :             saved.destroy();
     547              16 :         }
     548                 :     };
     549                 : 
     550            3672 :     auto ph = std::make_unique<post_handler>(h);
     551                 : 
     552            3672 :     if (auto* ctx = reactor_find_context(this))
     553                 :     {
     554               8 :         ++ctx->private_outstanding_work;
     555               8 :         ctx->private_queue.push(ph.release());
     556               8 :         return;
     557                 :     }
     558                 : 
     559            3664 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     560                 : 
     561            3664 :     lock_type lock(mutex_);
     562            3664 :     completed_ops_.push(ph.release());
     563            3664 :     wake_one_thread_and_unlock(lock);
     564            3672 : }
     565                 : 
     566                 : inline void
     567          113149 : reactor_scheduler::post(scheduler_op* h) const
     568                 : {
     569          113149 :     if (auto* ctx = reactor_find_context(this))
     570                 :     {
     571          111214 :         ++ctx->private_outstanding_work;
     572          111214 :         ctx->private_queue.push(h);
     573          111214 :         return;
     574                 :     }
     575                 : 
     576            1935 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     577                 : 
     578            1935 :     lock_type lock(mutex_);
     579            1935 :     completed_ops_.push(h);
     580            1935 :     wake_one_thread_and_unlock(lock);
     581            1935 : }
     582                 : 
     583                 : inline void
     584          236425 : reactor_scheduler::post(capy::continuation& c) const
     585                 : {
     586          236425 :     if (auto* ctx = reactor_find_context(this))
     587                 :     {
     588          204377 :         ++ctx->private_outstanding_work;
     589          204377 :         ctx->private_queue.push(c);
     590          204377 :         return;
     591                 :     }
     592                 : 
     593           32048 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     594                 : 
     595           32048 :     lock_type lock(mutex_);
     596           32048 :     completed_ops_.push(c);
     597           32048 :     wake_one_thread_and_unlock(lock);
     598           32048 : }
     599                 : 
     600                 : inline bool
     601           23720 : reactor_scheduler::running_in_this_thread() const noexcept
     602                 : {
     603           23720 :     return reactor_find_context(this) != nullptr;
     604                 : }
     605                 : 
     606                 : inline void
     607            8374 : reactor_scheduler::stop()
     608                 : {
     609            8374 :     lock_type lock(mutex_);
     610            8374 :     if (!stopped_.load(std::memory_order_acquire))
     611                 :     {
     612            7313 :         stopped_.store(true, std::memory_order_release);
     613            7313 :         signal_all(lock);
     614            7313 :         interrupt_reactor();
     615                 :     }
     616            8374 : }
     617                 : 
     618                 : inline bool
     619            3540 : reactor_scheduler::stopped() const noexcept
     620                 : {
     621            3540 :     return stopped_.load(std::memory_order_acquire);
     622                 : }
     623                 : 
     624                 : inline void
     625            5691 : reactor_scheduler::restart()
     626                 : {
     627            5691 :     stopped_.store(false, std::memory_order_release);
     628            5691 : }
     629                 : 
     630                 : inline std::size_t
     631            6483 : reactor_scheduler::run()
     632                 : {
     633           12966 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     634                 :     {
     635             271 :         stop();
     636             271 :         return 0;
     637                 :     }
     638                 : 
     639            6212 :     reactor_thread_context_guard ctx(this);
     640            6212 :     lock_type lock(mutex_);
     641                 : 
     642            6212 :     std::size_t n = 0;
     643                 :     for (;;)
     644                 :     {
     645          393013 :         if (!do_one(lock, -1, ctx.frame_))
     646            6209 :             break;
     647          386801 :         if (n != (std::numeric_limits<std::size_t>::max)())
     648          386801 :             ++n;
     649          386801 :         if (!lock.owns_lock())
     650           90101 :             lock.lock();
     651                 :     }
     652            6209 :     return n;
     653            6215 : }
     654                 : 
     655                 : inline std::size_t
     656             128 : reactor_scheduler::run_one()
     657                 : {
     658             256 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     659                 :     {
     660               3 :         stop();
     661               3 :         return 0;
     662                 :     }
     663                 : 
     664             125 :     reactor_thread_context_guard ctx(this);
     665             125 :     lock_type lock(mutex_);
     666             125 :     return do_one(lock, -1, ctx.frame_);
     667             125 : }
     668                 : 
     669                 : inline std::size_t
     670            6132 : reactor_scheduler::wait_one(long usec)
     671                 : {
     672           12264 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     673                 :     {
     674             815 :         stop();
     675             815 :         return 0;
     676                 :     }
     677                 : 
     678            5317 :     reactor_thread_context_guard ctx(this);
     679            5317 :     lock_type lock(mutex_);
     680            5317 :     return do_one(lock, usec, ctx.frame_);
     681            5317 : }
     682                 : 
     683                 : inline std::size_t
     684            1331 : reactor_scheduler::poll()
     685                 : {
     686            2662 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     687                 :     {
     688              29 :         stop();
     689              29 :         return 0;
     690                 :     }
     691                 : 
     692            1302 :     reactor_thread_context_guard ctx(this);
     693            1302 :     lock_type lock(mutex_);
     694                 : 
     695            1302 :     std::size_t n = 0;
     696                 :     for (;;)
     697                 :     {
     698            9300 :         if (!do_one(lock, 0, ctx.frame_))
     699            1302 :             break;
     700            7998 :         if (n != (std::numeric_limits<std::size_t>::max)())
     701            7998 :             ++n;
     702            7998 :         if (!lock.owns_lock())
     703            7996 :             lock.lock();
     704                 :     }
     705            1302 :     return n;
     706            1302 : }
     707                 : 
     708                 : inline std::size_t
     709              60 : reactor_scheduler::poll_one()
     710                 : {
     711             120 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     712                 :     {
     713               5 :         stop();
     714               5 :         return 0;
     715                 :     }
     716                 : 
     717              55 :     reactor_thread_context_guard ctx(this);
     718              55 :     lock_type lock(mutex_);
     719              55 :     return do_one(lock, 0, ctx.frame_);
     720              55 : }
     721                 : 
     722                 : inline void
     723           66684 : reactor_scheduler::work_started() noexcept
     724                 : {
     725           66684 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     726           66684 : }
     727                 : 
     728                 : inline void
     729          111750 : reactor_scheduler::work_finished() noexcept
     730                 : {
     731          223500 :     if (outstanding_work_.fetch_sub(1, std::memory_order_acq_rel) == 1)
     732            7186 :         stop();
     733          111750 : }
     734                 : 
     735                 : inline void
     736           36773 : reactor_scheduler::compensating_work_started() const noexcept
     737                 : {
     738           36773 :     auto* ctx = reactor_find_context(this);
     739           36773 :     if (ctx)
     740           36771 :         ++ctx->private_outstanding_work;
     741           36773 : }
     742                 : 
     743                 : inline void
     744            2073 : reactor_scheduler::post_counted(scheduler_op* op) const
     745                 : {
     746            2073 :     if (auto* ctx = reactor_find_context(this))
     747                 :     {
     748             605 :         ctx->private_queue.push(op);
     749             605 :         return;
     750                 :     }
     751                 : 
     752            1468 :     lock_type lock(mutex_);
     753            1468 :     completed_ops_.push(op);
     754            1468 :     wake_one_thread_and_unlock(lock);
     755            1468 : }
     756                 : 
     757                 : inline void
     758           15527 : reactor_scheduler::post_deferred_completions(ready_queue& ops) const
     759                 : {
     760           15527 :     if (ops.empty())
     761           15527 :         return;
     762                 : 
     763               2 :     if (auto* ctx = reactor_find_context(this))
     764                 :     {
     765               2 :         ctx->private_queue.splice(ops);
     766               2 :         return;
     767                 :     }
     768                 : 
     769 MIS           0 :     lock_type lock(mutex_);
     770               0 :     completed_ops_.splice(ops);
     771               0 :     wake_one_thread_and_unlock(lock);
     772               0 : }
     773                 : 
     774                 : inline void
     775 HIT       22564 : reactor_scheduler::retire_descriptor(
     776                 :     reactor_retirable& d, object_ref keep) const
     777                 : {
     778           22564 :     lock_type lock(mutex_);
     779           22564 :     if (!task_running_.load(std::memory_order_relaxed))
     780                 :     {
     781                 :         // No pass can hold an event, but an earlier one may have
     782                 :         // queued d.
     783           17209 :         d.hand_off(keep);
     784           17209 :         return;
     785                 :     }
     786                 :     // Already parked for this pass: the held reference suffices.
     787            5355 :     if (d.retired_ref_)
     788             107 :         return;
     789            5248 :     d.retired_ref_  = std::move(keep);
     790            5248 :     d.retired_next_ = retired_;
     791            5248 :     retired_        = &d;
     792           22564 : }
     793                 : 
     794                 : inline void
     795          297203 : reactor_scheduler::settle_retired(lock_type& lock) const
     796                 : {
     797                 :     // Handed off under the lock, since a later pass may retire the same
     798                 :     // descriptor again as soon as it is released. A reference that is
     799                 :     // not handed off may be the owner's last, and its release recycles
     800                 :     // through the pool mutex, so it is dropped with the lock released.
     801          297203 :     reactor_retirable* dropped = nullptr;
     802          302451 :     while (auto* d = retired_)
     803                 :     {
     804            5248 :         retired_         = d->retired_next_;
     805            5248 :         d->retired_next_ = nullptr;
     806            5248 :         auto keep        = std::move(d->retired_ref_);
     807            5248 :         d->hand_off(keep);
     808                 :         // A drop already pending keeps the owner alive, so this one
     809                 :         // cannot be the last and is released here.
     810            5248 :         if (!keep || d->dropped_ref_)
     811             392 :             continue;
     812            4856 :         d->dropped_ref_  = std::move(keep);
     813            4856 :         d->dropped_next_ = dropped;
     814            4856 :         dropped          = d;
     815           10496 :     }
     816          302059 :     while (auto* d = dropped)
     817                 :     {
     818            4856 :         dropped          = d->dropped_next_;
     819            4856 :         d->dropped_next_ = nullptr;
     820            4856 :         auto keep        = std::move(d->dropped_ref_);
     821            4856 :         lock.unlock();
     822            4856 :         keep.reset();
     823            4856 :         lock.lock();
     824            4856 :     }
     825          297203 : }
     826                 : 
     827                 : inline void
     828             393 : reactor_scheduler::on_timer_service_attached(timer_service& svc)
     829                 : {
     830             393 :     svc.set_on_earliest_changed(timer_service::callback(this, [](void* p) {
     831            3191 :         static_cast<reactor_scheduler*>(p)->interrupt_reactor();
     832            3191 :     }));
     833             393 : }
     834                 : 
     835                 : inline void
     836            4426 : reactor_scheduler::shutdown_drain()
     837                 : {
     838            4426 :     wait_cancels_posting();
     839                 : 
     840            4426 :     lock_type lock(mutex_);
     841            4426 :     settle_retired(lock);
     842            4426 :     destroy_queued(lock);
     843            4426 :     signal_all(lock);
     844            4426 : }
     845                 : 
     846            4442 : inline reactor_scheduler::~reactor_scheduler()
     847                 : {
     848                 :     // Services give up their work in shutdown(), but anything posted
     849                 :     // after the drain, such as from a destructor, is still destroyed.
     850            4442 :     lock_type lock(mutex_);
     851            4442 :     destroy_queued(lock);
     852            4442 : }
     853                 : 
     854                 : inline void
     855            8868 : reactor_scheduler::destroy_queued(lock_type& lock) noexcept
     856                 : {
     857           22660 :     while (auto e = completed_ops_.pop())
     858                 :     {
     859           13792 :         if (ready_is_continuation(e))
     860                 :         {
     861            7827 :             lock.unlock();
     862            7827 :             if (auto* c = ready_as_cont(e); c->h)
     863            7827 :                 capy::abandon(*c);
     864            7827 :             lock.lock();
     865                 :         }
     866                 :         else
     867                 :         {
     868            5965 :             auto* op = ready_as_op(e);
     869            5965 :             if (op == &task_op_)
     870            4423 :                 continue;
     871            1542 :             lock.unlock();
     872            1542 :             op->destroy();
     873            1542 :             lock.lock();
     874                 :         }
     875           13792 :     }
     876            8868 : }
     877                 : 
     878                 : inline void
     879           11739 : reactor_scheduler::signal_all(lock_type&) const
     880                 : {
     881           11739 :     state_ |= signaled_bit;
     882           11739 :     cond_.notify_all();
     883           11739 : }
     884                 : 
     885                 : inline bool
     886           39115 : reactor_scheduler::maybe_unlock_and_signal_one(lock_type& lock) const
     887                 : {
     888           39115 :     state_ |= signaled_bit;
     889           39115 :     if (state_ > signaled_bit)
     890                 :     {
     891                 :         // Signalled before unlocking: a post from another context's
     892                 :         // thread may be the last touch of this scheduler before the
     893                 :         // posted work runs, finishes and lets it be destroyed.
     894             136 :         cond_.notify_one();
     895             136 :         lock.unlock();
     896             136 :         return true;
     897                 :     }
     898           38979 :     return false;
     899                 : }
     900                 : 
     901                 : inline bool
     902          650828 : reactor_scheduler::unlock_and_signal_one(lock_type& lock) const
     903                 : {
     904          650828 :     state_ |= signaled_bit;
     905          650828 :     bool have_waiters = state_ > signaled_bit;
     906          650828 :     lock.unlock();
     907          650828 :     if (have_waiters)
     908            3367 :         cond_.notify_one();
     909          650828 :     return have_waiters;
     910                 : }
     911                 : 
     912                 : inline void
     913             750 : reactor_scheduler::clear_signal() const
     914                 : {
     915             750 :     state_ &= ~signaled_bit;
     916             750 : }
     917                 : 
     918                 : inline void
     919             741 : reactor_scheduler::wait_for_signal(lock_type& lock) const
     920                 : {
     921            1561 :     while ((state_ & signaled_bit) == 0)
     922                 :     {
     923             820 :         state_ += waiter_increment;
     924             820 :         cond_.wait(lock);
     925             820 :         state_ -= waiter_increment;
     926                 :     }
     927             741 : }
     928                 : 
     929                 : inline void
     930               9 : reactor_scheduler::wait_for_signal_for(lock_type& lock, long timeout_us) const
     931                 : {
     932               9 :     if ((state_ & signaled_bit) == 0)
     933                 :     {
     934               9 :         state_ += waiter_increment;
     935               9 :         cond_.wait_for(lock, std::chrono::microseconds(timeout_us));
     936               9 :         state_ -= waiter_increment;
     937                 :     }
     938               9 : }
     939                 : 
     940                 : inline void
     941           39115 : reactor_scheduler::wake_one_thread_and_unlock(lock_type& lock) const
     942                 : {
     943           39115 :     if (maybe_unlock_and_signal_one(lock))
     944             136 :         return;
     945                 : 
     946           38979 :     if (task_running_.load(std::memory_order_relaxed) && !task_interrupted_)
     947                 :     {
     948            1739 :         task_interrupted_ = true;
     949                 :         // Under the lock, for the same reason as the signal above.
     950            1739 :         interrupt_reactor();
     951            1739 :         lock.unlock();
     952                 :     }
     953                 :     else
     954                 :     {
     955           37240 :         lock.unlock();
     956                 :     }
     957                 : }
     958                 : 
     959          397643 : inline reactor_scheduler::work_cleanup::~work_cleanup()
     960                 : {
     961          397643 :     std::int64_t produced = ctx.private_outstanding_work;
     962          397643 :     if (produced > 1)
     963             980 :         sched->outstanding_work_.fetch_add(
     964                 :             produced - 1, std::memory_order_relaxed);
     965          396663 :     else if (produced < 1)
     966           65047 :         sched->work_finished();
     967          397643 :     ctx.private_outstanding_work = 0;
     968                 : 
     969          397643 :     if (!ctx.private_queue.empty())
     970                 :     {
     971          297145 :         lock->lock();
     972          297145 :         sched->completed_ops_.splice(ctx.private_queue);
     973                 :     }
     974          397643 : }
     975                 : 
     976          292780 : inline reactor_scheduler::task_cleanup::~task_cleanup()
     977                 : {
     978          292780 :     if (ctx.private_outstanding_work > 0)
     979                 :     {
     980           10150 :         sched->outstanding_work_.fetch_add(
     981           10150 :             ctx.private_outstanding_work, std::memory_order_relaxed);
     982           10150 :         ctx.private_outstanding_work = 0;
     983                 :     }
     984                 : 
     985          292780 :     if (!ctx.private_queue.empty())
     986                 :     {
     987           10150 :         if (!lock->owns_lock())
     988 MIS           0 :             lock->lock();
     989 HIT       10150 :         sched->completed_ops_.splice(ctx.private_queue);
     990                 :     }
     991          292780 : }
     992                 : 
     993                 : inline std::size_t
     994          407810 : reactor_scheduler::do_one(lock_type& lock, long timeout_us, context_type& ctx)
     995                 : {
     996                 :     for (;;)
     997                 :     {
     998          698687 :         if (stopped_.load(std::memory_order_acquire))
     999            6385 :             return 0;
    1000                 : 
    1001          692302 :         std::uintptr_t e = completed_ops_.pop();
    1002          692302 :         scheduler_op* op = ready_is_continuation(e) ? nullptr : ready_as_op(e);
    1003                 : 
    1004                 :         // Handle reactor sentinel — time to poll for I/O
    1005          692302 :         if (op == &task_op_)
    1006                 :         {
    1007          293909 :             bool more_handlers = !completed_ops_.empty();
    1008                 : 
    1009          332874 :             if (!more_handlers &&
    1010           77930 :                 (outstanding_work_.load(std::memory_order_acquire) == 0 ||
    1011                 :                  timeout_us == 0))
    1012                 :             {
    1013            1129 :                 completed_ops_.push(&task_op_);
    1014            1129 :                 return 0;
    1015                 :             }
    1016                 : 
    1017          292780 :             long task_timeout_us = more_handlers ? 0 : timeout_us;
    1018          292780 :             task_interrupted_    = task_timeout_us == 0;
    1019          292780 :             task_running_.store(true, std::memory_order_release);
    1020                 : 
    1021                 :             // Wake a peer to take the pending handlers while this thread
    1022                 :             // polls the reactor; skipped when one_thread_ (no peer exists).
    1023          292780 :             if (more_handlers && !one_thread_)
    1024          254933 :                 unlock_and_signal_one(lock);
    1025                 : 
    1026                 :             try
    1027                 :             {
    1028          292780 :                 run_task(lock, ctx, task_timeout_us);
    1029                 :             }
    1030               3 :             catch (...)
    1031                 :             {
    1032               3 :                 task_running_.store(false, std::memory_order_relaxed);
    1033               3 :                 throw;
    1034               3 :             }
    1035                 : 
    1036          292777 :             task_running_.store(false, std::memory_order_relaxed);
    1037          292777 :             completed_ops_.push(&task_op_);
    1038          292777 :             settle_retired(lock);
    1039          292777 :             if (timeout_us > 0)
    1040            2650 :                 return 0;
    1041          290127 :             continue;
    1042          290127 :         }
    1043                 : 
    1044                 :         // Handle ready entry (op or continuation)
    1045          398393 :         if (e != 0)
    1046                 :         {
    1047          397643 :             bool more = !completed_ops_.empty();
    1048                 : 
    1049          397643 :             if (more && !one_thread_)
    1050                 :             {
    1051                 :                 // Wake a peer for the remaining work; unassisted if none
    1052                 :                 // was parked to take it.
    1053          395895 :                 ctx.unassisted = !unlock_and_signal_one(lock);
    1054                 :             }
    1055                 :             else
    1056                 :             {
    1057                 :                 // No peer to wake (one_thread_, or nothing more queued).
    1058            1748 :                 ctx.unassisted = more;
    1059            1748 :                 lock.unlock();
    1060                 :             }
    1061                 : 
    1062          397643 :             [[maybe_unused]] work_cleanup on_exit{this, &lock, ctx};
    1063                 : 
    1064          397643 :             if (ready_is_continuation(e))
    1065          228598 :                 ready_as_cont(e)->h.resume();
    1066                 :             else
    1067          169045 :                 (*op)();
    1068          397643 :             return 1;
    1069          397643 :         }
    1070                 : 
    1071            1500 :         if (outstanding_work_.load(std::memory_order_acquire) == 0 ||
    1072                 :             timeout_us == 0)
    1073 MIS           0 :             return 0;
    1074                 : 
    1075 HIT         750 :         clear_signal();
    1076             750 :         if (timeout_us < 0)
    1077             741 :             wait_for_signal(lock);
    1078                 :         else
    1079               9 :             wait_for_signal_for(lock, timeout_us);
    1080          290877 :     }
    1081                 : }
    1082                 : 
    1083                 : } // namespace boost::corosio::detail
    1084                 : 
    1085                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_SCHEDULER_HPP
        

Generated by: LCOV version 2.3