Skip to content

Commit 3dd271a

Browse files
committed
perf(network): reuse curl handles for sync requests
Add a small per-network_data curl easy-handle pool for API and artifact requests to preserve connection reuse across sync operations. The pool is enabled by default and can be disabled with CLOUDSYNC_CURL_POOL=0, false, off, or no. Connection reuse is bounded by CLOUDSYNC_CURL_MAXCONNECTS, CLOUDSYNC_CURL_MAXAGE_CONN_SECONDS, and CLOUDSYNC_CURL_MAXLIFETIME_CONN_SECONDS.
1 parent 0dfebc3 commit 3dd271a

1 file changed

Lines changed: 99 additions & 10 deletions

File tree

src/network/network.c

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <stdint.h>
1111
#include <inttypes.h>
1212
#include <stdlib.h>
13+
#include <string.h>
1314

1415
#ifdef CLOUDSYNC_NETWORK_TRACE
1516
#ifdef _WIN32
@@ -45,6 +46,16 @@ static size_t cacert_len = sizeof(cacert_pem) - 1;
4546
#define CLOUDSYNC_NETWORK_MINBUF_SIZE 512
4647
#define CLOUDSYNC_SESSION_TOKEN_MAXSIZE 4096
4748

49+
#ifndef CLOUDSYNC_CURL_MAXCONNECTS
50+
#define CLOUDSYNC_CURL_MAXCONNECTS 2L
51+
#endif
52+
#ifndef CLOUDSYNC_CURL_MAXAGE_CONN_SECONDS
53+
#define CLOUDSYNC_CURL_MAXAGE_CONN_SECONDS 15L
54+
#endif
55+
#ifndef CLOUDSYNC_CURL_MAXLIFETIME_CONN_SECONDS
56+
#define CLOUDSYNC_CURL_MAXLIFETIME_CONN_SECONDS 60L
57+
#endif
58+
4859
#define DEFAULT_SYNC_WAIT_MS 100
4960
#define DEFAULT_SYNC_MAX_RETRIES 1
5061

@@ -64,6 +75,11 @@ struct network_data {
6475
char *upload_endpoint;
6576
char *apply_endpoint;
6677
char *status_endpoint;
78+
#ifndef CLOUDSYNC_OMIT_CURL
79+
CURL *api_curl;
80+
CURL *artifact_curl;
81+
int curl_pool_enabled;
82+
#endif
6783
};
6884

6985
#ifdef CLOUDSYNC_NETWORK_TRACE
@@ -105,6 +121,32 @@ void network_trace_log(network_data *data, const char *method, const char *endpo
105121
network_trace_endpoint_name(data, endpoint), method, http_status,
106122
network_trace_result_name(result_code), bytes, elapsed_ms);
107123
}
124+
125+
#ifndef CLOUDSYNC_OMIT_CURL
126+
void network_trace_log_curl(network_data *data, const char *method, const char *endpoint, long http_status, int result_code, size_t bytes, CURL *curl, bool pooled, double elapsed_ms) {
127+
double namelookup = 0.0;
128+
double connect = 0.0;
129+
double appconnect = 0.0;
130+
double starttransfer = 0.0;
131+
double total = 0.0;
132+
long num_connects = 0;
133+
if (curl) {
134+
curl_easy_getinfo(curl, CURLINFO_NAMELOOKUP_TIME, &namelookup);
135+
curl_easy_getinfo(curl, CURLINFO_CONNECT_TIME, &connect);
136+
curl_easy_getinfo(curl, CURLINFO_APPCONNECT_TIME, &appconnect);
137+
curl_easy_getinfo(curl, CURLINFO_STARTTRANSFER_TIME, &starttransfer);
138+
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &total);
139+
curl_easy_getinfo(curl, CURLINFO_NUM_CONNECTS, &num_connects);
140+
}
141+
fprintf(stderr,
142+
"[cloudsync-network] endpoint=%s method=%s pool=%s http_status=%ld result=%s bytes=%zu elapsed_ms=%.2f curl_total_ms=%.2f dns_ms=%.2f connect_ms=%.2f tls_ms=%.2f starttransfer_ms=%.2f num_connects=%ld\n",
143+
network_trace_endpoint_name(data, endpoint), method,
144+
pooled ? "on" : "off", http_status,
145+
network_trace_result_name(result_code), bytes, elapsed_ms,
146+
total * 1000.0, namelookup * 1000.0, connect * 1000.0,
147+
appconnect * 1000.0, starttransfer * 1000.0, num_connects);
148+
}
149+
#endif
108150
#endif
109151

110152
typedef struct {
@@ -211,6 +253,10 @@ bool network_data_set_endpoints (network_data *data, char *auth, char *check, ch
211253
void network_data_free (network_data *data) {
212254
if (!data) return;
213255

256+
#ifndef CLOUDSYNC_OMIT_CURL
257+
if (data->api_curl) curl_easy_cleanup(data->api_curl);
258+
if (data->artifact_curl) curl_easy_cleanup(data->artifact_curl);
259+
#endif
214260
if (data->authentication) cloudsync_memory_free(data->authentication);
215261
if (data->org_id) cloudsync_memory_free(data->org_id);
216262
if (data->check_endpoint) cloudsync_memory_free(data->check_endpoint);
@@ -223,6 +269,47 @@ void network_data_free (network_data *data) {
223269
// MARK: - Utils -
224270

225271
#ifndef CLOUDSYNC_OMIT_CURL
272+
static bool network_curl_pool_enabled(network_data *data) {
273+
if (!data) return false;
274+
if (data->curl_pool_enabled == 0) {
275+
const char *value = getenv("CLOUDSYNC_CURL_POOL");
276+
data->curl_pool_enabled = 1;
277+
if (value && (strcmp(value, "0") == 0 || strcmp(value, "false") == 0 || strcmp(value, "off") == 0 || strcmp(value, "no") == 0)) {
278+
data->curl_pool_enabled = -1;
279+
}
280+
}
281+
return data->curl_pool_enabled > 0;
282+
}
283+
284+
static bool network_endpoint_is_api(network_data *data, const char *endpoint) {
285+
if (!data || !endpoint) return false;
286+
return (data->check_endpoint && strcmp(endpoint, data->check_endpoint) == 0) ||
287+
(data->upload_endpoint && strcmp(endpoint, data->upload_endpoint) == 0) ||
288+
(data->apply_endpoint && strcmp(endpoint, data->apply_endpoint) == 0) ||
289+
(data->status_endpoint && strcmp(endpoint, data->status_endpoint) == 0);
290+
}
291+
292+
static CURL *network_curl_for_endpoint(network_data *data, const char *endpoint, bool *pooled) {
293+
if (pooled) *pooled = false;
294+
if (!network_curl_pool_enabled(data)) {
295+
return curl_easy_init();
296+
}
297+
298+
CURL **slot = network_endpoint_is_api(data, endpoint) ? &data->api_curl : &data->artifact_curl;
299+
if (!*slot) {
300+
*slot = curl_easy_init();
301+
} else {
302+
curl_easy_reset(*slot);
303+
}
304+
if (!*slot) return NULL;
305+
306+
curl_easy_setopt(*slot, CURLOPT_MAXCONNECTS, CLOUDSYNC_CURL_MAXCONNECTS);
307+
curl_easy_setopt(*slot, CURLOPT_MAXAGE_CONN, CLOUDSYNC_CURL_MAXAGE_CONN_SECONDS);
308+
curl_easy_setopt(*slot, CURLOPT_MAXLIFETIME_CONN, CLOUDSYNC_CURL_MAXLIFETIME_CONN_SECONDS);
309+
if (pooled) *pooled = true;
310+
return *slot;
311+
}
312+
226313
static bool network_buffer_check (network_buffer *data, size_t needed) {
227314
// alloc/resize buffer
228315
if (data->bused + needed > data->balloc) {
@@ -259,12 +346,13 @@ NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint,
259346
struct curl_slist* headers = NULL;
260347
char errbuf[CURL_ERROR_SIZE] = {0};
261348
long response_code = 0;
349+
bool pooled = false;
262350
const char *method = (json_payload || is_post_request) ? "POST" : "GET";
263351
#ifdef CLOUDSYNC_NETWORK_TRACE
264352
double trace_start_ms = network_trace_now_ms();
265353
#endif
266354

267-
CURL *curl = curl_easy_init();
355+
CURL *curl = network_curl_for_endpoint(data, endpoint, &pooled);
268356
if (!curl) return (NETWORK_RESULT){CLOUDSYNC_NETWORK_ERROR, NULL, 0, NULL, NULL};
269357

270358
// a buffer to store errors in
@@ -337,7 +425,6 @@ NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint,
337425
}
338426

339427
cleanup:
340-
if (curl) curl_easy_cleanup(curl);
341428
if (headers) curl_slist_free_all(headers);
342429

343430
// build result
@@ -353,8 +440,9 @@ NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint,
353440
}
354441

355442
#ifdef CLOUDSYNC_NETWORK_TRACE
356-
network_trace_log(data, method, endpoint, response_code, result.code, result.blen, network_trace_now_ms() - trace_start_ms);
443+
network_trace_log_curl(data, method, endpoint, response_code, result.code, result.blen, curl, pooled, network_trace_now_ms() - trace_start_ms);
357444
#endif
445+
if (curl && !pooled) curl_easy_cleanup(curl);
358446
return result;
359447
}
360448

@@ -378,12 +466,13 @@ bool network_send_buffer (network_data *data, const char *endpoint, const char *
378466
char errbuf[CURL_ERROR_SIZE] = {0};
379467
CURLcode rc = CURLE_OK;
380468
long response_code = 0;
469+
bool pooled = false;
381470
#ifdef CLOUDSYNC_NETWORK_TRACE
382471
double trace_start_ms = network_trace_now_ms();
383472
#endif
384473

385-
// init curl
386-
CURL *curl = curl_easy_init();
474+
// init/reuse curl
475+
CURL *curl = network_curl_for_endpoint(data, endpoint, &pooled);
387476
if (!curl) return false;
388477

389478
// set the URL
@@ -458,12 +547,12 @@ bool network_send_buffer (network_data *data, const char *endpoint, const char *
458547

459548
cleanup:
460549
#ifdef CLOUDSYNC_NETWORK_TRACE
461-
network_trace_log(data, "PUT", endpoint, response_code,
462-
result ? CLOUDSYNC_NETWORK_OK : CLOUDSYNC_NETWORK_ERROR,
463-
result ? (size_t)blob_size : 0,
464-
network_trace_now_ms() - trace_start_ms);
550+
network_trace_log_curl(data, "PUT", endpoint, response_code,
551+
result ? CLOUDSYNC_NETWORK_OK : CLOUDSYNC_NETWORK_ERROR,
552+
result ? (size_t)blob_size : 0,
553+
curl, pooled, network_trace_now_ms() - trace_start_ms);
465554
#endif
466-
if (curl) curl_easy_cleanup(curl);
555+
if (curl && !pooled) curl_easy_cleanup(curl);
467556
if (headers) curl_slist_free_all(headers);
468557
return result;
469558
}

0 commit comments

Comments
 (0)