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
|