From a4145bf4ffd7b69919f100683cbb2ce580087d0c Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Wed, 13 Aug 2025 16:30:27 -0500 Subject: [PATCH] refactor: use curl_global_init_mem with pg allocators Uses `curl_global_init_mem(CURL_GLOBAL_DEFAULT, net_malloc, net_free, net_realloc, net_strdup, net_calloc)` to keep better track of memory allocation by curl. The aim is to fix https://github.com/supabase/pg_net/issues/216. Unfortunately this currently segfaults when reaching 2 iterations: ```sql -- do twice select net.http_get('http://localhost:8080/pathological?status=200') from generate_series(1,10); select net.http_get('http://localhost:8080/pathological?status=200') from generate_series(1,10); -- or once (this will do two iterations since the batch size defaults to 200) select net.http_get('http://localhost:8080/pathological?status=200') from generate_series(1,300); ``` The logs: ``` 2025-08-14 17:55:08.571 -05 [643019] DEBUG: net_realloc(palloc): a((nil)), sz(32) 2025-08-14 17:55:08.571 -05 [643019] DEBUG: net_malloc: sz(32) 2025-08-14 17:55:08.665 -05 [643013] LOG: background worker "pg_net 0.19.5 worker" (PID 643019) was terminated by signal 11: Segmentation fault ``` Using `gdb` (via convenience `sudo xpg gdb`) will show that this will always fail when hitting curl functions: ``` Curl_attach_connection Curl_llist_count Curl_conn_is_alive ``` Which are called when using `curl_multi_socket_action` in our codebase in https://github.com/supabase/pg_net/blob/a7792bfd913c7859e14025d606624982706c2a7f/src/worker.c#L320-L336 Unfortunately this is quite hard to debug, it might be a bug in curl itself. --- src/worker.c | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/worker.c b/src/worker.c index cec0b5e..c41e496 100644 --- a/src/worker.c +++ b/src/worker.c @@ -232,6 +232,48 @@ static void unlock_extension(Oid ext_table_oids[static total_extension_tables]){ UnlockRelationOid(ext_table_oids[1], AccessShareLock); } +static void * +net_calloc(size_t a, size_t b) +{ + elog(DEBUG1, "net_calloc: a(%zu), b(%zu)", a, b); + return palloc0(mul_size(a, b)); +} + +static void +net_free(void *a) +{ + elog(DEBUG1, "net_free"); + if (a) + pfree(a); +} + +static void * +net_malloc(size_t sz) +{ + elog(DEBUG1, "net_malloc: sz(%zu)", sz); + return sz ? palloc(sz) : NULL; +} + +static void * +net_realloc(void *a, size_t sz) +{ + elog(DEBUG1, "net_realloc(%s): a(%p), sz(%zu)", (sz>0?(a?"repalloc":"palloc"):"return"), a, sz); + if (sz > 0){ + if (a) + return repalloc(a, sz); + else + return net_malloc(sz); + } + else + return a; +} + +static char * +net_strdup(const char *in) +{ + return pstrdup(in); +} + void pg_net_worker(__attribute__ ((unused)) Datum main_arg) { worker_state->shared_latch = &MyProc->procLatch; on_proc_exit(net_on_exit, 0); @@ -246,7 +288,7 @@ void pg_net_worker(__attribute__ ((unused)) Datum main_arg) { elog(INFO, "pg_net worker started with a config of: pg_net.ttl=%s, pg_net.batch_size=%d, pg_net.username=%s, pg_net.database_name=%s", guc_ttl, guc_batch_size, guc_username, guc_database_name); - int curl_ret = curl_global_init(CURL_GLOBAL_ALL); + int curl_ret = curl_global_init_mem(CURL_GLOBAL_DEFAULT, net_malloc, net_free, net_realloc, net_strdup, net_calloc); if(curl_ret != CURLE_OK) ereport(ERROR, errmsg("curl_global_init() returned %s\n", curl_easy_strerror(curl_ret)));