|
17 | 17 | #include <rpc/protocol.h> // For HTTP status codes
|
18 | 18 | #include <shutdown.h>
|
19 | 19 | #include <sync.h>
|
| 20 | +#include <util/check.h> |
20 | 21 | #include <util/strencodings.h>
|
21 | 22 | #include <util/threadnames.h>
|
22 | 23 | #include <util/translation.h>
|
|
28 | 29 | #include <memory>
|
29 | 30 | #include <optional>
|
30 | 31 | #include <string>
|
31 |
| -#include <unordered_set> |
| 32 | +#include <unordered_map> |
32 | 33 |
|
33 | 34 | #include <sys/types.h>
|
34 | 35 | #include <sys/stat.h>
|
@@ -149,10 +150,61 @@ static GlobalMutex g_httppathhandlers_mutex;
|
149 | 150 | static std::vector<HTTPPathHandler> pathHandlers GUARDED_BY(g_httppathhandlers_mutex);
|
150 | 151 | //! Bound listening sockets
|
151 | 152 | static std::vector<evhttp_bound_socket *> boundSockets;
|
| 153 | + |
| 154 | +/** |
| 155 | + * @brief Helps keep track of open `evhttp_connection`s with active `evhttp_requests` |
| 156 | + * |
| 157 | + */ |
| 158 | +class HTTPRequestTracker |
| 159 | +{ |
| 160 | +private: |
| 161 | + mutable Mutex m_mutex; |
| 162 | + mutable std::condition_variable m_cv; |
| 163 | + //! For each connection, keep a counter of how many requests are open |
| 164 | + std::unordered_map<const evhttp_connection*, size_t> m_tracker GUARDED_BY(m_mutex); |
| 165 | + |
| 166 | + void RemoveConnectionInternal(const decltype(m_tracker)::iterator it) EXCLUSIVE_LOCKS_REQUIRED(m_mutex) |
| 167 | + { |
| 168 | + m_tracker.erase(it); |
| 169 | + if (m_tracker.empty()) m_cv.notify_all(); |
| 170 | + } |
| 171 | +public: |
| 172 | + //! Increase request counter for the associated connection by 1 |
| 173 | + void AddRequest(evhttp_request* req) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
| 174 | + { |
| 175 | + const evhttp_connection* conn{Assert(evhttp_request_get_connection(Assert(req)))}; |
| 176 | + WITH_LOCK(m_mutex, ++m_tracker[conn]); |
| 177 | + } |
| 178 | + //! Decrease request counter for the associated connection by 1, remove connection if counter is 0 |
| 179 | + void RemoveRequest(evhttp_request* req) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
| 180 | + { |
| 181 | + const evhttp_connection* conn{Assert(evhttp_request_get_connection(Assert(req)))}; |
| 182 | + LOCK(m_mutex); |
| 183 | + auto it{m_tracker.find(conn)}; |
| 184 | + if (it != m_tracker.end() && it->second > 0) { |
| 185 | + if (--(it->second) == 0) RemoveConnectionInternal(it); |
| 186 | + } |
| 187 | + } |
| 188 | + //! Remove a connection entirely |
| 189 | + void RemoveConnection(const evhttp_connection* conn) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
| 190 | + { |
| 191 | + LOCK(m_mutex); |
| 192 | + auto it{m_tracker.find(Assert(conn))}; |
| 193 | + if (it != m_tracker.end()) RemoveConnectionInternal(it); |
| 194 | + } |
| 195 | + size_t CountActiveConnections() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
| 196 | + { |
| 197 | + return WITH_LOCK(m_mutex, return m_tracker.size()); |
| 198 | + } |
| 199 | + //! Wait until there are no more connections with active requests in the tracker |
| 200 | + void WaitUntilEmpty() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
| 201 | + { |
| 202 | + WAIT_LOCK(m_mutex, lock); |
| 203 | + m_cv.wait(lock, [this]() EXCLUSIVE_LOCKS_REQUIRED(m_mutex) { return m_tracker.empty(); }); |
| 204 | + } |
| 205 | +}; |
152 | 206 | //! Track active requests
|
153 |
| -static GlobalMutex g_requests_mutex; |
154 |
| -static std::condition_variable g_requests_cv; |
155 |
| -static std::unordered_set<evhttp_request*> g_requests GUARDED_BY(g_requests_mutex); |
| 207 | +static HTTPRequestTracker g_requests; |
156 | 208 |
|
157 | 209 | /** Check if a network address is allowed to access the HTTP server */
|
158 | 210 | static bool ClientAllowed(const CNetAddr& netaddr)
|
@@ -210,22 +262,22 @@ std::string RequestMethodString(HTTPRequest::RequestMethod m)
|
210 | 262 | /** HTTP request callback */
|
211 | 263 | static void http_request_cb(struct evhttp_request* req, void* arg)
|
212 | 264 | {
|
213 |
| - // Track requests and notify when a request is completed. |
| 265 | + evhttp_connection* conn{evhttp_request_get_connection(req)}; |
| 266 | + // Track active requests |
214 | 267 | {
|
215 |
| - WITH_LOCK(g_requests_mutex, g_requests.insert(req)); |
216 |
| - g_requests_cv.notify_all(); |
| 268 | + g_requests.AddRequest(req); |
217 | 269 | evhttp_request_set_on_complete_cb(req, [](struct evhttp_request* req, void*) {
|
218 |
| - auto n{WITH_LOCK(g_requests_mutex, return g_requests.erase(req))}; |
219 |
| - assert(n == 1); |
220 |
| - g_requests_cv.notify_all(); |
| 270 | + g_requests.RemoveRequest(req); |
| 271 | + }, nullptr); |
| 272 | + evhttp_connection_set_closecb(conn, [](evhttp_connection* conn, void* arg) { |
| 273 | + g_requests.RemoveConnection(conn); |
221 | 274 | }, nullptr);
|
222 | 275 | }
|
223 | 276 |
|
224 | 277 | // Disable reading to work around a libevent bug, fixed in 2.1.9
|
225 | 278 | // See https://github.com/libevent/libevent/commit/5ff8eb26371c4dc56f384b2de35bea2d87814779
|
226 | 279 | // and https://github.com/bitcoin/bitcoin/pull/11593.
|
227 | 280 | if (event_get_version_number() >= 0x02010600 && event_get_version_number() < 0x02010900) {
|
228 |
| - evhttp_connection* conn = evhttp_request_get_connection(req); |
229 | 281 | if (conn) {
|
230 | 282 | bufferevent* bev = evhttp_connection_get_bufferevent(conn);
|
231 | 283 | if (bev) {
|
@@ -473,13 +525,10 @@ void StopHTTPServer()
|
473 | 525 | }
|
474 | 526 | boundSockets.clear();
|
475 | 527 | {
|
476 |
| - WAIT_LOCK(g_requests_mutex, lock); |
477 |
| - if (!g_requests.empty()) { |
478 |
| - LogPrint(BCLog::HTTP, "Waiting for %d requests to stop HTTP server\n", g_requests.size()); |
| 528 | + if (const auto n_connections{g_requests.CountActiveConnections()}; n_connections != 0) { |
| 529 | + LogPrint(BCLog::HTTP, "Waiting for %d connections to stop HTTP server\n", n_connections); |
479 | 530 | }
|
480 |
| - g_requests_cv.wait(lock, []() EXCLUSIVE_LOCKS_REQUIRED(g_requests_mutex) { |
481 |
| - return g_requests.empty(); |
482 |
| - }); |
| 531 | + g_requests.WaitUntilEmpty(); |
483 | 532 | }
|
484 | 533 | if (eventHTTP) {
|
485 | 534 | // Schedule a callback to call evhttp_free in the event base thread, so
|
|
0 commit comments