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: 85.1 % 363 309 54
Test Date: 2026-09-02 21:27:06 Functions: 89.6 % 48 43 5

           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/capy/ex/execution_context.hpp>
      15                 : 
      16                 : #include <boost/corosio/detail/ready_queue.hpp>
      17                 : #include <boost/corosio/detail/scheduler.hpp>
      18                 : #include <boost/corosio/detail/scheduler_op.hpp>
      19                 : #include <boost/corosio/detail/thread_local_ptr.hpp>
      20                 : 
      21                 : #include <atomic>
      22                 : #include <chrono>
      23                 : #include <coroutine>
      24                 : #include <cstddef>
      25                 : #include <cstdint>
      26                 : #include <limits>
      27                 : #include <memory>
      28                 : #include <stdexcept>
      29                 : 
      30                 : #include <boost/corosio/detail/conditionally_enabled_mutex.hpp>
      31                 : #include <boost/corosio/detail/conditionally_enabled_event.hpp>
      32                 : 
      33                 : namespace boost::corosio::detail {
      34                 : 
      35                 : // Forward declarations
      36                 : class reactor_scheduler;
      37                 : class timer_service;
      38                 : 
      39                 : /** Per-thread state for a reactor scheduler.
      40                 : 
      41                 :     Each thread running a scheduler's event loop has one of these
      42                 :     on a thread-local stack. It holds a private work queue and
      43                 :     inline completion budget for speculative I/O fast paths.
      44                 : */
      45                 : struct BOOST_COROSIO_SYMBOL_VISIBLE reactor_scheduler_context
      46                 : {
      47                 :     /// Scheduler this context belongs to.
      48                 :     reactor_scheduler const* key;
      49                 : 
      50                 :     /// Next context frame on this thread's stack.
      51                 :     reactor_scheduler_context* next;
      52                 : 
      53                 :     /// Private work queue for reduced contention.
      54                 :     ready_queue private_queue;
      55                 : 
      56                 :     /// Unflushed work count for the private queue.
      57                 :     std::int64_t private_outstanding_work;
      58                 : 
      59                 :     /// Remaining inline completions allowed this cycle.
      60                 :     int inline_budget;
      61                 : 
      62                 :     /// Maximum inline budget (adaptive, 2-16).
      63                 :     int inline_budget_max;
      64                 : 
      65                 :     /// True if no other thread absorbed queued work last cycle.
      66                 :     bool unassisted;
      67                 : 
      68                 :     /// Construct a context frame linked to @a n.
      69                 :     reactor_scheduler_context(
      70                 :         reactor_scheduler const* k,
      71                 :         reactor_scheduler_context* n);
      72                 : };
      73                 : 
      74                 : /// Thread-local context stack for reactor schedulers.
      75                 : inline thread_local_ptr<reactor_scheduler_context> reactor_context_stack;
      76                 : 
      77                 : /// Find the context frame for a scheduler on this thread.
      78                 : inline reactor_scheduler_context*
      79 HIT      992581 : reactor_find_context(reactor_scheduler const* self) noexcept
      80                 : {
      81          992581 :     for (auto* c = reactor_context_stack.get(); c != nullptr; c = c->next)
      82                 :     {
      83          973811 :         if (c->key == self)
      84          973811 :             return c;
      85                 :     }
      86           18770 :     return nullptr;
      87                 : }
      88                 : 
      89                 : /// Flush private work count to global counter.
      90                 : inline void
      91 MIS           0 : reactor_flush_private_work(
      92                 :     reactor_scheduler_context* ctx,
      93                 :     std::atomic<std::int64_t>& outstanding_work) noexcept
      94                 : {
      95               0 :     if (ctx && ctx->private_outstanding_work > 0)
      96                 :     {
      97               0 :         outstanding_work.fetch_add(
      98                 :             ctx->private_outstanding_work, std::memory_order_relaxed);
      99               0 :         ctx->private_outstanding_work = 0;
     100                 :     }
     101               0 : }
     102                 : 
     103                 : /** Drain private queue to global queue, flushing work count first.
     104                 : 
     105                 :     @return True if any ops were drained.
     106                 : */
     107                 : inline bool
     108 HIT           6 : reactor_drain_private_queue(
     109                 :     reactor_scheduler_context* ctx,
     110                 :     std::atomic<std::int64_t>& outstanding_work,
     111                 :     ready_queue& completed_ops) noexcept
     112                 : {
     113               6 :     if (!ctx || ctx->private_queue.empty())
     114               6 :         return false;
     115                 : 
     116 MIS           0 :     reactor_flush_private_work(ctx, outstanding_work);
     117               0 :     completed_ops.splice(ctx->private_queue);
     118               0 :     return true;
     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                 :     , public capy::execution_context::service
     144                 : {
     145                 : public:
     146                 :     using key_type     = scheduler;
     147                 :     using context_type = reactor_scheduler_context;
     148                 :     using mutex_type = conditionally_enabled_mutex;
     149                 :     using lock_type = mutex_type::scoped_lock;
     150                 :     using event_type = conditionally_enabled_event;
     151                 : 
     152                 :     /// Post a coroutine for deferred execution.
     153                 :     void post(std::coroutine_handle<> h) const override;
     154                 : 
     155                 :     /// Post a scheduler operation for deferred execution.
     156                 :     void post(scheduler_op* h) const override;
     157                 : 
     158                 :     /// Post a continuation for deferred execution.
     159                 :     void post(capy::continuation&) const override;
     160                 : 
     161                 :     /// Return true if called from a thread running this scheduler.
     162                 :     bool running_in_this_thread() const noexcept override;
     163                 : 
     164                 :     /// Request the scheduler to stop dispatching handlers.
     165                 :     void stop() override;
     166                 : 
     167                 :     /// Return true if the scheduler has been stopped.
     168                 :     bool stopped() const noexcept override;
     169                 : 
     170                 :     /// Reset the stopped state so `run()` can resume.
     171                 :     void restart() override;
     172                 : 
     173                 :     /// Run the event loop until no work remains.
     174                 :     std::size_t run() override;
     175                 : 
     176                 :     /// Run until one handler completes or no work remains.
     177                 :     std::size_t run_one() override;
     178                 : 
     179                 :     /// Run until one handler completes or @a usec elapses.
     180                 :     std::size_t wait_one(long usec) override;
     181                 : 
     182                 :     /// Run ready handlers without blocking.
     183                 :     std::size_t poll() override;
     184                 : 
     185                 :     /// Run at most one ready handler without blocking.
     186                 :     std::size_t poll_one() override;
     187                 : 
     188                 :     /// Increment the outstanding work count.
     189                 :     void work_started() noexcept override;
     190                 : 
     191                 :     /// Decrement the outstanding work count, stopping on zero.
     192                 :     void work_finished() noexcept override;
     193                 : 
     194                 :     /** Reset the thread's inline completion budget.
     195                 : 
     196                 :         Called at the start of each posted completion handler to
     197                 :         grant a fresh budget for speculative inline completions.
     198                 :     */
     199                 :     void reset_inline_budget() const noexcept;
     200                 : 
     201                 :     /** Consume one unit of inline budget if available.
     202                 : 
     203                 :         @return True if budget was available and consumed.
     204                 :     */
     205                 :     bool try_consume_inline_budget() const noexcept;
     206                 : 
     207                 :     /** Offset a forthcoming work_finished from work_cleanup.
     208                 : 
     209                 :         Called by descriptor_state when all I/O returned EAGAIN and
     210                 :         no handler will be executed. Must be called from a scheduler
     211                 :         thread.
     212                 :     */
     213                 :     void compensating_work_started() const noexcept;
     214                 : 
     215                 :     /** Drain work from thread context's private queue to global queue.
     216                 : 
     217                 :         Flushes private work count to the global counter, then
     218                 :         transfers the queue under mutex protection.
     219                 : 
     220                 :         @param queue The private queue to drain.
     221                 :         @param count Private work count to flush before draining.
     222                 :     */
     223                 :     void drain_thread_queue(ready_queue& queue, std::int64_t count) const;
     224                 : 
     225                 :     /** Post completed operations for deferred invocation.
     226                 : 
     227                 :         If called from a thread running this scheduler, operations
     228                 :         go to the thread's private queue (fast path). Otherwise,
     229                 :         operations are added to the global queue under mutex and a
     230                 :         waiter is signaled.
     231                 : 
     232                 :         @par Preconditions
     233                 :         work_started() must have been called for each operation.
     234                 : 
     235                 :         @param ops Queue of operations to post.
     236                 :     */
     237                 :     void post_deferred_completions(ready_queue& ops) const;
     238                 : 
     239                 :     /** Apply runtime configuration to the scheduler.
     240                 : 
     241                 :         Called by `io_context` after construction. Values that do
     242                 :         not apply to this backend are silently ignored.
     243                 : 
     244                 :         @param max_events  Event buffer size for epoll/kqueue.
     245                 :         @param budget_init Starting inline completion budget.
     246                 :         @param budget_max  Hard ceiling on adaptive budget ramp-up.
     247                 :         @param unassisted  Budget when single-threaded.
     248                 :     */
     249                 :     virtual void configure_reactor(
     250                 :         unsigned max_events,
     251                 :         unsigned budget_init,
     252                 :         unsigned budget_max,
     253                 :         unsigned unassisted);
     254                 : 
     255                 :     /// Return the configured initial inline budget.
     256 HIT        1496 :     unsigned inline_budget_initial() const noexcept
     257                 :     {
     258            1496 :         return inline_budget_initial_;
     259                 :     }
     260                 : 
     261                 :     /// Return true when scheduler locking is disabled (fully-lockless tier).
     262             240 :     bool scheduler_locking_disabled() const noexcept override
     263                 :     {
     264             240 :         return scheduler_locking_disabled_;
     265                 :     }
     266                 : 
     267            1790 :     void configure_threading(threading_config cfg) noexcept override
     268                 :     {
     269            1790 :         scheduler_locking_disabled_ = !cfg.scheduler_locking;
     270                 :         // reactor_io_locking takes effect at descriptor registration (see the
     271                 :         // register_descriptor overrides), not here.
     272            1790 :         reactor_io_locking_ = cfg.reactor_io_locking;
     273            1790 :         one_thread_         = cfg.one_thread;
     274            1790 :         mutex_.set_enabled(cfg.scheduler_locking);
     275            1790 :         cond_.set_enabled(cfg.scheduler_locking);
     276            1790 :     }
     277                 : 
     278                 : protected:
     279                 :     timer_service* timer_svc_ = nullptr;
     280                 :     bool scheduler_locking_disabled_ = false;
     281                 :     bool reactor_io_locking_ = true;
     282                 :     bool one_thread_ = false;
     283                 : 
     284            1802 :     reactor_scheduler() = default;
     285                 : 
     286                 :     /** Drain completed_ops during shutdown.
     287                 : 
     288                 :         Pops all operations from the global queue and destroys them,
     289                 :         skipping the task sentinel. Signals all waiting threads.
     290                 :         Derived classes call this from their shutdown() override
     291                 :         before performing platform-specific cleanup.
     292                 :     */
     293                 :     void shutdown_drain();
     294                 : 
     295                 :     /// RAII guard that re-inserts the task sentinel after `run_task`.
     296                 :     struct task_cleanup
     297                 :     {
     298                 :         reactor_scheduler const* sched;
     299                 :         lock_type* lock;
     300                 :         context_type* ctx;
     301                 :         ~task_cleanup();
     302                 :     };
     303                 : 
     304                 :     mutable mutex_type mutex_{true};
     305                 :     mutable event_type cond_{true};
     306                 :     mutable ready_queue completed_ops_;
     307                 :     mutable std::atomic<std::int64_t> outstanding_work_{0};
     308                 :     std::atomic<bool> stopped_{false};
     309                 :     mutable std::atomic<bool> task_running_{false};
     310                 :     mutable bool task_interrupted_ = false;
     311                 : 
     312                 :     // Runtime-configurable reactor tuning parameters.
     313                 :     // Defaults match the library's built-in values.
     314                 :     unsigned max_events_per_poll_   = 128;
     315                 :     unsigned inline_budget_initial_ = 2;
     316                 :     unsigned inline_budget_max_     = 16;
     317                 :     unsigned unassisted_budget_     = 4;
     318                 : 
     319                 :     /// Bit 0 of `state_`: set when the condvar should be signaled.
     320                 :     static constexpr std::size_t signaled_bit = 1;
     321                 : 
     322                 :     /// Increment per waiting thread in `state_`.
     323                 :     static constexpr std::size_t waiter_increment = 2;
     324                 :     mutable std::size_t state_                    = 0;
     325                 : 
     326                 :     /// Sentinel op that triggers a reactor poll when dequeued.
     327                 :     struct task_op final : scheduler_op
     328                 :     {
     329 MIS           0 :         void operator()() override {}
     330               0 :         void destroy() override {}
     331                 :     };
     332                 :     task_op task_op_;
     333                 : 
     334                 :     /** Run the platform-specific reactor poll.
     335                 : 
     336                 :         @par Postconditions
     337                 :         `lock` is owned on return, however the poll ended. An
     338                 :         implementation that unlocks around the blocking call owes the
     339                 :         caller a matching re-acquire on every path out, including the
     340                 :         errors it retries rather than reports.
     341                 :     */
     342                 :     virtual void
     343                 :     run_task(lock_type& lock, context_type* ctx,
     344                 :         long timeout_us) = 0;
     345                 : 
     346                 :     /// Wake a blocked reactor (e.g. write to eventfd or pipe).
     347                 :     virtual void interrupt_reactor() const = 0;
     348                 : 
     349                 : private:
     350                 :     struct work_cleanup
     351                 :     {
     352                 :         reactor_scheduler* sched;
     353                 :         lock_type* lock;
     354                 :         context_type* ctx;
     355                 :         ~work_cleanup();
     356                 :     };
     357                 : 
     358                 :     std::size_t do_one(
     359                 :         lock_type& lock, long timeout_us, context_type* ctx);
     360                 : 
     361                 :     void signal_all(lock_type& lock) const;
     362                 :     bool maybe_unlock_and_signal_one(lock_type& lock) const;
     363                 :     bool unlock_and_signal_one(lock_type& lock) const;
     364                 :     void clear_signal() const;
     365                 :     void wait_for_signal(lock_type& lock) const;
     366                 :     void wait_for_signal_for(
     367                 :         lock_type& lock, long timeout_us) const;
     368                 :     void wake_one_thread_and_unlock(lock_type& lock) const;
     369                 : };
     370                 : 
     371                 : /** RAII guard that pushes/pops a scheduler context frame.
     372                 : 
     373                 :     On construction, pushes a new context frame onto the
     374                 :     thread-local stack. On destruction, drains any remaining
     375                 :     private queue items to the global queue and pops the frame.
     376                 : */
     377                 : struct reactor_thread_context_guard
     378                 : {
     379                 :     /// The context frame managed by this guard.
     380                 :     reactor_scheduler_context frame_;
     381                 : 
     382                 :     /// Construct the guard, pushing a frame for @a sched.
     383 HIT        1496 :     explicit reactor_thread_context_guard(
     384                 :         reactor_scheduler const* sched) noexcept
     385            1496 :         : frame_(sched, reactor_context_stack.get())
     386                 :     {
     387            1496 :         reactor_context_stack.set(&frame_);
     388            1496 :     }
     389                 : 
     390                 :     /// Destroy the guard, draining private work and popping the frame.
     391            1496 :     ~reactor_thread_context_guard() noexcept
     392                 :     {
     393            1496 :         if (!frame_.private_queue.empty())
     394 MIS           0 :             frame_.key->drain_thread_queue(
     395               0 :                 frame_.private_queue, frame_.private_outstanding_work);
     396 HIT        1496 :         reactor_context_stack.set(frame_.next);
     397            1496 :     }
     398                 : };
     399                 : 
     400                 : // ---- Inline implementations ------------------------------------------------
     401                 : 
     402                 : inline
     403            1496 : reactor_scheduler_context::reactor_scheduler_context(
     404                 :     reactor_scheduler const* k,
     405            1496 :     reactor_scheduler_context* n)
     406            1496 :     : key(k)
     407            1496 :     , next(n)
     408            1496 :     , private_outstanding_work(0)
     409            1496 :     , inline_budget(0)
     410            1496 :     , inline_budget_max(
     411            1496 :           static_cast<int>(k->inline_budget_initial()))
     412            1496 :     , unassisted(false)
     413                 : {
     414            1496 : }
     415                 : 
     416                 : inline void
     417              28 : reactor_scheduler::configure_reactor(
     418                 :     unsigned max_events,
     419                 :     unsigned budget_init,
     420                 :     unsigned budget_max,
     421                 :     unsigned unassisted)
     422                 : {
     423              54 :     if (max_events < 1 ||
     424              26 :         max_events > static_cast<unsigned>(std::numeric_limits<int>::max()))
     425                 :         throw std::out_of_range(
     426               2 :             "max_events_per_poll must be in [1, INT_MAX]");
     427              26 :     if (budget_max > static_cast<unsigned>(std::numeric_limits<int>::max()))
     428                 :         throw std::out_of_range(
     429 MIS           0 :             "inline_budget_max must be in [0, INT_MAX]");
     430                 : 
     431                 :     // Clamp initial and unassisted to budget_max.
     432 HIT          26 :     if (budget_init > budget_max)
     433               2 :         budget_init = budget_max;
     434              26 :     if (unassisted > budget_max)
     435               2 :         unassisted = budget_max;
     436                 : 
     437              26 :     max_events_per_poll_   = max_events;
     438              26 :     inline_budget_initial_ = budget_init;
     439              26 :     inline_budget_max_     = budget_max;
     440              26 :     unassisted_budget_     = unassisted;
     441              26 : }
     442                 : 
     443                 : inline void
     444           93273 : reactor_scheduler::reset_inline_budget() const noexcept
     445                 : {
     446                 :     // When budget is disabled (max==0), all paths below would no-op
     447                 :     // (inline_budget stays 0). Skip the TLS lookup entirely.
     448           93273 :     if (inline_budget_max_ == 0)
     449 MIS           0 :         return;
     450 HIT       93273 :     if (auto* ctx = reactor_find_context(this))
     451                 :     {
     452                 :         // Cap when no other thread absorbed queued work
     453           93273 :         if (ctx->unassisted)
     454                 :         {
     455           93273 :             ctx->inline_budget_max =
     456           93273 :                 static_cast<int>(unassisted_budget_);
     457           93273 :             ctx->inline_budget =
     458           93273 :                 static_cast<int>(unassisted_budget_);
     459           93273 :             return;
     460                 :         }
     461                 :         // Ramp up when previous cycle fully consumed budget.
     462                 :         // max(1, ...) ensures the doubling escapes zero.
     463 MIS           0 :         if (ctx->inline_budget == 0)
     464               0 :             ctx->inline_budget_max = (std::min)(
     465               0 :                 (std::max)(1, ctx->inline_budget_max) * 2,
     466               0 :                 static_cast<int>(inline_budget_max_));
     467               0 :         else if (ctx->inline_budget < ctx->inline_budget_max)
     468               0 :             ctx->inline_budget_max =
     469               0 :                 static_cast<int>(inline_budget_initial_);
     470               0 :         ctx->inline_budget = ctx->inline_budget_max;
     471                 :     }
     472                 : }
     473                 : 
     474                 : inline bool
     475 HIT      408267 : reactor_scheduler::try_consume_inline_budget() const noexcept
     476                 : {
     477          408267 :     if (inline_budget_max_ == 0)
     478 MIS           0 :         return false;
     479 HIT      408267 :     if (auto* ctx = reactor_find_context(this))
     480                 :     {
     481          408267 :         if (ctx->inline_budget > 0)
     482                 :         {
     483          326456 :             --ctx->inline_budget;
     484          326456 :             return true;
     485                 :         }
     486                 :     }
     487           81811 :     return false;
     488                 : }
     489                 : 
     490                 : inline void
     491            3686 : reactor_scheduler::post(std::coroutine_handle<> h) const
     492                 : {
     493                 :     struct post_handler final : scheduler_op
     494                 :     {
     495                 :         std::coroutine_handle<> h_;
     496                 : 
     497            3686 :         explicit post_handler(std::coroutine_handle<> h) : h_(h) {}
     498            7372 :         ~post_handler() override = default;
     499                 : 
     500            3674 :         void operator()() override
     501                 :         {
     502            3674 :             auto saved = h_;
     503            3674 :             delete this;
     504            3674 :             saved.resume();
     505            3674 :         }
     506                 : 
     507              12 :         void destroy() override
     508                 :         {
     509              12 :             auto saved = h_;
     510              12 :             delete this;
     511              12 :             saved.destroy();
     512              12 :         }
     513                 :     };
     514                 : 
     515            3686 :     auto ph = std::make_unique<post_handler>(h);
     516                 : 
     517            3686 :     if (auto* ctx = reactor_find_context(this))
     518                 :     {
     519              26 :         ++ctx->private_outstanding_work;
     520              26 :         ctx->private_queue.push(ph.release());
     521              26 :         return;
     522                 :     }
     523                 : 
     524            3660 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     525                 : 
     526            3660 :     lock_type lock(mutex_);
     527            3660 :     completed_ops_.push(ph.release());
     528            3660 :     wake_one_thread_and_unlock(lock);
     529            3686 : }
     530                 : 
     531                 : inline void
     532           90086 : reactor_scheduler::post(scheduler_op* h) const
     533                 : {
     534           90086 :     if (auto* ctx = reactor_find_context(this))
     535                 :     {
     536           89519 :         ++ctx->private_outstanding_work;
     537           89519 :         ctx->private_queue.push(h);
     538           89519 :         return;
     539                 :     }
     540                 : 
     541             567 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     542                 : 
     543             567 :     lock_type lock(mutex_);
     544             567 :     completed_ops_.push(h);
     545             567 :     wake_one_thread_and_unlock(lock);
     546             567 : }
     547                 : 
     548                 : inline void
     549           15417 : reactor_scheduler::post(capy::continuation& c) const
     550                 : {
     551           15417 :     if (auto* ctx = reactor_find_context(this))
     552                 :     {
     553            8144 :         ++ctx->private_outstanding_work;
     554            8144 :         ctx->private_queue.push(c);
     555            8144 :         return;
     556                 :     }
     557                 : 
     558            7273 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     559                 : 
     560            7273 :     lock_type lock(mutex_);
     561            7273 :     completed_ops_.push(c);
     562            7273 :     wake_one_thread_and_unlock(lock);
     563            7273 : }
     564                 : 
     565                 : inline bool
     566            7955 : reactor_scheduler::running_in_this_thread() const noexcept
     567                 : {
     568            7955 :     return reactor_find_context(this) != nullptr;
     569                 : }
     570                 : 
     571                 : inline void
     572            1477 : reactor_scheduler::stop()
     573                 : {
     574            1477 :     lock_type lock(mutex_);
     575            1477 :     if (!stopped_.load(std::memory_order_acquire))
     576                 :     {
     577            1381 :         stopped_.store(true, std::memory_order_release);
     578            1381 :         signal_all(lock);
     579            1381 :         interrupt_reactor();
     580                 :     }
     581            1477 : }
     582                 : 
     583                 : inline bool
     584              96 : reactor_scheduler::stopped() const noexcept
     585                 : {
     586              96 :     return stopped_.load(std::memory_order_acquire);
     587                 : }
     588                 : 
     589                 : inline void
     590             359 : reactor_scheduler::restart()
     591                 : {
     592             359 :     stopped_.store(false, std::memory_order_release);
     593             359 : }
     594                 : 
     595                 : inline std::size_t
     596            1422 : reactor_scheduler::run()
     597                 : {
     598            2844 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     599                 :     {
     600              99 :         stop();
     601              99 :         return 0;
     602                 :     }
     603                 : 
     604            1323 :     reactor_thread_context_guard ctx(this);
     605            1323 :     lock_type lock(mutex_);
     606                 : 
     607            1323 :     std::size_t n = 0;
     608                 :     for (;;)
     609                 :     {
     610          495148 :         if (!do_one(lock, -1, &ctx.frame_))
     611            1320 :             break;
     612          493825 :         if (n != (std::numeric_limits<std::size_t>::max)())
     613          493825 :             ++n;
     614          493825 :         if (!lock.owns_lock())
     615          401692 :             lock.lock();
     616                 :     }
     617            1320 :     return n;
     618            1326 : }
     619                 : 
     620                 : inline std::size_t
     621             110 : reactor_scheduler::run_one()
     622                 : {
     623             220 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     624                 :     {
     625               1 :         stop();
     626               1 :         return 0;
     627                 :     }
     628                 : 
     629             109 :     reactor_thread_context_guard ctx(this);
     630             109 :     lock_type lock(mutex_);
     631             109 :     return do_one(lock, -1, &ctx.frame_);
     632             109 : }
     633                 : 
     634                 : inline std::size_t
     635              64 : reactor_scheduler::wait_one(long usec)
     636                 : {
     637             128 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     638                 :     {
     639              22 :         stop();
     640              22 :         return 0;
     641                 :     }
     642                 : 
     643              42 :     reactor_thread_context_guard ctx(this);
     644              42 :     lock_type lock(mutex_);
     645              42 :     return do_one(lock, usec, &ctx.frame_);
     646              42 : }
     647                 : 
     648                 : inline std::size_t
     649              33 : reactor_scheduler::poll()
     650                 : {
     651              66 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     652                 :     {
     653              15 :         stop();
     654              15 :         return 0;
     655                 :     }
     656                 : 
     657              18 :     reactor_thread_context_guard ctx(this);
     658              18 :     lock_type lock(mutex_);
     659                 : 
     660              18 :     std::size_t n = 0;
     661                 :     for (;;)
     662                 :     {
     663              54 :         if (!do_one(lock, 0, &ctx.frame_))
     664              18 :             break;
     665              36 :         if (n != (std::numeric_limits<std::size_t>::max)())
     666              36 :             ++n;
     667              36 :         if (!lock.owns_lock())
     668              36 :             lock.lock();
     669                 :     }
     670              18 :     return n;
     671              18 : }
     672                 : 
     673                 : inline std::size_t
     674               9 : reactor_scheduler::poll_one()
     675                 : {
     676              18 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     677                 :     {
     678               5 :         stop();
     679               5 :         return 0;
     680                 :     }
     681                 : 
     682               4 :     reactor_thread_context_guard ctx(this);
     683               4 :     lock_type lock(mutex_);
     684               4 :     return do_one(lock, 0, &ctx.frame_);
     685               4 : }
     686                 : 
     687                 : inline void
     688           27117 : reactor_scheduler::work_started() noexcept
     689                 : {
     690           27117 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     691           27117 : }
     692                 : 
     693                 : inline void
     694           44305 : reactor_scheduler::work_finished() noexcept
     695                 : {
     696           88610 :     if (outstanding_work_.fetch_sub(1, std::memory_order_acq_rel) == 1)
     697            1320 :         stop();
     698           44305 : }
     699                 : 
     700                 : inline void
     701          373897 : reactor_scheduler::compensating_work_started() const noexcept
     702                 : {
     703          373897 :     auto* ctx = reactor_find_context(this);
     704          373897 :     if (ctx)
     705          373897 :         ++ctx->private_outstanding_work;
     706          373897 : }
     707                 : 
     708                 : inline void
     709 MIS           0 : reactor_scheduler::drain_thread_queue(
     710                 :     ready_queue& queue, std::int64_t count) const
     711                 : {
     712               0 :     if (count > 0)
     713               0 :         outstanding_work_.fetch_add(count, std::memory_order_relaxed);
     714                 : 
     715               0 :     lock_type lock(mutex_);
     716               0 :     completed_ops_.splice(queue);
     717               0 :     if (count > 0)
     718               0 :         maybe_unlock_and_signal_one(lock);
     719               0 : }
     720                 : 
     721                 : inline void
     722 HIT       10996 : reactor_scheduler::post_deferred_completions(ready_queue& ops) const
     723                 : {
     724           10996 :     if (ops.empty())
     725           10996 :         return;
     726                 : 
     727 MIS           0 :     if (auto* ctx = reactor_find_context(this))
     728                 :     {
     729               0 :         ctx->private_queue.splice(ops);
     730               0 :         return;
     731                 :     }
     732                 : 
     733               0 :     lock_type lock(mutex_);
     734               0 :     completed_ops_.splice(ops);
     735               0 :     wake_one_thread_and_unlock(lock);
     736               0 : }
     737                 : 
     738                 : inline void
     739 HIT        1790 : reactor_scheduler::shutdown_drain()
     740                 : {
     741            1790 :     lock_type lock(mutex_);
     742                 : 
     743            3934 :     while (auto e = completed_ops_.pop())
     744                 :     {
     745            2144 :         if (ready_is_continuation(e))
     746                 :         {
     747               8 :             lock.unlock();
     748               8 :             if (auto h = ready_as_cont(e)->h)
     749               8 :                 h.destroy();
     750               8 :             lock.lock();
     751                 :         }
     752                 :         else
     753                 :         {
     754            2136 :             auto* op = ready_as_op(e);
     755            2136 :             if (op == &task_op_)
     756            1787 :                 continue;
     757             349 :             lock.unlock();
     758             349 :             op->destroy();
     759             349 :             lock.lock();
     760                 :         }
     761            2144 :     }
     762                 : 
     763            1790 :     signal_all(lock);
     764            1790 : }
     765                 : 
     766                 : inline void
     767            3171 : reactor_scheduler::signal_all(lock_type&) const
     768                 : {
     769            3171 :     state_ |= signaled_bit;
     770            3171 :     cond_.notify_all();
     771            3171 : }
     772                 : 
     773                 : inline bool
     774           11500 : reactor_scheduler::maybe_unlock_and_signal_one(
     775                 :     lock_type& lock) const
     776                 : {
     777           11500 :     state_ |= signaled_bit;
     778           11500 :     if (state_ > signaled_bit)
     779                 :     {
     780 MIS           0 :         lock.unlock();
     781               0 :         cond_.notify_one();
     782               0 :         return true;
     783                 :     }
     784 HIT       11500 :     return false;
     785                 : }
     786                 : 
     787                 : inline bool
     788          542866 : reactor_scheduler::unlock_and_signal_one(
     789                 :     lock_type& lock) const
     790                 : {
     791          542866 :     state_ |= signaled_bit;
     792          542866 :     bool have_waiters = state_ > signaled_bit;
     793          542866 :     lock.unlock();
     794          542866 :     if (have_waiters)
     795               6 :         cond_.notify_one();
     796          542866 :     return have_waiters;
     797                 : }
     798                 : 
     799                 : inline void
     800               6 : reactor_scheduler::clear_signal() const
     801                 : {
     802               6 :     state_ &= ~signaled_bit;
     803               6 : }
     804                 : 
     805                 : inline void
     806               6 : reactor_scheduler::wait_for_signal(
     807                 :     lock_type& lock) const
     808                 : {
     809              14 :     while ((state_ & signaled_bit) == 0)
     810                 :     {
     811               8 :         state_ += waiter_increment;
     812               8 :         cond_.wait(lock);
     813               8 :         state_ -= waiter_increment;
     814                 :     }
     815               6 : }
     816                 : 
     817                 : inline void
     818 MIS           0 : reactor_scheduler::wait_for_signal_for(
     819                 :     lock_type& lock, long timeout_us) const
     820                 : {
     821               0 :     if ((state_ & signaled_bit) == 0)
     822                 :     {
     823               0 :         state_ += waiter_increment;
     824               0 :         cond_.wait_for(lock, std::chrono::microseconds(timeout_us));
     825               0 :         state_ -= waiter_increment;
     826                 :     }
     827               0 : }
     828                 : 
     829                 : inline void
     830 HIT       11500 : reactor_scheduler::wake_one_thread_and_unlock(
     831                 :     lock_type& lock) const
     832                 : {
     833           11500 :     if (maybe_unlock_and_signal_one(lock))
     834 MIS           0 :         return;
     835                 : 
     836 HIT       11500 :     if (task_running_.load(std::memory_order_relaxed) && !task_interrupted_)
     837                 :     {
     838             165 :         task_interrupted_ = true;
     839             165 :         lock.unlock();
     840             165 :         interrupt_reactor();
     841                 :     }
     842                 :     else
     843                 :     {
     844           11335 :         lock.unlock();
     845                 :     }
     846                 : }
     847                 : 
     848          493998 : inline reactor_scheduler::work_cleanup::~work_cleanup()
     849                 : {
     850          493998 :     if (ctx)
     851                 :     {
     852          493998 :         std::int64_t produced = ctx->private_outstanding_work;
     853          493998 :         if (produced > 1)
     854             322 :             sched->outstanding_work_.fetch_add(
     855                 :                 produced - 1, std::memory_order_relaxed);
     856          493676 :         else if (produced < 1)
     857           28252 :             sched->work_finished();
     858          493998 :         ctx->private_outstanding_work = 0;
     859                 : 
     860          493998 :         if (!ctx->private_queue.empty())
     861                 :         {
     862           92155 :             lock->lock();
     863           92155 :             sched->completed_ops_.splice(ctx->private_queue);
     864                 :         }
     865                 :     }
     866                 :     else
     867                 :     {
     868 MIS           0 :         sched->work_finished();
     869                 :     }
     870 HIT      493998 : }
     871                 : 
     872          797798 : inline reactor_scheduler::task_cleanup::~task_cleanup()
     873                 : {
     874          398899 :     if (!ctx)
     875 MIS           0 :         return;
     876                 : 
     877 HIT      398899 :     if (ctx->private_outstanding_work > 0)
     878                 :     {
     879            5505 :         sched->outstanding_work_.fetch_add(
     880            5505 :             ctx->private_outstanding_work, std::memory_order_relaxed);
     881            5505 :         ctx->private_outstanding_work = 0;
     882                 :     }
     883                 : 
     884          398899 :     if (!ctx->private_queue.empty())
     885                 :     {
     886            5505 :         if (!lock->owns_lock())
     887 MIS           0 :             lock->lock();
     888 HIT        5505 :         sched->completed_ops_.splice(ctx->private_queue);
     889                 :     }
     890          398899 : }
     891                 : 
     892                 : inline std::size_t
     893          495357 : reactor_scheduler::do_one(
     894                 :     lock_type& lock, long timeout_us, context_type* ctx)
     895                 : {
     896                 :     for (;;)
     897                 :     {
     898          894241 :         if (stopped_.load(std::memory_order_acquire))
     899            1322 :             return 0;
     900                 : 
     901          892919 :         std::uintptr_t e = completed_ops_.pop();
     902          892919 :         scheduler_op* op = ready_is_continuation(e) ? nullptr : ready_as_op(e);
     903                 : 
     904                 :         // Handle reactor sentinel — time to poll for I/O
     905          892919 :         if (op == &task_op_)
     906                 :         {
     907                 :             bool more_handlers =
     908          398915 :                 !completed_ops_.empty() || (ctx && !ctx->private_queue.empty());
     909                 : 
     910          748934 :             if (!more_handlers &&
     911          700038 :                 (outstanding_work_.load(std::memory_order_acquire) == 0 ||
     912                 :                  timeout_us == 0))
     913                 :             {
     914              16 :                 completed_ops_.push(&task_op_);
     915              16 :                 return 0;
     916                 :             }
     917                 : 
     918          398899 :             long task_timeout_us = more_handlers ? 0 : timeout_us;
     919          398899 :             task_interrupted_ = task_timeout_us == 0;
     920          398899 :             task_running_.store(true, std::memory_order_release);
     921                 : 
     922                 :             // Wake a peer to take the pending handlers while this thread
     923                 :             // polls the reactor; skipped when one_thread_ (no peer exists).
     924          398899 :             if (more_handlers && !one_thread_)
     925           48889 :                 unlock_and_signal_one(lock);
     926                 : 
     927                 :             try
     928                 :             {
     929          398899 :                 run_task(lock, ctx, task_timeout_us);
     930                 :             }
     931               3 :             catch (...)
     932                 :             {
     933               3 :                 task_running_.store(false, std::memory_order_relaxed);
     934               3 :                 throw;
     935               3 :             }
     936                 : 
     937          398896 :             task_running_.store(false, std::memory_order_relaxed);
     938          398896 :             completed_ops_.push(&task_op_);
     939          398896 :             if (timeout_us > 0)
     940              18 :                 return 0;
     941          398878 :             continue;
     942          398878 :         }
     943                 : 
     944                 :         // Handle ready entry (op or continuation)
     945          494004 :         if (e != 0)
     946                 :         {
     947          493998 :             bool more = !completed_ops_.empty();
     948                 : 
     949          493998 :             if (more && !one_thread_)
     950                 :             {
     951                 :                 // Wake a peer for the remaining work; unassisted if none
     952                 :                 // was parked to take it.
     953          493977 :                 ctx->unassisted = !unlock_and_signal_one(lock);
     954                 :             }
     955                 :             else
     956                 :             {
     957                 :                 // No peer to wake (one_thread_, or nothing more queued).
     958              21 :                 ctx->unassisted = more;
     959              21 :                 lock.unlock();
     960                 :             }
     961                 : 
     962          493998 :             [[maybe_unused]] work_cleanup on_exit{this, &lock, ctx};
     963                 : 
     964          493998 :             if (ready_is_continuation(e))
     965           15409 :                 ready_as_cont(e)->h.resume();
     966                 :             else
     967          478589 :                 (*op)();
     968          493998 :             return 1;
     969          493998 :         }
     970                 : 
     971                 :         // Try private queue before blocking
     972               6 :         if (reactor_drain_private_queue(ctx, outstanding_work_, completed_ops_))
     973 MIS           0 :             continue;
     974                 : 
     975 HIT          12 :         if (outstanding_work_.load(std::memory_order_acquire) == 0 ||
     976                 :             timeout_us == 0)
     977 MIS           0 :             return 0;
     978                 : 
     979 HIT           6 :         clear_signal();
     980               6 :         if (timeout_us < 0)
     981               6 :             wait_for_signal(lock);
     982                 :         else
     983 MIS           0 :             wait_for_signal_for(lock, timeout_us);
     984 HIT      398884 :     }
     985                 : }
     986                 : 
     987                 : } // namespace boost::corosio::detail
     988                 : 
     989                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_SCHEDULER_HPP
        

Generated by: LCOV version 2.3