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

            Line data    Source code
       1              : #ifndef THINGER_HTTP_CLIENT_CONNECTION_POOL_HPP
       2              : #define THINGER_HTTP_CLIENT_CONNECTION_POOL_HPP
       3              : 
       4              : #include <chrono>
       5              : #include <cstdint>
       6              : #include <list>
       7              : #include <map>
       8              : #include <memory>
       9              : #include <mutex>
      10              : #include <string>
      11              : #include <unordered_map>
      12              : #include <vector>
      13              : #include "client_connection.hpp"
      14              : 
      15              : namespace thinger::http {
      16              : 
      17              : /**
      18              :  * Keep-alive pool of the connections of a client (RFC 9112 section 9.3). A connection is lent
      19              :  * to one request at a time (a lease), and given back when the request ends: kept idle for the
      20              :  * next request with the same key if it can carry another one, closed otherwise. The key tells
      21              :  * which connections are interchangeable: same origin (or Unix socket), reached the same way
      22              :  * (directly or through which proxy, trusting which CAs).
      23              :  *
      24              :  * Idle connections are bounded per key and in total (the oldest is closed to make room),
      25              :  * and closed after an idle timeout, shortened to the one the server announces
      26              :  * (Keep-Alive: timeout=N) minus a margin. One taken from the pool is checked first, without
      27              :  * waiting: if the server closed it, reset it or sent something unasked, it is closed and the
      28              :  * next one tried. Thread-safe: a lease may be taken and given back on any thread.
      29              :  */
      30              : class connection_pool {
      31              : public:
      32              :     struct config {
      33              :         // Idle connections kept per key (0: none, the pool is disabled)
      34              :         size_t max_idle_per_host = 8;
      35              :         // Idle connections kept in total (0: none)
      36              :         size_t max_idle = 100;
      37              :         // Time an idle connection is kept
      38              :         std::chrono::milliseconds idle_timeout{4000};
      39              :     };
      40              : 
      41              :     // Subtracted from the idle timeout a server announces, so the connection is not reused
      42              :     // just as the server closes it
      43              :     static constexpr std::chrono::milliseconds keep_alive_margin{1000};
      44              : 
      45              :     struct host_stats {
      46              :         size_t idle = 0;
      47              :         size_t active = 0;
      48              :     };
      49              : 
      50              :     struct stats {
      51              :         size_t idle = 0;            // connections kept idle
      52              :         size_t active = 0;          // connections lent to requests
      53              :         size_t created = 0;         // connections created (since the pool was)
      54              :         size_t reused = 0;          // requests sent on an idle connection
      55              :         std::map<std::string, host_stats> hosts;    // by origin ("https://host:port", "unix:/path")
      56              :     };
      57              : 
      58              : private:
      59              :     struct state;
      60              : 
      61              : public:
      62              :     // A connection lent to a request, given back to the pool when released or destroyed
      63              :     class lease {
      64              :     public:
      65        15279 :         lease() = default;
      66         3420 :         lease(lease&& other) noexcept = default;
      67              :         lease& operator=(lease&& other) noexcept;
      68              :         lease(const lease&) = delete;
      69              :         lease& operator=(const lease&) = delete;
      70        74980 :         ~lease() { release(); }
      71              : 
      72        64448 :         explicit operator bool() const { return connection_ != nullptr; }
      73        24954 :         client_connection* operator->() const { return connection_.get(); }
      74         1551 :         client_connection& operator*() const { return *connection_; }
      75        48024 :         const std::shared_ptr<client_connection>& connection() const { return connection_; }
      76              : 
      77              :         // Give the connection back: kept idle if it can carry another request, closed
      78              :         // otherwise. The lease is empty afterwards
      79              :         void release();
      80              : 
      81              :     private:
      82              :         friend class connection_pool;
      83        56281 :         lease(std::weak_ptr<state> pool, std::string key, std::shared_ptr<client_connection> connection)
      84        56281 :             : pool_(std::move(pool)), key_(std::move(key)), connection_(std::move(connection)) {}
      85              : 
      86              :         std::weak_ptr<state> pool_;
      87              :         std::string key_;
      88              :         std::shared_ptr<client_connection> connection_;
      89              :     };
      90              : 
      91              :     connection_pool();
      92              :     ~connection_pool();
      93              :     connection_pool(const connection_pool&) = delete;
      94              :     connection_pool& operator=(const connection_pool&) = delete;
      95              : 
      96              :     // Limits, applied to the connections already idle too
      97              :     void configure(const config& config);
      98              :     config get_config() const;
      99              : 
     100              :     // Close each idle connection with a timer on its io_context (the default). Without them,
     101              :     // expired connections are closed whenever the pool is used (a connection taken or given
     102              :     // back): for an io_context that only runs while requests do, as a pending timer would
     103              :     // keep it running
     104              :     void use_idle_timers(bool enabled);
     105              : 
     106              :     // An idle connection for `key`, claimed for a request; empty if there is none usable
     107              :     lease acquire(const std::string& key);
     108              : 
     109              :     // Lend a new connection for `key`; `origin` names it in the stats
     110              :     lease add(const std::string& key, const std::string& origin, std::shared_ptr<client_connection> connection);
     111              : 
     112              :     // Idle connections
     113              :     size_t size() const;
     114              : 
     115              :     stats get_stats() const;
     116              : 
     117              :     // Close the idle connections (those lent are not affected)
     118              :     void clear();
     119              : 
     120              : private:
     121              :     std::shared_ptr<state> state_;
     122              : };
     123              : 
     124              : } // namespace thinger::http
     125              : 
     126              : #endif // THINGER_HTTP_CLIENT_CONNECTION_POOL_HPP
        

Generated by: LCOV version 2.0-1