Skip to content

Commit ae86ada

Browse files
stickies-vfanquake
authored andcommitted
http: refactor: use encapsulated HTTPRequestTracker
Introduces and uses a HTTPRequestTracker class to keep track of how many HTTP requests are currently active, so we don't stop the server before they're all handled. This has two purposes: 1. In a next commit, allows us to untrack all requests associated with a connection without running into lifetime issues of the connection living longer than the request (see bitcoin#27909 (comment)) 2. Improve encapsulation by making the mutex and cv internal members, and exposing just the WaitUntilEmpty() method that can be safely used. Github-Pull: bitcoin#28551 Rebased-From: 41f9027
1 parent f31899d commit ae86ada

File tree

1 file changed

+70
-16
lines changed

1 file changed

+70
-16
lines changed

src/httpserver.cpp

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <rpc/protocol.h> // For HTTP status codes
1616
#include <shutdown.h>
1717
#include <sync.h>
18+
#include <util/check.h>
1819
#include <util/strencodings.h>
1920
#include <util/syscall_sandbox.h>
2021
#include <util/system.h>
@@ -26,9 +27,10 @@
2627
#include <cstdlib>
2728
#include <deque>
2829
#include <memory>
30+
#include <numeric>
2931
#include <optional>
3032
#include <string>
31-
#include <unordered_set>
33+
#include <unordered_map>
3234

3335
#include <sys/types.h>
3436
#include <sys/stat.h>
@@ -149,10 +151,68 @@ static GlobalMutex g_httppathhandlers_mutex;
149151
static std::vector<HTTPPathHandler> pathHandlers GUARDED_BY(g_httppathhandlers_mutex);
150152
//! Bound listening sockets
151153
static std::vector<evhttp_bound_socket *> boundSockets;
154+
155+
/**
156+
* @brief Helps keep track of open `evhttp_connection`s with active `evhttp_requests`
157+
*
158+
*/
159+
class HTTPRequestTracker
160+
{
161+
private:
162+
mutable Mutex m_mutex;
163+
mutable std::condition_variable m_cv;
164+
//! For each connection, keep a counter of how many requests are open
165+
std::unordered_map<const evhttp_connection*, size_t> m_tracker GUARDED_BY(m_mutex);
166+
167+
void RemoveConnectionInternal(const decltype(m_tracker)::iterator it) EXCLUSIVE_LOCKS_REQUIRED(m_mutex)
168+
{
169+
m_tracker.erase(it);
170+
if (m_tracker.empty()) m_cv.notify_all();
171+
}
172+
public:
173+
//! Increase request counter for the associated connection by 1
174+
void AddRequest(evhttp_request* req) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
175+
{
176+
const evhttp_connection* conn{Assert(evhttp_request_get_connection(Assert(req)))};
177+
WITH_LOCK(m_mutex, ++m_tracker[conn]);
178+
}
179+
//! Decrease request counter for the associated connection by 1, remove connection if counter is 0
180+
void RemoveRequest(evhttp_request* req) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
181+
{
182+
const evhttp_connection* conn{Assert(evhttp_request_get_connection(Assert(req)))};
183+
LOCK(m_mutex);
184+
auto it{m_tracker.find(conn)};
185+
if (it != m_tracker.end() && it->second > 0) {
186+
if (--(it->second) == 0) RemoveConnectionInternal(it);
187+
}
188+
}
189+
//! Remove a connection entirely
190+
void RemoveConnection(const evhttp_connection* conn) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
191+
{
192+
LOCK(m_mutex);
193+
auto it{m_tracker.find(Assert(conn))};
194+
if (it != m_tracker.end()) RemoveConnectionInternal(it);
195+
}
196+
197+
size_t CountActiveRequests() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
198+
{
199+
LOCK(m_mutex);
200+
return std::accumulate(m_tracker.begin(), m_tracker.end(), size_t(0),
201+
[](size_t acc_count, const auto& pair) { return acc_count + pair.second; });
202+
}
203+
size_t CountActiveConnections() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
204+
{
205+
return WITH_LOCK(m_mutex, return m_tracker.size());
206+
}
207+
//! Wait until there are no more connections with active requests in the tracker
208+
void WaitUntilEmpty() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
209+
{
210+
WAIT_LOCK(m_mutex, lock);
211+
m_cv.wait(lock, [this]() EXCLUSIVE_LOCKS_REQUIRED(m_mutex) { return m_tracker.empty(); });
212+
}
213+
};
152214
//! 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);
215+
static HTTPRequestTracker g_requests;
156216

157217
/** Check if a network address is allowed to access the HTTP server */
158218
static bool ClientAllowed(const CNetAddr& netaddr)
@@ -214,14 +274,11 @@ std::string RequestMethodString(HTTPRequest::RequestMethod m)
214274
/** HTTP request callback */
215275
static void http_request_cb(struct evhttp_request* req, void* arg)
216276
{
217-
// Track requests and notify when a request is completed.
277+
// Track active requests
218278
{
219-
WITH_LOCK(g_requests_mutex, g_requests.insert(req));
220-
g_requests_cv.notify_all();
279+
g_requests.AddRequest(req);
221280
evhttp_request_set_on_complete_cb(req, [](struct evhttp_request* req, void*) {
222-
auto n{WITH_LOCK(g_requests_mutex, return g_requests.erase(req))};
223-
assert(n == 1);
224-
g_requests_cv.notify_all();
281+
g_requests.RemoveRequest(req);
225282
}, nullptr);
226283
}
227284

@@ -477,13 +534,10 @@ void StopHTTPServer()
477534
}
478535
boundSockets.clear();
479536
{
480-
WAIT_LOCK(g_requests_mutex, lock);
481-
if (!g_requests.empty()) {
482-
LogPrint(BCLog::HTTP, "Waiting for %d requests to stop HTTP server\n", g_requests.size());
537+
if (g_requests.CountActiveConnections() != 0) {
538+
LogPrint(BCLog::HTTP, "Waiting for %d requests to stop HTTP server\n", g_requests.CountActiveRequests());
483539
}
484-
g_requests_cv.wait(lock, []() EXCLUSIVE_LOCKS_REQUIRED(g_requests_mutex) {
485-
return g_requests.empty();
486-
});
540+
g_requests.WaitUntilEmpty();
487541
}
488542
if (eventHTTP) {
489543
// Schedule a callback to call evhttp_free in the event base thread, so

0 commit comments

Comments
 (0)