Fix multithreaded postmaster on macOS

curl_global_init() with an IPv6 enabled curl build on macOS will cause
the calling program to become multithreaded. Unfortunately for
shared_preload_libraries, that means the postmaster becomes
multithreaded, which CANNOT happen. There are checks in Postgres to make
sure that this is not the case.
This commit is contained in:
Tristan Partin
2024-02-12 08:33:37 -06:00
committed by Tristan Partin
parent 03f8a42ed9
commit 76b92e3389
4 changed files with 134 additions and 70 deletions

View File

@@ -35,16 +35,16 @@
#include "utils/memutils.h"
#include "utils/jsonb.h"
#include "neon_utils.h"
static ProcessUtility_hook_type PreviousProcessUtilityHook = NULL;
static const char *jwt_token = NULL;
/* GUCs */
static char *ConsoleURL = NULL;
static bool ForwardDDL = true;
/* Curl structures for sending the HTTP requests */
static CURL *CurlHandle;
static struct curl_slist *ContentHeader = NULL;
/*
* CURL docs say that this buffer must exist until we call curl_easy_cleanup
* (which we never do), so we make this a static
@@ -226,6 +226,8 @@ ErrorWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
static void
SendDeltasToControlPlane()
{
static CURL *handle = NULL;
if (!RootTable.db_table && !RootTable.role_table)
return;
if (!ConsoleURL)
@@ -236,29 +238,57 @@ SendDeltasToControlPlane()
if (!ForwardDDL)
return;
char *message = ConstructDeltaMessage();
ErrorString str = {};
if (handle == NULL)
{
struct curl_slist *headers = NULL;
curl_easy_setopt(CurlHandle, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(CurlHandle, CURLOPT_HTTPHEADER, ContentHeader);
curl_easy_setopt(CurlHandle, CURLOPT_POSTFIELDS, message);
curl_easy_setopt(CurlHandle, CURLOPT_URL, ConsoleURL);
curl_easy_setopt(CurlHandle, CURLOPT_ERRORBUFFER, CurlErrorBuf);
curl_easy_setopt(CurlHandle, CURLOPT_TIMEOUT, 3L /* seconds */ );
curl_easy_setopt(CurlHandle, CURLOPT_WRITEDATA, &str);
curl_easy_setopt(CurlHandle, CURLOPT_WRITEFUNCTION, ErrorWriteCallback);
headers = curl_slist_append(headers, "Content-Type: application/json");
if (headers == NULL)
{
elog(ERROR, "Failed to set Content-Type header");
}
if (jwt_token)
{
char auth_header[8192];
snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", jwt_token);
headers = curl_slist_append(headers, auth_header);
if (headers == NULL)
{
elog(ERROR, "Failed to set Authorization header");
}
}
handle = alloc_curl_handle();
curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(handle, CURLOPT_URL, ConsoleURL);
curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, CurlErrorBuf);
curl_easy_setopt(handle, CURLOPT_TIMEOUT, 3L /* seconds */ );
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ErrorWriteCallback);
}
char *message = ConstructDeltaMessage();
ErrorString str;
str.size = 0;
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, message);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &str);
const int num_retries = 5;
int curl_status;
CURLcode curl_status;
for (int i = 0; i < num_retries; i++)
{
if ((curl_status = curl_easy_perform(CurlHandle)) == 0)
if ((curl_status = curl_easy_perform(handle)) == 0)
break;
elog(LOG, "Curl request failed on attempt %d: %s", i, CurlErrorBuf);
pg_usleep(1000 * 1000);
}
if (curl_status != 0)
if (curl_status != CURLE_OK)
{
elog(ERROR, "Failed to perform curl request: %s", CurlErrorBuf);
}
@@ -266,13 +296,11 @@ SendDeltasToControlPlane()
{
long response_code;
if (curl_easy_getinfo(CurlHandle, CURLINFO_RESPONSE_CODE, &response_code) != CURLE_UNKNOWN_OPTION)
if (curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code) != CURLE_UNKNOWN_OPTION)
{
bool error_exists = str.size != 0;
if (response_code != 200)
{
if (error_exists)
if (str.size != 0)
{
elog(ERROR,
"Received HTTP code %ld from control plane: %s",
@@ -835,34 +863,10 @@ InitControlPlaneConnector()
NULL,
NULL);
const char *jwt_token = getenv("NEON_CONTROL_PLANE_TOKEN");
jwt_token = getenv("NEON_CONTROL_PLANE_TOKEN");
if (!jwt_token)
{
elog(LOG, "Missing NEON_CONTROL_PLANE_TOKEN environment variable, forwarding will not be authenticated");
}
if (curl_global_init(CURL_GLOBAL_DEFAULT))
{
elog(ERROR, "Failed to initialize curl");
}
if ((CurlHandle = curl_easy_init()) == NULL)
{
elog(ERROR, "Failed to initialize curl handle");
}
if ((ContentHeader = curl_slist_append(ContentHeader, "Content-Type: application/json")) == NULL)
{
elog(ERROR, "Failed to initialize content header");
}
if (jwt_token)
{
char auth_header[8192];
snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", jwt_token);
if ((ContentHeader = curl_slist_append(ContentHeader, auth_header)) == NULL)
{
elog(ERROR, "Failed to initialize authorization header");
}
}
}

View File

@@ -14,6 +14,8 @@
#include "utils/guc.h"
#include "neon_utils.h"
static int extension_server_port = 0;
static download_extension_file_hook_type prev_download_extension_file_hook = NULL;
@@ -31,15 +33,19 @@ static download_extension_file_hook_type prev_download_extension_file_hook = NUL
static bool
neon_download_extension_file_http(const char *filename, bool is_library)
{
CURL *curl;
static CURL *handle = NULL;
CURLcode res;
char *compute_ctl_url;
char *postdata;
bool ret = false;
if ((curl = curl_easy_init()) == NULL)
if (handle == NULL)
{
elog(ERROR, "Failed to initialize curl handle");
handle = alloc_curl_handle();
curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(handle, CURLOPT_TIMEOUT, 3L /* seconds */ );
}
compute_ctl_url = psprintf("http://localhost:%d/extension_server/%s%s",
@@ -47,28 +53,22 @@ neon_download_extension_file_http(const char *filename, bool is_library)
elog(LOG, "Sending request to compute_ctl: %s", compute_ctl_url);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(curl, CURLOPT_URL, compute_ctl_url);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L /* seconds */ );
curl_easy_setopt(handle, CURLOPT_URL, compute_ctl_url);
if (curl)
/* Perform the request, res will get the return code */
res = curl_easy_perform(handle);
/* Check for errors */
if (res == CURLE_OK)
{
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if (res == CURLE_OK)
{
ret = true;
}
else
{
/* Don't error here because postgres will try to find the file */
/* and will fail with some proper error message if it's not found. */
elog(WARNING, "neon_download_extension_file_http failed: %s\n", curl_easy_strerror(res));
}
/* always cleanup */
curl_easy_cleanup(curl);
ret = true;
}
else
{
/*
* Don't error here because postgres will try to find the file and will
* fail with some proper error message if it's not found.
*/
elog(WARNING, "neon_download_extension_file_http failed: %s\n", curl_easy_strerror(res));
}
return ret;

View File

@@ -1,6 +1,9 @@
#include <sys/resource.h>
#ifndef WALPROPOSER_LIB
#include <curl/curl.h>
#endif
#include "postgres.h"
#include "lib/stringinfo.h"
@@ -114,3 +117,48 @@ disable_core_dump()
fprintf(stderr, "WARNING: disable cores setrlimit failed: %s", strerror(save_errno));
}
}
#ifndef WALPROPOSER_LIB
/*
* On macOS with a libcurl that has IPv6 support, curl_global_init() calls
* SCDynamicStoreCopyProxies(), which makes the program multithreaded. An ideal
* place to call curl_global_init() would be _PG_init(), but Neon has to be
* added to shared_preload_libraries, which are loaded in the Postmaster
* process. The Postmaster is not supposed to become multithreaded at any point
* in its lifecycle. Postgres doesn't have any good hook that I know of to
* initialize per-backend structures, so we have to check this on any
* allocation of a CURL handle.
*
* Free the allocated CURL handle with curl_easy_cleanup(3).
*
* https://developer.apple.com/documentation/systemconfiguration/1517088-scdynamicstorecopyproxies
*/
CURL *
alloc_curl_handle(void)
{
static bool curl_initialized = false;
CURL *handle;
if (unlikely(!curl_initialized))
{
/* Protected by mutex internally */
if (curl_global_init(CURL_GLOBAL_DEFAULT))
{
elog(ERROR, "Failed to initialize curl");
}
curl_initialized = true;
}
handle = curl_easy_init();
if (handle == NULL)
{
elog(ERROR, "Failed to initialize curl handle");
}
return handle;
}
#endif

View File

@@ -1,6 +1,12 @@
#ifndef __NEON_UTILS_H__
#define __NEON_UTILS_H__
#include "lib/stringinfo.h"
#ifndef WALPROPOSER_LIB
#include <curl/curl.h>
#endif
bool HexDecodeString(uint8 *result, char *input, int nbytes);
uint32 pq_getmsgint32_le(StringInfo msg);
uint64 pq_getmsgint64_le(StringInfo msg);
@@ -8,4 +14,10 @@ void pq_sendint32_le(StringInfo buf, uint32 i);
void pq_sendint64_le(StringInfo buf, uint64 i);
extern void disable_core_dump();
#ifndef WALPROPOSER_LIB
CURL * alloc_curl_handle(void);
#endif
#endif /* __NEON_UTILS_H__ */