mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-06 13:02:55 +00:00
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:
committed by
Tristan Partin
parent
03f8a42ed9
commit
76b92e3389
@@ -35,16 +35,16 @@
|
|||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/jsonb.h"
|
#include "utils/jsonb.h"
|
||||||
|
|
||||||
|
#include "neon_utils.h"
|
||||||
|
|
||||||
static ProcessUtility_hook_type PreviousProcessUtilityHook = NULL;
|
static ProcessUtility_hook_type PreviousProcessUtilityHook = NULL;
|
||||||
|
|
||||||
|
static const char *jwt_token = NULL;
|
||||||
|
|
||||||
/* GUCs */
|
/* GUCs */
|
||||||
static char *ConsoleURL = NULL;
|
static char *ConsoleURL = NULL;
|
||||||
static bool ForwardDDL = true;
|
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
|
* CURL docs say that this buffer must exist until we call curl_easy_cleanup
|
||||||
* (which we never do), so we make this a static
|
* (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
|
static void
|
||||||
SendDeltasToControlPlane()
|
SendDeltasToControlPlane()
|
||||||
{
|
{
|
||||||
|
static CURL *handle = NULL;
|
||||||
|
|
||||||
if (!RootTable.db_table && !RootTable.role_table)
|
if (!RootTable.db_table && !RootTable.role_table)
|
||||||
return;
|
return;
|
||||||
if (!ConsoleURL)
|
if (!ConsoleURL)
|
||||||
@@ -236,29 +238,57 @@ SendDeltasToControlPlane()
|
|||||||
if (!ForwardDDL)
|
if (!ForwardDDL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
char *message = ConstructDeltaMessage();
|
if (handle == NULL)
|
||||||
ErrorString str = {};
|
{
|
||||||
|
struct curl_slist *headers = NULL;
|
||||||
|
|
||||||
curl_easy_setopt(CurlHandle, CURLOPT_CUSTOMREQUEST, "PATCH");
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||||
curl_easy_setopt(CurlHandle, CURLOPT_HTTPHEADER, ContentHeader);
|
if (headers == NULL)
|
||||||
curl_easy_setopt(CurlHandle, CURLOPT_POSTFIELDS, message);
|
{
|
||||||
curl_easy_setopt(CurlHandle, CURLOPT_URL, ConsoleURL);
|
elog(ERROR, "Failed to set Content-Type header");
|
||||||
curl_easy_setopt(CurlHandle, CURLOPT_ERRORBUFFER, CurlErrorBuf);
|
}
|
||||||
curl_easy_setopt(CurlHandle, CURLOPT_TIMEOUT, 3L /* seconds */ );
|
|
||||||
curl_easy_setopt(CurlHandle, CURLOPT_WRITEDATA, &str);
|
if (jwt_token)
|
||||||
curl_easy_setopt(CurlHandle, CURLOPT_WRITEFUNCTION, ErrorWriteCallback);
|
{
|
||||||
|
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;
|
const int num_retries = 5;
|
||||||
int curl_status;
|
CURLcode curl_status;
|
||||||
|
|
||||||
for (int i = 0; i < num_retries; i++)
|
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;
|
break;
|
||||||
elog(LOG, "Curl request failed on attempt %d: %s", i, CurlErrorBuf);
|
elog(LOG, "Curl request failed on attempt %d: %s", i, CurlErrorBuf);
|
||||||
pg_usleep(1000 * 1000);
|
pg_usleep(1000 * 1000);
|
||||||
}
|
}
|
||||||
if (curl_status != 0)
|
if (curl_status != CURLE_OK)
|
||||||
{
|
{
|
||||||
elog(ERROR, "Failed to perform curl request: %s", CurlErrorBuf);
|
elog(ERROR, "Failed to perform curl request: %s", CurlErrorBuf);
|
||||||
}
|
}
|
||||||
@@ -266,13 +296,11 @@ SendDeltasToControlPlane()
|
|||||||
{
|
{
|
||||||
long response_code;
|
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 (response_code != 200)
|
||||||
{
|
{
|
||||||
if (error_exists)
|
if (str.size != 0)
|
||||||
{
|
{
|
||||||
elog(ERROR,
|
elog(ERROR,
|
||||||
"Received HTTP code %ld from control plane: %s",
|
"Received HTTP code %ld from control plane: %s",
|
||||||
@@ -835,34 +863,10 @@ InitControlPlaneConnector()
|
|||||||
NULL,
|
NULL,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
const char *jwt_token = getenv("NEON_CONTROL_PLANE_TOKEN");
|
jwt_token = getenv("NEON_CONTROL_PLANE_TOKEN");
|
||||||
|
|
||||||
if (!jwt_token)
|
if (!jwt_token)
|
||||||
{
|
{
|
||||||
elog(LOG, "Missing NEON_CONTROL_PLANE_TOKEN environment variable, forwarding will not be authenticated");
|
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
#include "utils/guc.h"
|
#include "utils/guc.h"
|
||||||
|
|
||||||
|
#include "neon_utils.h"
|
||||||
|
|
||||||
static int extension_server_port = 0;
|
static int extension_server_port = 0;
|
||||||
|
|
||||||
static download_extension_file_hook_type prev_download_extension_file_hook = NULL;
|
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
|
static bool
|
||||||
neon_download_extension_file_http(const char *filename, bool is_library)
|
neon_download_extension_file_http(const char *filename, bool is_library)
|
||||||
{
|
{
|
||||||
CURL *curl;
|
static CURL *handle = NULL;
|
||||||
|
|
||||||
CURLcode res;
|
CURLcode res;
|
||||||
char *compute_ctl_url;
|
char *compute_ctl_url;
|
||||||
char *postdata;
|
char *postdata;
|
||||||
bool ret = false;
|
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",
|
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);
|
elog(LOG, "Sending request to compute_ctl: %s", compute_ctl_url);
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
|
curl_easy_setopt(handle, CURLOPT_URL, compute_ctl_url);
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, compute_ctl_url);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L /* seconds */ );
|
|
||||||
|
|
||||||
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 */
|
ret = true;
|
||||||
res = curl_easy_perform(curl);
|
}
|
||||||
/* Check for errors */
|
else
|
||||||
if (res == CURLE_OK)
|
{
|
||||||
{
|
/*
|
||||||
ret = true;
|
* 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.
|
||||||
else
|
*/
|
||||||
{
|
elog(WARNING, "neon_download_extension_file_http failed: %s\n", curl_easy_strerror(res));
|
||||||
/* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
|
|
||||||
|
#ifndef WALPROPOSER_LIB
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "lib/stringinfo.h"
|
#include "lib/stringinfo.h"
|
||||||
@@ -114,3 +117,48 @@ disable_core_dump()
|
|||||||
fprintf(stderr, "WARNING: disable cores setrlimit failed: %s", strerror(save_errno));
|
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
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
#ifndef __NEON_UTILS_H__
|
#ifndef __NEON_UTILS_H__
|
||||||
#define __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);
|
bool HexDecodeString(uint8 *result, char *input, int nbytes);
|
||||||
uint32 pq_getmsgint32_le(StringInfo msg);
|
uint32 pq_getmsgint32_le(StringInfo msg);
|
||||||
uint64 pq_getmsgint64_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);
|
void pq_sendint64_le(StringInfo buf, uint64 i);
|
||||||
extern void disable_core_dump();
|
extern void disable_core_dump();
|
||||||
|
|
||||||
|
#ifndef WALPROPOSER_LIB
|
||||||
|
|
||||||
|
CURL * alloc_curl_handle(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* __NEON_UTILS_H__ */
|
#endif /* __NEON_UTILS_H__ */
|
||||||
|
|||||||
Reference in New Issue
Block a user