LCOV - code coverage report
Current view: top level - corosio/native/detail/reactor - reactor_scheduler.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 98.5 % 392 386 6
Test Date: 2026-11-13 06:11:18 Functions: 98.1 % 53 52 1

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

Generated by: LCOV version 2.3