Skip to content

Commit e7f6564

Browse files
committed
Incremental memory management improvements.
1 parent 0dfe280 commit e7f6564

5 files changed

Lines changed: 121 additions & 119 deletions

File tree

libs/internal/src/network/curl_requester.cpp

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ void CurlRequester::PerformRequestWithMulti(std::shared_ptr<CurlMultiManager> mu
9393
return;
9494
}
9595

96-
CURL* curl = curl_easy_init();
96+
std::shared_ptr<CURL>curl (curl_easy_init(), curl_easy_cleanup);
9797
if (!curl) {
9898
cb(HttpResult(kErrorCurlInit));
9999
return;
@@ -102,23 +102,14 @@ void CurlRequester::PerformRequestWithMulti(std::shared_ptr<CurlMultiManager> mu
102102
// Create context to hold data for this request
103103
// This will be cleaned up in the completion callback
104104
struct RequestContext {
105-
CURL* curl;
106105
std::string url;
107106
std::string body; // Keep body alive
108107
std::string response_body;
109108
HttpResult::HeadersType response_headers;
110109
std::function<void(const HttpResult&)> callback;
111-
112-
~RequestContext() {
113-
// Headers are managed by CurlMultiManager
114-
if (curl) {
115-
curl_easy_cleanup(curl);
116-
}
117-
}
118110
};
119111

120112
auto ctx = std::make_shared<RequestContext>();
121-
ctx->curl = curl;
122113
ctx->callback = std::move(cb);
123114

124115
// Headers will be managed by CurlMultiManager
@@ -144,28 +135,28 @@ void CurlRequester::PerformRequestWithMulti(std::shared_ptr<CurlMultiManager> mu
144135
ctx->url = request.Url();
145136

146137
// Set URL
147-
CURL_SETOPT_CHECK(curl, CURLOPT_URL, ctx->url.c_str());
138+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_URL, ctx->url.c_str());
148139

149140
// Set HTTP method
150141
if (request.Method() == HttpMethod::kPost) {
151142
// Basically CURLOPT_POST is a flag that indicates this is a post.
152143
// Passing 1 enables this flag.
153144
// This will also set a content type, but the headers for the request
154145
// should override that with the correct value.
155-
CURL_SETOPT_CHECK(curl, CURLOPT_POST, 1L);
146+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_POST, 1L);
156147
} else if (request.Method() == HttpMethod::kPut) {
157-
CURL_SETOPT_CHECK(curl, CURLOPT_CUSTOMREQUEST, kHttpMethodPut);
148+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_CUSTOMREQUEST, kHttpMethodPut);
158149
} else if (request.Method() == HttpMethod::kReport) {
159-
CURL_SETOPT_CHECK(curl, CURLOPT_CUSTOMREQUEST, kHttpMethodReport);
150+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_CUSTOMREQUEST, kHttpMethodReport);
160151
} else if (request.Method() == HttpMethod::kGet) {
161-
CURL_SETOPT_CHECK(curl, CURLOPT_HTTPGET, 1L);
152+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_HTTPGET, 1L);
162153
}
163154

164155
// Set request body if present
165156
if (request.Body().has_value()) {
166157
ctx->body = request.Body().value();
167-
CURL_SETOPT_CHECK(curl, CURLOPT_POSTFIELDS, ctx->body.c_str());
168-
CURL_SETOPT_CHECK(curl, CURLOPT_POSTFIELDSIZE, ctx->body.size());
158+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_POSTFIELDS, ctx->body.c_str());
159+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_POSTFIELDSIZE, ctx->body.size());
169160
}
170161

171162
// Set headers
@@ -183,31 +174,31 @@ void CurlRequester::PerformRequestWithMulti(std::shared_ptr<CurlMultiManager> mu
183174
headers = appendResult;
184175
}
185176
if (headers) {
186-
CURL_SETOPT_CHECK(curl, CURLOPT_HTTPHEADER, headers);
177+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_HTTPHEADER, headers);
187178
}
188179

189180
// Set timeouts with millisecond precision
190181
const long connect_timeout_ms = request.Properties().ConnectTimeout().count();
191182
const long response_timeout_ms = request.Properties().ResponseTimeout().count();
192183

193-
CURL_SETOPT_CHECK(curl, CURLOPT_CONNECTTIMEOUT_MS, connect_timeout_ms > 0 ? connect_timeout_ms : 30000L);
194-
CURL_SETOPT_CHECK(curl, CURLOPT_TIMEOUT_MS, response_timeout_ms > 0 ? response_timeout_ms : 60000L);
184+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_CONNECTTIMEOUT_MS, connect_timeout_ms > 0 ? connect_timeout_ms : 30000L);
185+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_TIMEOUT_MS, response_timeout_ms > 0 ? response_timeout_ms : 60000L);
195186

196187
// Set TLS options
197188
using VerifyMode = config::shared::built::TlsOptions::VerifyMode;
198189
if (tls_options.PeerVerifyMode() == VerifyMode::kVerifyNone) {
199-
CURL_SETOPT_CHECK(curl, CURLOPT_SSL_VERIFYPEER, 0L);
200-
CURL_SETOPT_CHECK(curl, CURLOPT_SSL_VERIFYHOST, 0L);
190+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_SSL_VERIFYPEER, 0L);
191+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_SSL_VERIFYHOST, 0L);
201192
} else {
202-
CURL_SETOPT_CHECK(curl, CURLOPT_SSL_VERIFYPEER, 1L);
193+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_SSL_VERIFYPEER, 1L);
203194
// 1 or 2 seem to basically be the same, but the documentation says to
204195
// use 2, and that it would default to 2.
205196
// https://curl.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html
206-
CURL_SETOPT_CHECK(curl, CURLOPT_SSL_VERIFYHOST, 2L);
197+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_SSL_VERIFYHOST, 2L);
207198

208199
// Set custom CA file if provided
209200
if (tls_options.CustomCAFile().has_value()) {
210-
CURL_SETOPT_CHECK(curl, CURLOPT_CAINFO, tls_options.CustomCAFile()->c_str());
201+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_CAINFO, tls_options.CustomCAFile()->c_str());
211202
}
212203
}
213204

@@ -216,19 +207,19 @@ void CurlRequester::PerformRequestWithMulti(std::shared_ptr<CurlMultiManager> mu
216207
// Empty string explicitly disables proxy (overrides environment variables).
217208
auto const& proxy_url = request.Properties().Proxy().Url();
218209
if (proxy_url.has_value()) {
219-
CURL_SETOPT_CHECK(curl, CURLOPT_PROXY, proxy_url->c_str());
210+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_PROXY, proxy_url->c_str());
220211
}
221212
// If proxy URL is std::nullopt, CURL will use environment variables (default behavior)
222213

223214
// Set callbacks
224-
CURL_SETOPT_CHECK(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
225-
CURL_SETOPT_CHECK(curl, CURLOPT_WRITEDATA, &ctx->response_body);
226-
CURL_SETOPT_CHECK(curl, CURLOPT_HEADERFUNCTION, HeaderCallback);
227-
CURL_SETOPT_CHECK(curl, CURLOPT_HEADERDATA, &ctx->response_headers);
215+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback);
216+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_WRITEDATA, &ctx->response_body);
217+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_HEADERFUNCTION, HeaderCallback);
218+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_HEADERDATA, &ctx->response_headers);
228219

229220
// Follow redirects
230-
CURL_SETOPT_CHECK(curl, CURLOPT_FOLLOWLOCATION, 1L);
231-
CURL_SETOPT_CHECK(curl, CURLOPT_MAXREDIRS, 20L);
221+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);
222+
CURL_SETOPT_CHECK(curl.get(), CURLOPT_MAXREDIRS, 20L);
232223

233224
#undef CURL_SETOPT_CHECK
234225

libs/networking/include/launchdarkly/network/curl_multi_manager.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class CurlMultiManager : public std::enable_shared_from_this<CurlMultiManager> {
6060
* @param headers The curl_slist headers (will be freed automatically)
6161
* @param callback Called when the transfer completes
6262
*/
63-
void add_handle(CURL* easy,
63+
void add_handle(std::shared_ptr<CURL> easy,
6464
curl_slist* headers,
6565
CompletionCallback callback);
6666

@@ -112,6 +112,7 @@ class CurlMultiManager : public std::enable_shared_from_this<CurlMultiManager> {
112112
std::mutex mutex_;
113113
std::map<CURL*, CompletionCallback> callbacks_;
114114
std::map<CURL*, curl_slist*> headers_;
115+
std::map<CURL*, std::shared_ptr<CURL>> handles_;
115116
std::map<curl_socket_t, SocketInfo> sockets_; // Managed socket info
116117
int still_running_{0};
117118
};

libs/networking/src/curl_multi_manager.cpp

Lines changed: 89 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -43,49 +43,49 @@ CurlMultiManager::~CurlMultiManager() {
4343
curl_slist_free_all(header_it->second);
4444
}
4545

46-
curl_easy_cleanup(easy);
46+
// curl_easy_cleanup(easy);
4747
}
4848
}
4949
}
5050

51-
void CurlMultiManager::add_handle(CURL* easy,
51+
void CurlMultiManager::add_handle(std::shared_ptr<CURL> easy,
5252
curl_slist* headers,
5353
CompletionCallback callback) {
54-
{
55-
std::lock_guard lock(mutex_);
56-
callbacks_[easy] = std::move(callback);
57-
headers_[easy] = headers;
58-
}
59-
60-
if (const CURLMcode rc = curl_multi_add_handle(multi_handle_.get(), easy);
54+
if (const CURLMcode rc = curl_multi_add_handle(
55+
multi_handle_.get(), easy.get());
6156
rc != CURLM_OK) {
62-
std::lock_guard lock(mutex_);
63-
callbacks_.erase(easy);
64-
6557
// Free headers on error
66-
if (const auto header_it = headers_.find(easy);
67-
header_it != headers_.end() && header_it->second) {
68-
curl_slist_free_all(header_it->second);
58+
if (headers) {
59+
curl_slist_free_all(headers);
6960
}
70-
headers_.erase(easy);
7161

7262
std::cerr << "Failed to add handle to multi: "
7363
<< curl_multi_strerror(rc) << std::endl;
64+
return;
65+
}
66+
67+
{
68+
std::lock_guard lock(mutex_);
69+
callbacks_[easy.get()] = std::move(callback);
70+
headers_[easy.get()] = headers;
71+
handles_[easy.get()] = easy;
7472
}
7573
}
7674

7775
void CurlMultiManager::remove_handle(CURL* easy) {
7876
curl_multi_remove_handle(multi_handle_.get(), easy);
7977

8078
std::lock_guard lock(mutex_);
81-
callbacks_.erase(easy);
8279

8380
// Free headers if they exist
8481
if (const auto header_it = headers_.find(easy);
8582
header_it != headers_.end() && header_it->second) {
8683
curl_slist_free_all(header_it->second);
8784
}
85+
86+
callbacks_.erase(easy);
8887
headers_.erase(easy);
88+
handles_.erase(easy);
8989
}
9090

9191
int CurlMultiManager::socket_callback(CURL* easy,
@@ -182,17 +182,21 @@ void CurlMultiManager::check_multi_info() {
182182
curl_slist* headers = nullptr;
183183
{
184184
std::lock_guard lock(mutex_);
185+
185186
if (auto it = callbacks_.find(easy); it != callbacks_.end()) {
186187
callback = std::move(it->second);
187188
callbacks_.erase(it);
188189
}
190+
callbacks_.erase(easy);
189191

190192
// Get and remove headers
191193
if (auto header_it = headers_.find(easy);
192194
header_it != headers_.end()) {
193195
headers = header_it->second;
194196
headers_.erase(header_it);
195197
}
198+
199+
handles_.erase(easy);
196200
}
197201

198202
// Remove from multi handle
@@ -224,10 +228,12 @@ void CurlMultiManager::start_socket_monitor(SocketInfo* socket_info,
224228
// Assign the CURL socket to the ASIO socket
225229
// tcp::socket::assign works with native socket handles on both platforms
226230
boost::system::error_code ec;
227-
socket_info->handle->assign(boost::asio::ip::tcp::v4(), socket_info->sockfd, ec);
231+
socket_info->handle->assign(boost::asio::ip::tcp::v4(),
232+
socket_info->sockfd, ec);
228233

229234
if (ec) {
230-
std::cerr << "Failed to assign socket: " << ec.message() << std::endl;
235+
std::cerr << "Failed to assign socket: " << ec.message() <<
236+
std::endl;
231237
socket_info->handle.reset();
232238
return;
233239
}
@@ -249,36 +255,41 @@ void CurlMultiManager::start_socket_monitor(SocketInfo* socket_info,
249255

250256
// Create and store handler in SocketInfo to keep it alive
251257
// Use weak_ptr in capture to avoid circular reference
252-
socket_info->read_handler = std::make_shared<std::function<void()>>();
253-
std::weak_ptr<std::function<void()>> weak_read_handler = socket_info->read_handler;
254-
*socket_info->read_handler = [weak_self, sockfd, weak_handle, weak_read_handler]() {
255-
// Check if manager and handle are still valid
256-
const auto self = weak_self.lock();
257-
const auto handle = weak_handle.lock();
258-
if (!self || !handle) {
259-
return;
260-
}
258+
socket_info->read_handler = std::make_shared<std::function<void
259+
()>>();
260+
std::weak_ptr<std::function<void()>> weak_read_handler = socket_info
261+
->read_handler;
262+
*socket_info->read_handler = [weak_self, sockfd, weak_handle,
263+
weak_read_handler]() {
264+
// Check if manager and handle are still valid
265+
const auto self = weak_self.lock();
266+
const auto handle = weak_handle.lock();
267+
if (!self || !handle) {
268+
return;
269+
}
261270

262-
handle->async_wait(
263-
boost::asio::ip::tcp::socket::wait_read,
264-
[weak_self, sockfd, weak_handle, weak_read_handler](
271+
handle->async_wait(
272+
boost::asio::ip::tcp::socket::wait_read,
273+
[weak_self, sockfd, weak_handle, weak_read_handler](
265274
const boost::system::error_code& ec) {
266-
// If operation was canceled or had an error, don't re-register
267-
if (ec) {
268-
return;
269-
}
270-
271-
if (const auto self = weak_self.lock()) {
272-
self->handle_socket_action(sockfd, CURL_CSELECT_IN);
273-
274-
// Always try to re-register for continuous monitoring
275-
// The validity check at the top of read_handler will stop it if needed
276-
if (const auto handler = weak_read_handler.lock()) {
277-
(*handler)(); // Recursive call
275+
// If operation was canceled or had an error, don't re-register
276+
if (ec) {
277+
return;
278+
}
279+
280+
if (const auto self = weak_self.lock()) {
281+
self->handle_socket_action(
282+
sockfd, CURL_CSELECT_IN);
283+
284+
// Always try to re-register for continuous monitoring
285+
// The validity check at the top of read_handler will stop it if needed
286+
if (const auto handler = weak_read_handler.
287+
lock()) {
288+
(*handler)(); // Recursive call
289+
}
278290
}
279-
}
280-
});
281-
};
291+
});
292+
};
282293
(*socket_info->read_handler)(); // Initial call
283294
}
284295
}
@@ -292,36 +303,41 @@ void CurlMultiManager::start_socket_monitor(SocketInfo* socket_info,
292303

293304
// Create and store handler in SocketInfo to keep it alive
294305
// Use weak_ptr in capture to avoid circular reference
295-
socket_info->write_handler = std::make_shared<std::function<void()>>();
296-
std::weak_ptr<std::function<void()>> weak_write_handler = socket_info->write_handler;
297-
*socket_info->write_handler = [weak_self, sockfd, weak_handle, weak_write_handler]() {
298-
// Check if manager and handle are still valid
299-
const auto self = weak_self.lock();
300-
const auto handle = weak_handle.lock();
301-
if (!self || !handle) {
302-
return;
303-
}
306+
socket_info->write_handler = std::make_shared<std::function<void
307+
()>>();
308+
std::weak_ptr<std::function<void()>> weak_write_handler =
309+
socket_info->write_handler;
310+
*socket_info->write_handler = [weak_self, sockfd, weak_handle,
311+
weak_write_handler]() {
312+
// Check if manager and handle are still valid
313+
const auto self = weak_self.lock();
314+
const auto handle = weak_handle.lock();
315+
if (!self || !handle) {
316+
return;
317+
}
304318

305-
handle->async_wait(
306-
boost::asio::ip::tcp::socket::wait_write,
307-
[weak_self, sockfd, weak_handle, weak_write_handler](
319+
handle->async_wait(
320+
boost::asio::ip::tcp::socket::wait_write,
321+
[weak_self, sockfd, weak_handle, weak_write_handler](
308322
const boost::system::error_code& ec) {
309-
// If operation was canceled or had an error, don't re-register
310-
if (ec) {
311-
return;
312-
}
313-
314-
if (const auto self = weak_self.lock()) {
315-
self->handle_socket_action(sockfd, CURL_CSELECT_OUT);
316-
317-
// Always try to re-register for continuous monitoring
318-
// The validity check at the top of write_handler will stop it if needed
319-
if (const auto handler = weak_write_handler.lock()) {
320-
(*handler)(); // Recursive call
323+
// If operation was canceled or had an error, don't re-register
324+
if (ec) {
325+
return;
326+
}
327+
328+
if (const auto self = weak_self.lock()) {
329+
self->handle_socket_action(
330+
sockfd, CURL_CSELECT_OUT);
331+
332+
// Always try to re-register for continuous monitoring
333+
// The validity check at the top of write_handler will stop it if needed
334+
if (const auto handler = weak_write_handler.
335+
lock()) {
336+
(*handler)(); // Recursive call
337+
}
321338
}
322-
}
323-
});
324-
};
339+
});
340+
};
325341
(*socket_info->write_handler)(); // Initial call
326342
}
327343
}

0 commit comments

Comments
 (0)