LCOV - code coverage report
Current view: top level - asio - workers.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 1 1
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 1 1

            Line data    Source code
       1              : #ifndef THINGER_ASIO_WORKERS
       2              : #define THINGER_ASIO_WORKERS
       3              : 
       4              : #include <atomic>
       5              : #include <condition_variable>
       6              : #include <csignal>
       7              : #include <functional>
       8              : #include <memory>
       9              : #include <mutex>
      10              : #include <set>
      11              : #include <string>
      12              : #include <thread>
      13              : #include <vector>
      14              : #include <boost/asio/any_io_executor.hpp>
      15              : #include <boost/asio/io_context.hpp>
      16              : #include <boost/system/error_code.hpp>
      17              : #include "worker_thread.hpp"
      18              : 
      19              : namespace thinger::asio {
      20              : 
      21              :         // A pool of threads, each running an io_context of its own, plus the isolated threads asked
      22              :         // for. An ordinary object: the application owns it, starts it and stops it (nothing starts
      23              :         // or stops it by itself), and hands it by reference to the servers and clients that run on
      24              :         // it (pool_server, async_client). They own no thread, and the application keeps the workers
      25              :         // alive longer than them. Several instances may live in one process; they share nothing.
      26              :         //
      27              :         // Never destroy a workers from one of its own threads.
      28              :         class workers {
      29              :         public:
      30              :         workers();
      31              :                 ~workers();
      32              : 
      33              :         workers(const workers&) = delete;
      34              :         workers& operator=(const workers&) = delete;
      35              : 
      36              :                 /// start `threads` threads in the pool: `already_started` if it runs already (no thread
      37              :                 /// is added), `invalid_argument` for 0 threads. Their io_contexts are kept until the
      38              :                 /// workers are destroyed, also when they start again after stop()
      39              :         boost::system::error_code start(size_t threads = std::thread::hardware_concurrency());
      40              : 
      41              :         /// stop every thread, the pool's and the isolated ones, destroying their pending
      42              :         /// operations, and release the wait() calls in progress. From any thread, more than once
      43              :         /// (a warning once stopped). Stop the servers and clients on it first. Their io_contexts
      44              :         /// live on, stopped, until the workers are destroyed: a client destroyed afterwards
      45              :         /// still closes its connections on them. Called from one of its threads, that thread
      46              :         /// ends once its handler returns, and the destructor joins it.
      47              :         void stop();
      48              : 
      49              :         /// whether the workers run
      50         2579 :         bool running() const { return running_; }
      51              : 
      52              :         /// the executor of the next thread of the pool (round-robin), from any thread, without
      53              :         /// a lock; a null executor when the workers do not run
      54              :         boost::asio::any_io_executor next_executor();
      55              : 
      56              :         /// next_executor(), as a callable (for a listener)
      57              :         std::function<boost::asio::any_io_executor()> provider();
      58              : 
      59              :         /// the executor of a new thread of its own, out of the pool, named `name`: it runs until
      60              :         /// the workers stop. A null executor when the workers do not run
      61              :         boost::asio::any_io_executor isolated_executor(std::string name);
      62              : 
      63              :         /// whether the caller thread is one of the threads these workers started (of the pool
      64              :         /// or isolated) and they still run: a thread-local, without a lock
      65              :         bool in_worker_thread() const;
      66              : 
      67              :         /// block until one of `signals` arrives, and return it; 0 when stop() released it
      68              :         /// instead, or at once if the workers do not run. The workers keep running: what to do
      69              :         /// with the signal (a server's shutdown(), then stop()) is the application's. With an
      70              :         /// empty set, it waits for stop(). While waiting, the signals do not take their default
      71              :         /// action; afterwards they do again.
      72              :         int wait(std::set<int> signals = {SIGINT, SIGTERM, SIGQUIT});
      73              : 
      74              :         /// the io_context of the next thread of the pool (round-robin), without a lock. Throws
      75              :         /// std::logic_error when the workers do not run
      76              :         boost::asio::io_context& get_next_io_context();
      77              : 
      78              :         /// the io_context of the caller thread when it is one of these workers' threads (from a
      79              :         /// thread-local, without a lock), or else the one of the first thread of the pool, which
      80              :         /// throws std::logic_error when the workers do not run. It goes at 0.10
      81              :                 boost::asio::io_context& get_thread_io_context();
      82              : 
      83              :         private:
      84              :         // The pool's io_contexts, published by start() and read without a lock: immutable, and
      85              :         // kept (as the io_contexts) until the workers are destroyed
      86              :         struct pool_table {
      87              :             std::vector<boost::asio::io_context*> contexts;
      88              :         };
      89              : 
      90              :         /// the io_context of the next thread of the pool, or null when not running
      91              :         boost::asio::io_context* next_context();
      92              : 
      93              :         /// start a thread of these workers, marked as theirs
      94              :         std::unique_ptr<worker_thread> start_thread(std::string name);
      95              : 
      96              :         /// guards start(), stop(), the threads and the waits
      97              :         mutable std::mutex mutex_;
      98              : 
      99              :         /// the stop() calls still joining threads, which the destructor waits for (one called
     100              :         /// from a thread of the workers returns after the wait()s it releases)
     101              :         size_t stopping_ = 0;
     102              :         std::condition_variable stopped_;
     103              : 
     104              :         /// flag for controlling if it is running
     105              :                 std::atomic<bool> running_{false};
     106              : 
     107              :         /// the published pool table (null when not running), and every table published
     108              :         std::atomic<const pool_table*> table_{nullptr};
     109              :         std::vector<std::unique_ptr<const pool_table>> tables_;
     110              : 
     111              :         /// index of the next io_context of the round-robin (relaxed: it only spreads the load)
     112              :         std::atomic<unsigned> next_io_context_{0};
     113              : 
     114              :                 /// threads running: of the pool, and the isolated ones
     115              :                 std::vector<std::unique_ptr<worker_thread>> worker_threads_;
     116              :         std::vector<std::unique_ptr<worker_thread>> isolated_threads_;
     117              : 
     118              :         /// threads stopped, with their io_contexts (see stop())
     119              :         std::vector<std::unique_ptr<worker_thread>> stopped_threads_;
     120              : 
     121              :         /// the io_contexts of the wait() calls in progress, which stop() releases
     122              :         std::vector<boost::asio::io_context*> waits_;
     123              :         };
     124              : 
     125              : }
     126              : 
     127              : #endif
        

Generated by: LCOV version 2.0-1