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: 95.8 % 333 319 14
Test Date: 2026-09-06 03:35:56 Functions: 100.0 % 43 43

           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     1041465 : reactor_find_context(reactor_scheduler const* self) noexcept
      80                 : {
      81         1041465 :     for (auto* c = reactor_context_stack.get(); c != nullptr; c = c->next)
      82                 :     {
      83         1019880 :         if (c->key == self)
      84         1019880 :             return c;
      85                 :     }
      86           21585 :     return nullptr;
      87                 : }
      88                 : 
      89                 : 
      90                 : /** Non-template base for reactor-backed scheduler implementations.
      91                 : 
      92                 :     Provides the complete threading model shared by epoll, kqueue,
      93                 :     and select schedulers: signal state machine, inline completion
      94                 :     budget, work counting, run/poll methods, and the do_one event
      95                 :     loop.
      96                 : 
      97                 :     Derived classes provide platform-specific hooks by overriding:
      98                 :     - `run_task(lock, ctx)` to run the reactor poll
      99                 :     - `interrupt_reactor()` to wake a blocked reactor
     100                 : 
     101                 :     De-templated from the original CRTP design to eliminate
     102                 :     duplicate instantiations when multiple backends are compiled
     103                 :     into the same binary. Virtual dispatch for run_task (called
     104                 :     once per reactor cycle, before a blocking syscall) has
     105                 :     negligible overhead.
     106                 : 
     107                 :     @par Thread Safety
     108                 :     All public member functions are thread-safe.
     109                 : */
     110                 : class reactor_scheduler
     111                 :     : public scheduler
     112                 :     , public capy::execution_context::service
     113                 : {
     114                 : public:
     115                 :     using key_type     = scheduler;
     116                 :     using context_type = reactor_scheduler_context;
     117                 :     using mutex_type = conditionally_enabled_mutex;
     118                 :     using lock_type = mutex_type::scoped_lock;
     119                 :     using event_type = conditionally_enabled_event;
     120                 : 
     121                 :     /// Post a coroutine for deferred execution.
     122                 :     void post(std::coroutine_handle<> h) const override;
     123                 : 
     124                 :     /// Post a scheduler operation for deferred execution.
     125                 :     void post(scheduler_op* h) const override;
     126                 : 
     127                 :     /// Post a continuation for deferred execution.
     128                 :     void post(capy::continuation&) const override;
     129                 : 
     130                 :     /// Return true if called from a thread running this scheduler.
     131                 :     bool running_in_this_thread() const noexcept override;
     132                 : 
     133                 :     /// Request the scheduler to stop dispatching handlers.
     134                 :     void stop() override;
     135                 : 
     136                 :     /// Return true if the scheduler has been stopped.
     137                 :     bool stopped() const noexcept override;
     138                 : 
     139                 :     /// Reset the stopped state so `run()` can resume.
     140                 :     void restart() override;
     141                 : 
     142                 :     /// Run the event loop until no work remains.
     143                 :     std::size_t run() override;
     144                 : 
     145                 :     /// Run until one handler completes or no work remains.
     146                 :     std::size_t run_one() override;
     147                 : 
     148                 :     /// Run until one handler completes or @a usec elapses.
     149                 :     std::size_t wait_one(long usec) override;
     150                 : 
     151                 :     /// Run ready handlers without blocking.
     152                 :     std::size_t poll() override;
     153                 : 
     154                 :     /// Run at most one ready handler without blocking.
     155                 :     std::size_t poll_one() override;
     156                 : 
     157                 :     /// Increment the outstanding work count.
     158                 :     void work_started() noexcept override;
     159                 : 
     160                 :     /// Decrement the outstanding work count, stopping on zero.
     161                 :     void work_finished() noexcept override;
     162                 : 
     163                 :     /** Reset the thread's inline completion budget.
     164                 : 
     165                 :         Called at the start of each posted completion handler to
     166                 :         grant a fresh budget for speculative inline completions.
     167                 :     */
     168                 :     void reset_inline_budget() const noexcept;
     169                 : 
     170                 :     /** Consume one unit of inline budget if available.
     171                 : 
     172                 :         @return True if budget was available and consumed.
     173                 :     */
     174                 :     bool try_consume_inline_budget() const noexcept;
     175                 : 
     176                 :     /** Offset a forthcoming work_finished from work_cleanup.
     177                 : 
     178                 :         Called by descriptor_state when all I/O returned EAGAIN and
     179                 :         no handler will be executed. Must be called from a scheduler
     180                 :         thread.
     181                 :     */
     182                 :     void compensating_work_started() const noexcept;
     183                 : 
     184                 : 
     185                 :     /** Post completed operations for deferred invocation.
     186                 : 
     187                 :         If called from a thread running this scheduler, operations
     188                 :         go to the thread's private queue (fast path). Otherwise,
     189                 :         operations are added to the global queue under mutex and a
     190                 :         waiter is signaled.
     191                 : 
     192                 :         @par Preconditions
     193                 :         work_started() must have been called for each operation.
     194                 : 
     195                 :         @param ops Queue of operations to post.
     196                 :     */
     197                 :     void post_deferred_completions(ready_queue& ops) const;
     198                 : 
     199                 :     /** Apply runtime configuration to the scheduler.
     200                 : 
     201                 :         Called by `io_context` after construction. Values that do
     202                 :         not apply to this backend are silently ignored.
     203                 : 
     204                 :         @param max_events  Event buffer size for epoll/kqueue.
     205                 :         @param budget_init Starting inline completion budget.
     206                 :         @param budget_max  Hard ceiling on adaptive budget ramp-up.
     207                 :         @param unassisted  Budget when single-threaded.
     208                 :     */
     209                 :     virtual void configure_reactor(
     210                 :         unsigned max_events,
     211                 :         unsigned budget_init,
     212                 :         unsigned budget_max,
     213                 :         unsigned unassisted);
     214                 : 
     215                 :     /// Return the configured initial inline budget.
     216            1964 :     unsigned inline_budget_initial() const noexcept
     217                 :     {
     218            1964 :         return inline_budget_initial_;
     219                 :     }
     220                 : 
     221                 :     /// Return true when scheduler locking is disabled (fully-lockless tier).
     222             298 :     bool scheduler_locking_disabled() const noexcept override
     223                 :     {
     224             298 :         return scheduler_locking_disabled_;
     225                 :     }
     226                 : 
     227            2095 :     void configure_threading(threading_config cfg) noexcept override
     228                 :     {
     229            2095 :         scheduler_locking_disabled_ = !cfg.scheduler_locking;
     230                 :         // reactor_io_locking takes effect at descriptor registration (see the
     231                 :         // register_descriptor overrides), not here.
     232            2095 :         reactor_io_locking_ = cfg.reactor_io_locking;
     233            2095 :         one_thread_         = cfg.one_thread;
     234            2095 :         mutex_.set_enabled(cfg.scheduler_locking);
     235            2095 :         cond_.set_enabled(cfg.scheduler_locking);
     236            2095 :     }
     237                 : 
     238                 : protected:
     239                 :     timer_service* timer_svc_ = nullptr;
     240                 :     bool scheduler_locking_disabled_ = false;
     241                 :     bool reactor_io_locking_ = true;
     242                 :     bool one_thread_ = false;
     243                 : 
     244            2107 :     reactor_scheduler() = default;
     245                 : 
     246                 :     /** Drain completed_ops during shutdown.
     247                 : 
     248                 :         Pops all operations from the global queue and destroys them,
     249                 :         skipping the task sentinel. Signals all waiting threads.
     250                 :         Derived classes call this from their shutdown() override
     251                 :         before performing platform-specific cleanup.
     252                 :     */
     253                 :     void shutdown_drain();
     254                 : 
     255                 :     /// RAII guard that re-inserts the task sentinel after `run_task`.
     256                 :     struct task_cleanup
     257                 :     {
     258                 :         reactor_scheduler const* sched;
     259                 :         lock_type* lock;
     260                 :         context_type& ctx;
     261                 :         ~task_cleanup();
     262                 :     };
     263                 : 
     264                 :     mutable mutex_type mutex_{true};
     265                 :     mutable event_type cond_{true};
     266                 :     mutable ready_queue completed_ops_;
     267                 :     mutable std::atomic<std::int64_t> outstanding_work_{0};
     268                 :     std::atomic<bool> stopped_{false};
     269                 :     mutable std::atomic<bool> task_running_{false};
     270                 :     mutable bool task_interrupted_ = false;
     271                 : 
     272                 :     // Runtime-configurable reactor tuning parameters.
     273                 :     // Defaults match the library's built-in values.
     274                 :     unsigned max_events_per_poll_   = 128;
     275                 :     unsigned inline_budget_initial_ = 2;
     276                 :     unsigned inline_budget_max_     = 16;
     277                 :     unsigned unassisted_budget_     = 4;
     278                 : 
     279                 :     /// Bit 0 of `state_`: set when the condvar should be signaled.
     280                 :     static constexpr std::size_t signaled_bit = 1;
     281                 : 
     282                 :     /// Increment per waiting thread in `state_`.
     283                 :     static constexpr std::size_t waiter_increment = 2;
     284                 :     mutable std::size_t state_                    = 0;
     285                 : 
     286                 :     /// Sentinel op that triggers a reactor poll when dequeued.
     287                 :     struct task_op final : scheduler_op
     288                 :     {
     289                 :         // LCOV_EXCL_START: the sentinel is intercepted by pointer
     290                 :         // identity; its virtuals exist for vtable completeness.
     291                 :         void operator()() override {}
     292                 :         void destroy() override {}
     293                 :         // LCOV_EXCL_STOP
     294                 :     };
     295                 :     task_op task_op_;
     296                 : 
     297                 :     /** Run the platform-specific reactor poll.
     298                 : 
     299                 :         @par Postconditions
     300                 :         `lock` is owned on return, however the poll ended. An
     301                 :         implementation that unlocks around the blocking call owes the
     302                 :         caller a matching re-acquire on every path out, including the
     303                 :         errors it retries rather than reports.
     304                 :     */
     305                 :     virtual void
     306                 :     run_task(lock_type& lock, context_type& ctx,
     307                 :         long timeout_us) = 0;
     308                 : 
     309                 :     /// Wake a blocked reactor (e.g. write to eventfd or pipe).
     310                 :     virtual void interrupt_reactor() const = 0;
     311                 : 
     312                 : private:
     313                 :     struct work_cleanup
     314                 :     {
     315                 :         reactor_scheduler* sched;
     316                 :         lock_type* lock;
     317                 :         context_type& ctx;
     318                 :         ~work_cleanup();
     319                 :     };
     320                 : 
     321                 :     std::size_t do_one(
     322                 :         lock_type& lock, long timeout_us, context_type& ctx);
     323                 : 
     324                 :     void signal_all(lock_type& lock) const;
     325                 :     bool maybe_unlock_and_signal_one(lock_type& lock) const;
     326                 :     bool unlock_and_signal_one(lock_type& lock) const;
     327                 :     void clear_signal() const;
     328                 :     void wait_for_signal(lock_type& lock) const;
     329                 :     void wait_for_signal_for(
     330                 :         lock_type& lock, long timeout_us) const;
     331                 :     void wake_one_thread_and_unlock(lock_type& lock) const;
     332                 : };
     333                 : 
     334                 : /** RAII guard that pushes/pops a scheduler context frame.
     335                 : 
     336                 :     On construction, pushes a new context frame onto the
     337                 :     thread-local stack. On destruction, drains any remaining
     338                 :     private queue items to the global queue and pops the frame.
     339                 : */
     340                 : struct reactor_thread_context_guard
     341                 : {
     342                 :     /// The context frame managed by this guard.
     343                 :     reactor_scheduler_context frame_;
     344                 : 
     345                 :     /// Construct the guard, pushing a frame for @a sched.
     346            1964 :     explicit reactor_thread_context_guard(
     347                 :         reactor_scheduler const* sched) noexcept
     348            1964 :         : frame_(sched, reactor_context_stack.get())
     349                 :     {
     350            1964 :         reactor_context_stack.set(&frame_);
     351            1964 :     }
     352                 : 
     353                 :     /** Destroy the guard, popping the frame.
     354                 : 
     355                 :         The private queue is empty here by invariant: work_cleanup and
     356                 :         task_cleanup splice it to the global queue after every handler
     357                 :         and every reactor pass.
     358                 :     */
     359            1964 :     ~reactor_thread_context_guard() noexcept
     360                 :     {
     361            1964 :         reactor_context_stack.set(frame_.next);
     362            1964 :     }
     363                 : };
     364                 : 
     365                 : // ---- Inline implementations ------------------------------------------------
     366                 : 
     367                 : inline
     368            1964 : reactor_scheduler_context::reactor_scheduler_context(
     369                 :     reactor_scheduler const* k,
     370            1964 :     reactor_scheduler_context* n)
     371            1964 :     : key(k)
     372            1964 :     , next(n)
     373            1964 :     , private_outstanding_work(0)
     374            1964 :     , inline_budget(0)
     375            1964 :     , inline_budget_max(
     376            1964 :           static_cast<int>(k->inline_budget_initial()))
     377            1964 :     , unassisted(false)
     378                 : {
     379            1964 : }
     380                 : 
     381                 : inline void
     382              36 : reactor_scheduler::configure_reactor(
     383                 :     unsigned max_events,
     384                 :     unsigned budget_init,
     385                 :     unsigned budget_max,
     386                 :     unsigned unassisted)
     387                 : {
     388              70 :     if (max_events < 1 ||
     389              34 :         max_events > static_cast<unsigned>(std::numeric_limits<int>::max()))
     390                 :         throw std::out_of_range(
     391               2 :             "max_events_per_poll must be in [1, INT_MAX]");
     392              34 :     if (budget_max > static_cast<unsigned>(std::numeric_limits<int>::max()))
     393                 :         throw std::out_of_range(
     394               2 :             "inline_budget_max must be in [0, INT_MAX]");
     395                 : 
     396                 :     // Clamp initial and unassisted to budget_max.
     397              32 :     if (budget_init > budget_max)
     398               8 :         budget_init = budget_max;
     399              32 :     if (unassisted > budget_max)
     400               8 :         unassisted = budget_max;
     401                 : 
     402              32 :     max_events_per_poll_   = max_events;
     403              32 :     inline_budget_initial_ = budget_init;
     404              32 :     inline_budget_max_     = budget_max;
     405              32 :     unassisted_budget_     = unassisted;
     406              32 : }
     407                 : 
     408                 : inline void
     409          100371 : reactor_scheduler::reset_inline_budget() const noexcept
     410                 : {
     411                 :     // When budget is disabled (max==0), all paths below would no-op
     412                 :     // (inline_budget stays 0). Skip the TLS lookup entirely.
     413          100371 :     if (inline_budget_max_ == 0)
     414              30 :         return;
     415          100341 :     if (auto* ctx = reactor_find_context(this))
     416                 :     {
     417                 :         // Cap when no other thread absorbed queued work
     418          100341 :         if (ctx->unassisted)
     419                 :         {
     420          100341 :             ctx->inline_budget_max =
     421          100341 :                 static_cast<int>(unassisted_budget_);
     422          100341 :             ctx->inline_budget =
     423          100341 :                 static_cast<int>(unassisted_budget_);
     424          100341 :             return;
     425                 :         }
     426                 :         // Ramp up when previous cycle fully consumed budget.
     427                 :         // max(1, ...) ensures the doubling escapes zero.
     428 MIS           0 :         if (ctx->inline_budget == 0)
     429               0 :             ctx->inline_budget_max = (std::min)(
     430               0 :                 (std::max)(1, ctx->inline_budget_max) * 2,
     431               0 :                 static_cast<int>(inline_budget_max_));
     432               0 :         else if (ctx->inline_budget < ctx->inline_budget_max)
     433               0 :             ctx->inline_budget_max =
     434               0 :                 static_cast<int>(inline_budget_initial_);
     435               0 :         ctx->inline_budget = ctx->inline_budget_max;
     436                 :     }
     437                 : }
     438                 : 
     439                 : inline bool
     440 HIT      428621 : reactor_scheduler::try_consume_inline_budget() const noexcept
     441                 : {
     442          428621 :     if (inline_budget_max_ == 0)
     443              26 :         return false;
     444          428595 :     if (auto* ctx = reactor_find_context(this))
     445                 :     {
     446          428595 :         if (ctx->inline_budget > 0)
     447                 :         {
     448          342700 :             --ctx->inline_budget;
     449          342700 :             return true;
     450                 :         }
     451                 :     }
     452           85895 :     return false;
     453                 : }
     454                 : 
     455                 : inline void
     456            3756 : reactor_scheduler::post(std::coroutine_handle<> h) const
     457                 : {
     458                 :     struct post_handler final : scheduler_op
     459                 :     {
     460                 :         std::coroutine_handle<> h_;
     461                 : 
     462            3756 :         explicit post_handler(std::coroutine_handle<> h) : h_(h) {}
     463            7512 :         ~post_handler() override = default;
     464                 : 
     465            3744 :         void operator()() override
     466                 :         {
     467            3744 :             auto saved = h_;
     468            3744 :             delete this;
     469            3744 :             saved.resume();
     470            3744 :         }
     471                 : 
     472              12 :         void destroy() override
     473                 :         {
     474              12 :             auto saved = h_;
     475              12 :             delete this;
     476              12 :             saved.destroy();
     477              12 :         }
     478                 :     };
     479                 : 
     480            3756 :     auto ph = std::make_unique<post_handler>(h);
     481                 : 
     482            3756 :     if (auto* ctx = reactor_find_context(this))
     483                 :     {
     484              96 :         ++ctx->private_outstanding_work;
     485              96 :         ctx->private_queue.push(ph.release());
     486              96 :         return;
     487                 :     }
     488                 : 
     489            3660 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     490                 : 
     491            3660 :     lock_type lock(mutex_);
     492            3660 :     completed_ops_.push(ph.release());
     493            3660 :     wake_one_thread_and_unlock(lock);
     494            3756 : }
     495                 : 
     496                 : inline void
     497          108008 : reactor_scheduler::post(scheduler_op* h) const
     498                 : {
     499          108008 :     if (auto* ctx = reactor_find_context(this))
     500                 :     {
     501          107082 :         ++ctx->private_outstanding_work;
     502          107082 :         ctx->private_queue.push(h);
     503          107082 :         return;
     504                 :     }
     505                 : 
     506             926 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     507                 : 
     508             926 :     lock_type lock(mutex_);
     509             926 :     completed_ops_.push(h);
     510             926 :     wake_one_thread_and_unlock(lock);
     511             926 : }
     512                 : 
     513                 : inline void
     514           26090 : reactor_scheduler::post(capy::continuation& c) const
     515                 : {
     516           26090 :     if (auto* ctx = reactor_find_context(this))
     517                 :     {
     518           17589 :         ++ctx->private_outstanding_work;
     519           17589 :         ctx->private_queue.push(c);
     520           17589 :         return;
     521                 :     }
     522                 : 
     523            8501 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     524                 : 
     525            8501 :     lock_type lock(mutex_);
     526            8501 :     completed_ops_.push(c);
     527            8501 :     wake_one_thread_and_unlock(lock);
     528            8501 : }
     529                 : 
     530                 : inline bool
     531            9438 : reactor_scheduler::running_in_this_thread() const noexcept
     532                 : {
     533            9438 :     return reactor_find_context(this) != nullptr;
     534                 : }
     535                 : 
     536                 : inline void
     537            1846 : reactor_scheduler::stop()
     538                 : {
     539            1846 :     lock_type lock(mutex_);
     540            1846 :     if (!stopped_.load(std::memory_order_acquire))
     541                 :     {
     542            1743 :         stopped_.store(true, std::memory_order_release);
     543            1743 :         signal_all(lock);
     544            1743 :         interrupt_reactor();
     545                 :     }
     546            1846 : }
     547                 : 
     548                 : inline bool
     549             137 : reactor_scheduler::stopped() const noexcept
     550                 : {
     551             137 :     return stopped_.load(std::memory_order_acquire);
     552                 : }
     553                 : 
     554                 : inline void
     555             457 : reactor_scheduler::restart()
     556                 : {
     557             457 :     stopped_.store(false, std::memory_order_release);
     558             457 : }
     559                 : 
     560                 : inline std::size_t
     561            1780 : reactor_scheduler::run()
     562                 : {
     563            3560 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     564                 :     {
     565             103 :         stop();
     566             103 :         return 0;
     567                 :     }
     568                 : 
     569            1677 :     reactor_thread_context_guard ctx(this);
     570            1677 :     lock_type lock(mutex_);
     571                 : 
     572            1677 :     std::size_t n = 0;
     573                 :     for (;;)
     574                 :     {
     575          518271 :         if (!do_one(lock, -1, ctx.frame_))
     576            1674 :             break;
     577          516594 :         if (n != (std::numeric_limits<std::size_t>::max)())
     578          516594 :             ++n;
     579          516594 :         if (!lock.owns_lock())
     580          410809 :             lock.lock();
     581                 :     }
     582            1674 :     return n;
     583            1680 : }
     584                 : 
     585                 : inline std::size_t
     586             112 : reactor_scheduler::run_one()
     587                 : {
     588             224 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     589                 :     {
     590               3 :         stop();
     591               3 :         return 0;
     592                 :     }
     593                 : 
     594             109 :     reactor_thread_context_guard ctx(this);
     595             109 :     lock_type lock(mutex_);
     596             109 :     return do_one(lock, -1, ctx.frame_);
     597             109 : }
     598                 : 
     599                 : inline std::size_t
     600             163 : reactor_scheduler::wait_one(long usec)
     601                 : {
     602             326 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     603                 :     {
     604              25 :         stop();
     605              25 :         return 0;
     606                 :     }
     607                 : 
     608             138 :     reactor_thread_context_guard ctx(this);
     609             138 :     lock_type lock(mutex_);
     610             138 :     return do_one(lock, usec, ctx.frame_);
     611             138 : }
     612                 : 
     613                 : inline std::size_t
     614              49 : reactor_scheduler::poll()
     615                 : {
     616              98 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     617                 :     {
     618              15 :         stop();
     619              15 :         return 0;
     620                 :     }
     621                 : 
     622              34 :     reactor_thread_context_guard ctx(this);
     623              34 :     lock_type lock(mutex_);
     624                 : 
     625              34 :     std::size_t n = 0;
     626                 :     for (;;)
     627                 :     {
     628              75 :         if (!do_one(lock, 0, ctx.frame_))
     629              34 :             break;
     630              41 :         if (n != (std::numeric_limits<std::size_t>::max)())
     631              41 :             ++n;
     632              41 :         if (!lock.owns_lock())
     633              41 :             lock.lock();
     634                 :     }
     635              34 :     return n;
     636              34 : }
     637                 : 
     638                 : inline std::size_t
     639              11 : reactor_scheduler::poll_one()
     640                 : {
     641              22 :     if (outstanding_work_.load(std::memory_order_acquire) == 0)
     642                 :     {
     643               5 :         stop();
     644               5 :         return 0;
     645                 :     }
     646                 : 
     647               6 :     reactor_thread_context_guard ctx(this);
     648               6 :     lock_type lock(mutex_);
     649               6 :     return do_one(lock, 0, ctx.frame_);
     650               6 : }
     651                 : 
     652                 : inline void
     653           40802 : reactor_scheduler::work_started() noexcept
     654                 : {
     655           40802 :     outstanding_work_.fetch_add(1, std::memory_order_relaxed);
     656           40802 : }
     657                 : 
     658                 : inline void
     659           73005 : reactor_scheduler::work_finished() noexcept
     660                 : {
     661          146010 :     if (outstanding_work_.fetch_sub(1, std::memory_order_acq_rel) == 1)
     662            1680 :         stop();
     663           73005 : }
     664                 : 
     665                 : inline void
     666          365235 : reactor_scheduler::compensating_work_started() const noexcept
     667                 : {
     668          365235 :     auto* ctx = reactor_find_context(this);
     669          365235 :     if (ctx)
     670          365235 :         ++ctx->private_outstanding_work;
     671          365235 : }
     672                 : 
     673                 : 
     674                 : inline void
     675           13823 : reactor_scheduler::post_deferred_completions(ready_queue& ops) const
     676                 : {
     677           13823 :     if (ops.empty())
     678           13823 :         return;
     679                 : 
     680               2 :     if (auto* ctx = reactor_find_context(this))
     681                 :     {
     682               2 :         ctx->private_queue.splice(ops);
     683               2 :         return;
     684                 :     }
     685                 : 
     686 MIS           0 :     lock_type lock(mutex_);
     687               0 :     completed_ops_.splice(ops);
     688               0 :     wake_one_thread_and_unlock(lock);
     689               0 : }
     690                 : 
     691                 : inline void
     692 HIT        2095 : reactor_scheduler::shutdown_drain()
     693                 : {
     694            2095 :     lock_type lock(mutex_);
     695                 : 
     696            4561 :     while (auto e = completed_ops_.pop())
     697                 :     {
     698            2466 :         if (ready_is_continuation(e))
     699                 :         {
     700               8 :             lock.unlock();
     701               8 :             if (auto h = ready_as_cont(e)->h)
     702               8 :                 h.destroy();
     703               8 :             lock.lock();
     704                 :         }
     705                 :         else
     706                 :         {
     707            2458 :             auto* op = ready_as_op(e);
     708            2458 :             if (op == &task_op_)
     709            2092 :                 continue;
     710             366 :             lock.unlock();
     711             366 :             op->destroy();
     712             366 :             lock.lock();
     713                 :         }
     714            2466 :     }
     715                 : 
     716            2095 :     signal_all(lock);
     717            2095 : }
     718                 : 
     719                 : inline void
     720            3838 : reactor_scheduler::signal_all(lock_type&) const
     721                 : {
     722            3838 :     state_ |= signaled_bit;
     723            3838 :     cond_.notify_all();
     724            3838 : }
     725                 : 
     726                 : inline bool
     727           13087 : reactor_scheduler::maybe_unlock_and_signal_one(
     728                 :     lock_type& lock) const
     729                 : {
     730           13087 :     state_ |= signaled_bit;
     731           13087 :     if (state_ > signaled_bit)
     732                 :     {
     733              40 :         lock.unlock();
     734              40 :         cond_.notify_one();
     735              40 :         return true;
     736                 :     }
     737           13047 :     return false;
     738                 : }
     739                 : 
     740                 : inline bool
     741          571824 : reactor_scheduler::unlock_and_signal_one(
     742                 :     lock_type& lock) const
     743                 : {
     744          571824 :     state_ |= signaled_bit;
     745          571824 :     bool have_waiters = state_ > signaled_bit;
     746          571824 :     lock.unlock();
     747          571824 :     if (have_waiters)
     748               5 :         cond_.notify_one();
     749          571824 :     return have_waiters;
     750                 : }
     751                 : 
     752                 : inline void
     753              55 : reactor_scheduler::clear_signal() const
     754                 : {
     755              55 :     state_ &= ~signaled_bit;
     756              55 : }
     757                 : 
     758                 : inline void
     759               7 : reactor_scheduler::wait_for_signal(
     760                 :     lock_type& lock) const
     761                 : {
     762              15 :     while ((state_ & signaled_bit) == 0)
     763                 :     {
     764               8 :         state_ += waiter_increment;
     765               8 :         cond_.wait(lock);
     766               8 :         state_ -= waiter_increment;
     767                 :     }
     768               7 : }
     769                 : 
     770                 : inline void
     771              48 : reactor_scheduler::wait_for_signal_for(
     772                 :     lock_type& lock, long timeout_us) const
     773                 : {
     774              48 :     if ((state_ & signaled_bit) == 0)
     775                 :     {
     776              48 :         state_ += waiter_increment;
     777              48 :         cond_.wait_for(lock, std::chrono::microseconds(timeout_us));
     778              48 :         state_ -= waiter_increment;
     779                 :     }
     780              48 : }
     781                 : 
     782                 : inline void
     783           13087 : reactor_scheduler::wake_one_thread_and_unlock(
     784                 :     lock_type& lock) const
     785                 : {
     786           13087 :     if (maybe_unlock_and_signal_one(lock))
     787              40 :         return;
     788                 : 
     789           13047 :     if (task_running_.load(std::memory_order_relaxed) && !task_interrupted_)
     790                 :     {
     791             221 :         task_interrupted_ = true;
     792             221 :         lock.unlock();
     793             221 :         interrupt_reactor();
     794                 :     }
     795                 :     else
     796                 :     {
     797           12826 :         lock.unlock();
     798                 :     }
     799                 : }
     800                 : 
     801          516830 : inline reactor_scheduler::work_cleanup::~work_cleanup()
     802                 : {
     803          516830 :     std::int64_t produced = ctx.private_outstanding_work;
     804          516830 :     if (produced > 1)
     805             340 :         sched->outstanding_work_.fetch_add(
     806                 :             produced - 1, std::memory_order_relaxed);
     807          516490 :     else if (produced < 1)
     808           46096 :         sched->work_finished();
     809          516830 :     ctx.private_outstanding_work = 0;
     810                 : 
     811          516830 :     if (!ctx.private_queue.empty())
     812                 :     {
     813          105809 :         lock->lock();
     814          105809 :         sched->completed_ops_.splice(ctx.private_queue);
     815                 :     }
     816          516830 : }
     817                 : 
     818          403673 : inline reactor_scheduler::task_cleanup::~task_cleanup()
     819                 : {
     820          403673 :     if (ctx.private_outstanding_work > 0)
     821                 :     {
     822           11902 :         sched->outstanding_work_.fetch_add(
     823           11902 :             ctx.private_outstanding_work, std::memory_order_relaxed);
     824           11902 :         ctx.private_outstanding_work = 0;
     825                 :     }
     826                 : 
     827          403673 :     if (!ctx.private_queue.empty())
     828                 :     {
     829           11902 :         if (!lock->owns_lock())
     830 MIS           0 :             lock->lock();
     831 HIT       11902 :         sched->completed_ops_.splice(ctx.private_queue);
     832                 :     }
     833          403673 : }
     834                 : 
     835                 : inline std::size_t
     836          518599 : reactor_scheduler::do_one(
     837                 :     lock_type& lock, long timeout_us, context_type& ctx)
     838                 : {
     839                 :     for (;;)
     840                 :     {
     841          922269 :         if (stopped_.load(std::memory_order_acquire))
     842            1675 :             return 0;
     843                 : 
     844          920594 :         std::uintptr_t e = completed_ops_.pop();
     845          920594 :         scheduler_op* op = ready_is_continuation(e) ? nullptr : ready_as_op(e);
     846                 : 
     847                 :         // Handle reactor sentinel — time to poll for I/O
     848          920594 :         if (op == &task_op_)
     849                 :         {
     850          403709 :             bool more_handlers = !completed_ops_.empty();
     851                 : 
     852          752359 :             if (!more_handlers &&
     853          697300 :                 (outstanding_work_.load(std::memory_order_acquire) == 0 ||
     854                 :                  timeout_us == 0))
     855                 :             {
     856              36 :                 completed_ops_.push(&task_op_);
     857              36 :                 return 0;
     858                 :             }
     859                 : 
     860          403673 :             long task_timeout_us = more_handlers ? 0 : timeout_us;
     861          403673 :             task_interrupted_ = task_timeout_us == 0;
     862          403673 :             task_running_.store(true, std::memory_order_release);
     863                 : 
     864                 :             // Wake a peer to take the pending handlers while this thread
     865                 :             // polls the reactor; skipped when one_thread_ (no peer exists).
     866          403673 :             if (more_handlers && !one_thread_)
     867           55052 :                 unlock_and_signal_one(lock);
     868                 : 
     869                 :             try
     870                 :             {
     871          403673 :                 run_task(lock, ctx, task_timeout_us);
     872                 :             }
     873               3 :             catch (...)
     874                 :             {
     875               3 :                 task_running_.store(false, std::memory_order_relaxed);
     876               3 :                 throw;
     877               3 :             }
     878                 : 
     879          403670 :             task_running_.store(false, std::memory_order_relaxed);
     880          403670 :             completed_ops_.push(&task_op_);
     881          403670 :             if (timeout_us > 0)
     882              55 :                 return 0;
     883          403615 :             continue;
     884          403615 :         }
     885                 : 
     886                 :         // Handle ready entry (op or continuation)
     887          516885 :         if (e != 0)
     888                 :         {
     889          516830 :             bool more = !completed_ops_.empty();
     890                 : 
     891          516830 :             if (more && !one_thread_)
     892                 :             {
     893                 :                 // Wake a peer for the remaining work; unassisted if none
     894                 :                 // was parked to take it.
     895          516772 :                 ctx.unassisted = !unlock_and_signal_one(lock);
     896                 :             }
     897                 :             else
     898                 :             {
     899                 :                 // No peer to wake (one_thread_, or nothing more queued).
     900              58 :                 ctx.unassisted = more;
     901              58 :                 lock.unlock();
     902                 :             }
     903                 : 
     904          516830 :             [[maybe_unused]] work_cleanup on_exit{this, &lock, ctx};
     905                 : 
     906          516830 :             if (ready_is_continuation(e))
     907           26082 :                 ready_as_cont(e)->h.resume();
     908                 :             else
     909          490748 :                 (*op)();
     910          516830 :             return 1;
     911          516830 :         }
     912                 : 
     913             110 :         if (outstanding_work_.load(std::memory_order_acquire) == 0 ||
     914                 :             timeout_us == 0)
     915 MIS           0 :             return 0;
     916                 : 
     917 HIT          55 :         clear_signal();
     918              55 :         if (timeout_us < 0)
     919               7 :             wait_for_signal(lock);
     920                 :         else
     921              48 :             wait_for_signal_for(lock, timeout_us);
     922          403670 :     }
     923                 : }
     924                 : 
     925                 : } // namespace boost::corosio::detail
     926                 : 
     927                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_SCHEDULER_HPP
        

Generated by: LCOV version 2.3