You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/src/main.cpp

2929 lines
86 KiB

#define MAIN_PROXY_SQLITE3
#include "../deps/json/json.hpp"
using json = nlohmann::json;
#define PROXYJSON
#include <iostream>
#include <thread>
#include "btree_map.h"
#include "proxysql.h"
#include <random>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//#define PROXYSQL_EXTERN
#include "cpp.h"
#include "mysqld_error.h"
#include "ProxySQL_Statistics.hpp"
#include "MySQL_PreparedStatement.h"
#include "ProxySQL_Cluster.hpp"
#include "MySQL_Logger.hpp"
#include "PgSQL_Logger.hpp"
#include "SQLite3_Server.h"
#include "MySQL_Query_Processor.h"
#include "PgSQL_Query_Processor.h"
#include "MySQL_Authentication.hpp"
#include "PgSQL_Authentication.h"
#include "MySQL_LDAP_Authentication.hpp"
#include "MySQL_Query_Cache.h"
#include "PgSQL_Query_Cache.h"
#include "proxysql_restapi.h"
#include "Web_Interface.hpp"
#include "proxysql_utils.h"
#include "PgSQL_Monitor.hpp"
#include "libdaemon/dfork.h"
#include "libdaemon/dsignal.h"
#include "libdaemon/dlog.h"
#include "libdaemon/dpid.h"
#include "libdaemon/dexec.h"
#include "ev.h"
#include "curl/curl.h"
#include "openssl/x509v3.h"
#include <sys/mman.h>
#include <uuid/uuid.h>
#include <atomic>
#ifdef DEBUG
#include "proxy_protocol_info.h"
#endif // DEBUG
/*
extern "C" MySQL_LDAP_Authentication * create_MySQL_LDAP_Authentication_func() {
return NULL;
}
*/
using std::map;
using std::string;
using std::vector;
void sleep_iter(unsigned int iter) {
usleep(50*iter);
#ifdef RUNNING_ON_VALGRIND
usleep((1000+rand()%1000)*iter);
#endif // RUNNING_ON_VALGRIND
}
volatile create_MySQL_LDAP_Authentication_t * create_MySQL_LDAP_Authentication = NULL;
void * __mysql_ldap_auth;
volatile create_Web_Interface_t * create_Web_Interface = NULL;
void * __web_interface;
std::thread* pgsql_monitor_thread = nullptr;
extern int ProxySQL_create_or_load_TLS(bool bootstrap, std::string& msg);
char *binary_sha1 = NULL;
// MariaDB client library redefines dlerror(), see https://mariadb.atlassian.net/browse/CONC-101
#ifdef dlerror
#undef dlerror
#endif
static pthread_mutex_t *lockarray;
#include "openssl/crypto.h"
// this fuction will be called as a deatached thread
static void * waitpid_thread(void *arg) {
pid_t *cpid_ptr=(pid_t *)arg;
int status;
set_thread_name("waitpid");
waitpid(*cpid_ptr, &status, 0);
free(cpid_ptr);
return NULL;
}
struct MemoryStruct {
char *memory;
size_t size;
};
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);
assert(mem->memory);
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
char * know_latest_version = NULL;
static unsigned int randID = 0;
/**
* @brief Checks for the latest version of ProxySQL by querying the specified URL.
*
* This function sends an HTTP GET request to the ProxySQL website to fetch the latest version information.
* It sets a custom user-agent string containing the version, SHA1 hash of the binary (if available), and a random ID.
* The response is stored in memory and returned as a character pointer.
*
* @return A character pointer containing the response data from the Proxysql website.
* If an error occurs during the HTTP request, NULL is returned.
*/
static char * main_check_latest_version() {
CURL *curl_handle; // CURL handle for performing HTTP requests
CURLcode res; // Variable to store CURL operation result
struct MemoryStruct chunk; // // Struct to store memory chunk received from HTTP response
// Initialize memory struct to store response data
chunk.memory = (char *)malloc(1);
chunk.size = 0;
// Initialize CURL library
curl_global_init(CURL_GLOBAL_ALL);
// Initialize CURL handle for HTTP request
curl_handle = curl_easy_init();
// Set CURL options for the HTTP request
curl_easy_setopt(curl_handle, CURLOPT_URL, "https://www.proxysql.com/latest");
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYSTATUS, 0L);
curl_easy_setopt(curl_handle, CURLOPT_RANGE, "0-31");
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
// Set custom user-agent string including ProxySQL version, binary SHA1 hash, and a random ID
string s = "proxysql-agent/";
s += PROXYSQL_VERSION;
if (binary_sha1) {
s += " (" ;
s+= binary_sha1;
s += ")" ;
}
s += " " + std::to_string(randID);
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, s.c_str());
// Set timeout and connect timeout for the HTTP request
curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, 10);
curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 10);
// Perform the HTTP request
res = curl_easy_perform(curl_handle);
// Check if the request was successful
if (res != CURLE_OK) {
switch (res) {
// Handle common errors and free memory if necessary
case CURLE_COULDNT_RESOLVE_HOST:
case CURLE_COULDNT_CONNECT:
case CURLE_OPERATION_TIMEDOUT:
// These errors are expected in case of network issues or timeouts
break;
default:
// Log other errors using proxy_error and free memory
proxy_error("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
break;
}
free(chunk.memory);
chunk.memory = NULL;
}
// Cleanup CURL handle and global resources
curl_easy_cleanup(curl_handle);
curl_global_cleanup();
// Return the response data from the ProxySQL website
return chunk.memory;
}
/**
* @brief Thread function to check for the latest version of ProxySQL asynchronously.
*
* This function is intended to be executed as a separate thread to asynchronously check for the latest version of ProxySQL.
* It calls the main_check_latest_version function to fetch the latest version information from the ProxySQL website.
* If the fetched version information is valid and different from the currently known latest version,
* it updates the known latest version and logs a message indicating the availability of the new version.
*
* @param arg Pointer to the argument passed to the thread function (unused).
* @return NULL.
*/
void * main_check_latest_version_thread(void *arg) {
set_thread_name("CheckLatestVers");
// Fetch the latest version information
char * latest_version = main_check_latest_version();
// we check for potential invalid data , see issue #4042
// Check for potential invalid data and update the known latest version if a new version is detected
if (latest_version != NULL && strlen(latest_version) < 32) {
if (
(know_latest_version == NULL) // first check
|| (strcmp(know_latest_version,latest_version)) // new version detected
) {
// Free previously known latest version and update it with the new version
if (know_latest_version)
free(know_latest_version);
// Duplicate latest version string
know_latest_version = strdup(latest_version);
// Log the availability of the new version
proxy_info("Latest ProxySQL version available: %s\n", latest_version);
}
}
free(latest_version);
return NULL;
}
// Note: if you are running ProxySQL under gdb, you may consider setting this
// variable immediately to 1
// Example:
// set disable_watchdog=1
volatile int disable_watchdog = 0;
void parent_open_error_log() {
if (GloVars.global.foreground==false) {
int outfd=0;
int errfd=0;
outfd=open(GloVars.errorlog, O_WRONLY | O_APPEND | O_CREAT , S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
if (outfd>0) {
dup2(outfd, STDOUT_FILENO);
close(outfd);
} else {
proxy_error("Impossible to open file\n");
}
errfd=open(GloVars.errorlog, O_WRONLY | O_APPEND | O_CREAT , S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
if (errfd>0) {
dup2(errfd, STDERR_FILENO);
close(errfd);
} else {
proxy_error("Impossible to open file\n");
}
}
}
void parent_close_error_log() {
if (GloVars.global.foreground==false) {
close(STDOUT_FILENO);
close(STDERR_FILENO);
}
}
pid_t pid;
static const char * proxysql_pid_file() {
static char fn[512];
snprintf(fn, sizeof(fn), "%s", daemon_pid_file_ident);
return fn;
}
/*struct cpu_timer
{
~cpu_timer()
{
auto end = std::clock() ;
std::cerr << double( end - begin ) / CLOCKS_PER_SEC << " secs.\n" ;
};
const std::clock_t begin = std::clock() ;
};
*/
struct cpu_timer
{
cpu_timer() {
begin = monotonic_time();
}
~cpu_timer()
{
unsigned long long end = monotonic_time();
#ifdef DEBUG
std::cerr << double( end - begin ) / 1000000 << " secs.\n" ;
#endif /* DEBUG */
begin=end-begin; // here only to make compiler happy
};
unsigned long long begin;
};
static unsigned long thread_id(void) {
unsigned long ret;
ret = (unsigned long)pthread_self();
return ret;
}
static void init_locks(void) {
int i;
lockarray = (pthread_mutex_t *)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
for(i = 0; i<CRYPTO_num_locks(); i++) {
pthread_mutex_init(&(lockarray[i]), NULL);
}
CRYPTO_set_id_callback((unsigned long (*)())thread_id);
// deprecated
//CRYPTO_set_locking_callback((void (*)(int, int, const char *, int))lock_callback);
}
static bool check_openssl_version() {
unsigned long version = OpenSSL_version_num();
const unsigned long OPENSSL_3_0_0 = 0x30000000L;
const unsigned long OPENSSL_3_2_0 = 0x30200000L;
proxy_info("Using OpenSSL version: %s\n", OpenSSL_version(OPENSSL_VERSION));
if (version > OPENSSL_3_0_0 && version < OPENSSL_3_2_0) {
proxy_warning(
"Detected OpenSSL version has known performance regressions, please upgrade to '3.2.0' or later."
" For further details, please refer to: https://github.com/openssl/openssl/issues/18814\n"
);
}
if (version < OPENSSL_3_0_0) {
proxy_error("%s\n", "ProxySQL server required openssl version 3.0.0 or above");
return false;
}
return true;
}
void ProxySQL_Main_init_SSL_module() {
int rc = SSL_library_init();
if (rc==0) {
proxy_error("%s\n", SSL_alert_desc_string_long(rc));
}
init_locks();
SSL_METHOD *ssl_method;
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
//ssl_method = (SSL_METHOD *)TLSv1_server_method();
//ssl_method = (SSL_METHOD *)SSLv23_server_method();
ssl_method = (SSL_METHOD *)TLS_server_method();
GloVars.global.ssl_ctx = SSL_CTX_new(ssl_method);
if (GloVars.global.ssl_ctx==NULL) {
ERR_print_errors_fp(stderr);
proxy_error("Unable to initialize SSL. Shutting down...\n");
exit(EXIT_SUCCESS); // we exit gracefully to not be restarted
}
if (!SSL_CTX_set_min_proto_version(GloVars.global.ssl_ctx,TLS1_VERSION)) {
proxy_error("Unable to initialize SSL. SSL_set_min_proto_version failed. Shutting down...\n");
exit(EXIT_SUCCESS); // we exit gracefully to not be restarted
}
//SSL_CTX_set_options(GloVars.global.ssl_ctx, SSL_OP_NO_SSLv3); // no necessary, because of previous SSL_CTX_set_min_proto_version
#ifdef DEBUG
#if 0
{
STACK_OF(SSL_CIPHER) *ciphers;
ciphers = SSL_CTX_get_ciphers(GloVars.global.ssl_ctx);
fprintf(stderr,"List of cipher avaiable:\n");
if (ciphers) {
int num = sk_SSL_CIPHER_num(ciphers);
char buf[130];
for(int i = 0; i < num; i++){
const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(ciphers, i);
fprintf(stderr,"%s: %s", SSL_CIPHER_get_name(cipher), SSL_CIPHER_description(cipher, buf, 128));
}
}
fprintf(stderr,"\n");
}
#endif // 0
#endif // DEBUG
std::string msg = "";
ProxySQL_create_or_load_TLS(true, msg);
}
/*
void example_listern() {
// few examples tests to demonstrate the ability to add and remove listeners at runtime
GloMTH->listener_add((char *)"0.0.0.0:6033");
sleep(3);
GloMTH->listener_add((char *)"127.0.0.1:5033");
sleep(3);
GloMTH->listener_add((char *)"127.0.0.2:5033");
sleep(3);
GloMTH->listener_add((char *)"/tmp/proxysql.sock");
for (int t=0; t<10; t++) {
GloMTH->listener_add((char *)"127.0.0.1",7000+t);
sleep(3);
}
GloMTH->listener_del((char *)"0.0.0.0:6033");
sleep(3);
GloMTH->listener_del((char *)"127.0.0.1:5033");
sleep(3);
GloMTH->listener_del((char *)"127.0.0.2:5033");
sleep(3);
GloMTH->listener_del((char *)"/tmp/proxysql.sock");
}
*/
void * __qc;
void * __mysql_thread;
void * __mysql_threads_handler;
void * __query_processor;
//void * __mysql_auth;
using namespace std;
//__cmd_proxysql_config_file=NULL;
#define MAX_EVENTS 100
std::atomic<int> load_;
//__thread l_sfp *__thr_sfp=NULL;
//#ifdef DEBUG
//const char *malloc_conf = "xmalloc:true,lg_tcache_max:16,purge:decay,junk:true,tcache:false";
//#else
//const char *malloc_conf = "xmalloc:true,lg_tcache_max:16,purge:decay";
#ifndef __FreeBSD__
const char *malloc_conf = "xmalloc:true,lg_tcache_max:16,prof:true,prof_accum:true,prof_leak:true,lg_prof_sample:20,lg_prof_interval:30,prof_active:false";
#endif
//#endif /* DEBUG */
//const char *malloc_conf = "prof_leak:true,lg_prof_sample:0,prof_final:true,xmalloc:true,lg_tcache_max:16";
int listen_fd;
int socket_fd;
MySQL_Query_Cache *GloMyQC;
PgSQL_Query_Cache* GloPgQC;
MySQL_Authentication *GloMyAuth;
PgSQL_Authentication* GloPgAuth;
MySQL_LDAP_Authentication *GloMyLdapAuth;
#ifdef PROXYSQLCLICKHOUSE
ClickHouse_Authentication *GloClickHouseAuth;
#endif /* PROXYSQLCLICKHOUSE */
MySQL_Query_Processor* GloMyQPro;
PgSQL_Query_Processor* GloPgQPro;
ProxySQL_Admin *GloAdmin;
MySQL_Threads_Handler *GloMTH = NULL;
PgSQL_Threads_Handler* GloPTH = NULL;
Web_Interface *GloWebInterface;
MySQL_STMT_Manager_v14 *GloMyStmt;
MySQL_Monitor *GloMyMon;
PgSQL_Monitor *GloPgMon;
std::thread *MyMon_thread = NULL;
MySQL_Logger *GloMyLogger;
PgSQL_Logger* GloPgSQL_Logger;
MySQL_Variables mysql_variables;
PgSQL_Variables pgsql_variables;
SQLite3_Server *GloSQLite3Server;
#ifdef PROXYSQLCLICKHOUSE
ClickHouse_Server *GloClickHouseServer;
#endif /* PROXYSQLCLICKHOUSE */
ProxySQL_Cluster *GloProxyCluster = NULL;
ProxySQL_Statistics *GloProxyStats = NULL;
void * mysql_worker_thread_func(void *arg) {
// __thr_sfp=l_mem_init();
pthread_attr_t thread_attr;
size_t tmp_stack_size=0;
if (!pthread_attr_init(&thread_attr)) {
if (!pthread_attr_getstacksize(&thread_attr , &tmp_stack_size )) {
__sync_fetch_and_add(&GloVars.statuses.stack_memory_mysql_threads,tmp_stack_size);
}
}
proxysql_mysql_thread_t *mysql_thread=(proxysql_mysql_thread_t *)arg;
MySQL_Thread *worker = new MySQL_Thread();
mysql_thread->worker=worker;
worker->init();
// worker->poll_listener_add(listen_fd);
// worker->poll_listener_add(socket_fd);
load_ -= 1;
unsigned int iter = 0;
do { sleep_iter(++iter); } while (load_);
worker->run();
//delete worker;
delete worker;
mysql_thread->worker=NULL;
// l_mem_destroy(__thr_sfp);
__sync_fetch_and_sub(&GloVars.statuses.stack_memory_mysql_threads,tmp_stack_size);
return NULL;
}
#ifdef IDLE_THREADS
void * mysql_worker_thread_func_idles(void *arg) {
pthread_attr_t thread_attr;
size_t tmp_stack_size=0;
if (!pthread_attr_init(&thread_attr)) {
if (!pthread_attr_getstacksize(&thread_attr , &tmp_stack_size )) {
__sync_fetch_and_add(&GloVars.statuses.stack_memory_mysql_threads,tmp_stack_size);
}
}
// __thr_sfp=l_mem_init();
proxysql_mysql_thread_t *mysql_thread=(proxysql_mysql_thread_t *)arg;
MySQL_Thread *worker = new MySQL_Thread();
mysql_thread->worker=worker;
worker->epoll_thread=true;
worker->init();
// worker->poll_listener_add(listen_fd);
// worker->poll_listener_add(socket_fd);
load_ -= 1;
unsigned int iter = 0;
do { sleep_iter(++iter); } while (load_);
worker->run();
//delete worker;
delete worker;
// l_mem_destroy(__thr_sfp);
__sync_fetch_and_sub(&GloVars.statuses.stack_memory_mysql_threads,tmp_stack_size);
return NULL;
}
#endif // IDLE_THREADS
void* pgsql_worker_thread_func(void* arg) {
// __thr_sfp=l_mem_init();
pthread_attr_t thread_attr;
size_t tmp_stack_size = 0;
if (!pthread_attr_init(&thread_attr)) {
if (!pthread_attr_getstacksize(&thread_attr, &tmp_stack_size)) {
__sync_fetch_and_add(&GloVars.statuses.stack_memory_pgsql_threads, tmp_stack_size);
}
}
proxysql_pgsql_thread_t* pgsql_thread = (proxysql_pgsql_thread_t*)arg;
PgSQL_Thread* worker = new PgSQL_Thread();
pgsql_thread->worker = worker;
worker->init();
// worker->poll_listener_add(listen_fd);
// worker->poll_listener_add(socket_fd);
load_ -= 1;
unsigned int iter = 0;
do { sleep_iter(++iter); } while (load_);
worker->run();
//delete worker;
delete worker;
pgsql_thread->worker = NULL;
// l_mem_destroy(__thr_sfp);
__sync_fetch_and_sub(&GloVars.statuses.stack_memory_pgsql_threads, tmp_stack_size);
return NULL;
}
#ifdef IDLE_THREADS
void* pgsql_worker_thread_func_idles(void* arg) {
pthread_attr_t thread_attr;
size_t tmp_stack_size = 0;
if (!pthread_attr_init(&thread_attr)) {
if (!pthread_attr_getstacksize(&thread_attr, &tmp_stack_size)) {
__sync_fetch_and_add(&GloVars.statuses.stack_memory_pgsql_threads, tmp_stack_size);
}
}
// __thr_sfp=l_mem_init();
proxysql_pgsql_thread_t* pgsql_thread = (proxysql_pgsql_thread_t*)arg;
PgSQL_Thread* worker = new PgSQL_Thread();
pgsql_thread->worker = worker;
worker->epoll_thread = true;
worker->init();
// worker->poll_listener_add(listen_fd);
// worker->poll_listener_add(socket_fd);
load_ -= 1;
unsigned int iter = 0;
do { sleep_iter(++iter); } while (load_);
worker->run();
//delete worker;
delete worker;
// l_mem_destroy(__thr_sfp);
__sync_fetch_and_sub(&GloVars.statuses.stack_memory_pgsql_threads, tmp_stack_size);
return NULL;
}
#endif // IDLE_THREADS
void* unified_query_cache_purge_thread(void *arg) {
set_thread_name("QCPurgeThread");
MySQL_Thread* mysql_thr = new MySQL_Thread();
unsigned int MySQL_Monitor__thread_MySQL_Thread_Variables_version;
MySQL_Monitor__thread_MySQL_Thread_Variables_version = GloMTH->get_global_version();
mysql_thr->refresh_variables();
uint64_t mysql_max_memory_size = static_cast<uint64_t>(mysql_thread___query_cache_size_MB * 1024ULL * 1024ULL);
PgSQL_Thread* pgsql_thr = new PgSQL_Thread();
unsigned int PgSQL_Monitor__thread_PgSQL_Thread_Variables_version;
PgSQL_Monitor__thread_PgSQL_Thread_Variables_version = GloPTH->get_global_version();
pgsql_thr->refresh_variables();
uint64_t pgsql_max_memory_size = static_cast<uint64_t>(pgsql_thread___query_cache_size_MB * 1024ULL * 1024ULL);
// Both MySQL and PgSQL query caches use the same shutting_down value
while (GloMyQC->shutting_down == false && GloPgQC->shutting_down == false) {
// Both MySQL and PgSQL query caches share the same purge_loop_time value.
// Therefore, using either purge_loop_time will have no impact on the behavior.
usleep(GloMyQC->purge_loop_time);
const unsigned int mysql_glover = GloMTH->get_global_version();
if (MySQL_Monitor__thread_MySQL_Thread_Variables_version < mysql_glover) {
MySQL_Monitor__thread_MySQL_Thread_Variables_version = mysql_glover;
mysql_thr->refresh_variables();
mysql_max_memory_size = static_cast<uint64_t>(mysql_thread___query_cache_size_MB * 1024ULL * 1024ULL);
}
GloMyQC->purgeHash(mysql_max_memory_size);
const unsigned int pgsql_glover = GloPTH->get_global_version();
if (PgSQL_Monitor__thread_PgSQL_Thread_Variables_version < pgsql_glover) {
PgSQL_Monitor__thread_PgSQL_Thread_Variables_version = pgsql_glover;
pgsql_thr->refresh_variables();
pgsql_max_memory_size = static_cast<uint64_t>(pgsql_thread___query_cache_size_MB * 1024ULL * 1024ULL);
}
GloPgQC->purgeHash(pgsql_max_memory_size);
}
delete mysql_thr;
delete pgsql_thr;
return NULL;
}
/*void* pgsql_shared_query_cache_funct(void* arg) {
GloPgQC->purgeHash_thread(NULL);
return NULL;
}*/
void ProxySQL_Main_process_global_variables(int argc, const char **argv) {
GloVars.errorlog = NULL;
GloVars.pid = NULL;
GloVars.parse(argc,argv);
GloVars.process_opts_pre();
GloVars.restart_on_missing_heartbeats = 10; // default
// alwasy try to open a config file
if (GloVars.confFile->OpenFile(GloVars.config_file) == true) {
GloVars.configfile_open=true;
proxy_info("Using config file %s\n", GloVars.config_file);
const Setting& root = GloVars.confFile->cfg.getRoot();
if (root.exists("restart_on_missing_heartbeats")==true) {
// restart_on_missing_heartbeats datadir from config file
int restart_on_missing_heartbeats;
bool rc;
rc=root.lookupValue("restart_on_missing_heartbeats", restart_on_missing_heartbeats);
if (rc==true) {
GloVars.restart_on_missing_heartbeats=restart_on_missing_heartbeats;
}
}
if (root.exists("execute_on_exit_failure")==true) {
// restart_on_missing_heartbeats datadir from config file
string execute_on_exit_failure;
bool rc;
rc=root.lookupValue("execute_on_exit_failure", execute_on_exit_failure);
if (rc==true) {
GloVars.execute_on_exit_failure=strdup(execute_on_exit_failure.c_str());
}
}
if (root.exists("errorlog")==true) {
// restart_on_missing_heartbeats datadir from config file
string errorlog_path;
bool rc;
rc=root.lookupValue("errorlog", errorlog_path);
if (rc==true) {
GloVars.errorlog = strdup(errorlog_path.c_str());
}
}
if (root.exists("uuid")==true) {
string uuid;
bool rc;
rc=root.lookupValue("uuid", uuid);
if (rc==true) {
uuid_t uu;
if (uuid_parse(uuid.c_str(), uu)==0) {
if (GloVars.uuid == NULL) {
// it is not set yet, that means it wasn't specified on the cmdline
GloVars.uuid = strdup(uuid.c_str());
}
} else {
proxy_error("The config file is configured with an invalid UUID: %s\n", uuid.c_str());
}
}
}
// if cluster_sync_interfaces is true, interfaces variables are synced too
if (root.exists("cluster_sync_interfaces")==true) {
bool value_bool;
bool rc;
rc=root.lookupValue("cluster_sync_interfaces", value_bool);
if (rc==true) {
GloVars.cluster_sync_interfaces=value_bool;
} else {
proxy_error("The config file is configured with an invalid cluster_sync_interfaces\n");
}
}
if (root.exists("set_thread_name")==true) {
bool value_bool;
bool rc;
rc=root.lookupValue("set_thread_name", value_bool);
if (rc==true) {
GloVars.set_thread_name=value_bool;
} else {
proxy_error("The config file is configured with an invalid set_thread_name\n");
}
}
if (root.exists("pidfile")==true) {
string pidfile_path;
bool rc;
rc=root.lookupValue("pidfile", pidfile_path);
if (rc==true) {
GloVars.pid = strdup(pidfile_path.c_str());
}
}
if (root.exists("sqlite3_plugin")==true) {
string sqlite3_plugin;
bool rc;
rc=root.lookupValue("sqlite3_plugin", sqlite3_plugin);
if (rc==true) {
GloVars.sqlite3_plugin=strdup(sqlite3_plugin.c_str());
}
}
if (root.exists("web_interface_plugin")==true) {
string web_interface_plugin;
bool rc;
rc=root.lookupValue("web_interface_plugin", web_interface_plugin);
if (rc==true) {
GloVars.web_interface_plugin=strdup(web_interface_plugin.c_str());
}
}
if (root.exists("ldap_auth_plugin")==true) {
string ldap_auth_plugin;
bool rc;
rc=root.lookupValue("ldap_auth_plugin", ldap_auth_plugin);
if (rc==true) {
GloVars.ldap_auth_plugin=strdup(ldap_auth_plugin.c_str());
}
}
const map<string, char**> varnames_globals_map {
{ "mysql-ssl_p2s_ca", &GloVars.global.gr_bootstrap_ssl_ca },
{ "mysql-ssl_p2s_capath", &GloVars.global.gr_bootstrap_ssl_capath },
{ "mysql-ssl_p2s_cert", &GloVars.global.gr_bootstrap_ssl_cert },
{ "mysql-ssl_p2s_key", &GloVars.global.gr_bootstrap_ssl_key },
{ "mysql-ssl_p2s_cipher", &GloVars.global.gr_bootstrap_ssl_cipher },
{ "mysql-ssl_p2s_crl", &GloVars.global.gr_bootstrap_ssl_crl },
{ "mysql-ssl_p2s_crlpath", &GloVars.global.gr_bootstrap_ssl_crlpath }
};
// Command line options always take precedence
if (GloVars.global.gr_bootstrap_mode && root.exists("mysql_variables")) {
const Setting& mysql_vars = root["mysql_variables"];
for (const pair<const string,char**>& name_global : varnames_globals_map) {
for (const auto& setting_it : mysql_vars) {
if (*name_global.second == nullptr) {
if (setting_it.getName() == name_global.first && setting_it.isString()) {
const char* setting_val = setting_it.c_str();
*name_global.second = strdup(setting_val);
}
}
}
}
}
} else {
proxy_warning("Unable to open config file %s\n", GloVars.config_file); // issue #705
if (GloVars.__cmd_proxysql_config_file) {
proxy_error("Unable to open config file %s specified in the command line. Aborting!\n", GloVars.config_file);
exit(EXIT_SUCCESS); // we exit gracefully to avoid restart
}
}
char *t=getcwd(NULL, 512);
if (GloVars.__cmd_proxysql_datadir==NULL) {
// datadir was not specified , try to read config file
if (GloVars.configfile_open==true) {
const Setting& root = GloVars.confFile->cfg.getRoot();
if (root.exists("datadir")==true) {
// reading datadir from config file
std::string datadir;
bool rc;
rc=root.lookupValue("datadir", datadir);
if (rc==true) {
GloVars.datadir=strdup(datadir.c_str());
} else {
GloVars.datadir=strdup(t);
}
} else {
// datadir was not specified in config file
GloVars.datadir=strdup(t);
}
if (root.exists("restart_on_missing_heartbeats")==true) {
// restart_on_missing_heartbeats datadir from config file
int restart_on_missing_heartbeats;
bool rc;
rc=root.lookupValue("restart_on_missing_heartbeats", restart_on_missing_heartbeats);
if (rc==true) {
GloVars.restart_on_missing_heartbeats=restart_on_missing_heartbeats;
} else {
GloVars.restart_on_missing_heartbeats = 10; // default
}
} else {
// restart_on_missing_heartbeats was not specified in config file
GloVars.restart_on_missing_heartbeats = 10; // default
}
} else {
// config file not readable
GloVars.datadir=strdup(t);
std::cerr << "[Warning]: Cannot open any default config file . Using default datadir in current working directory " << GloVars.datadir << endl;
}
} else {
GloVars.datadir=GloVars.__cmd_proxysql_datadir;
}
free(t);
GloVars.admindb=(char *)malloc(strlen(GloVars.datadir)+strlen((char *)"proxysql.db")+2);
sprintf(GloVars.admindb,"%s/%s",GloVars.datadir, (char *)"proxysql.db");
GloVars.sqlite3serverdb=(char *)malloc(strlen(GloVars.datadir)+strlen((char *)"sqlite3server.db")+2);
sprintf(GloVars.sqlite3serverdb,"%s/%s",GloVars.datadir, (char *)"sqlite3server.db");
GloVars.statsdb_disk=(char *)malloc(strlen(GloVars.datadir)+strlen((char *)"proxysql_stats.db")+2);
sprintf(GloVars.statsdb_disk,"%s/%s",GloVars.datadir, (char *)"proxysql_stats.db");
if (GloVars.errorlog == NULL) {
GloVars.errorlog=(char *)malloc(strlen(GloVars.datadir)+strlen((char *)"proxysql.log")+2);
sprintf(GloVars.errorlog,"%s/%s",GloVars.datadir, (char *)"proxysql.log");
}
if (GloVars.pid == NULL) {
GloVars.pid=(char *)malloc(strlen(GloVars.datadir)+strlen((char *)"proxysql.pid")+2);
sprintf(GloVars.pid,"%s/%s",GloVars.datadir, (char *)"proxysql.pid");
}
if (GloVars.__cmd_proxysql_initial==true) {
std::cerr << "Renaming database file " << GloVars.admindb << endl;
char *newpath=(char *)malloc(strlen(GloVars.admindb)+8);
sprintf(newpath,"%s.bak",GloVars.admindb);
rename(GloVars.admindb,newpath); // FIXME: should we check return value, or ignore whatever it successed or not?
}
GloVars.confFile->ReadGlobals();
GloVars.process_opts_post();
}
void ProxySQL_Main_init_main_modules() {
GloMyQC=NULL;
GloPgQC=NULL;
GloMyQPro=NULL;
GloMTH=NULL;
GloMyAuth=NULL;
GloPgAuth=NULL;
GloPTH=NULL;
#ifdef PROXYSQLCLICKHOUSE
GloClickHouseAuth=NULL;
#endif /* PROXYSQLCLICKHOUSE */
GloMyMon=NULL;
GloMyLogger=NULL;
GloPgSQL_Logger = NULL;
GloMyStmt=NULL;
// initialize libev
if (!ev_default_loop (EVBACKEND_POLL | EVFLAG_NOENV)) {
fprintf(stderr,"could not initialise libev");
exit(EXIT_FAILURE);
}
MyHGM=new MySQL_HostGroups_Manager();
MyHGM->init();
MySQL_Threads_Handler * _tmp_GloMTH = NULL;
_tmp_GloMTH=new MySQL_Threads_Handler();
GloMTH = _tmp_GloMTH;
GloMyLogger = new MySQL_Logger();
GloMyLogger->print_version();
GloPgSQL_Logger = new PgSQL_Logger();
GloPgSQL_Logger->print_version();
GloMyStmt=new MySQL_STMT_Manager_v14();
PgHGM = new PgSQL_HostGroups_Manager();
PgHGM->init();
PgSQL_Threads_Handler* _tmp_GloPTH = NULL;
_tmp_GloPTH = new PgSQL_Threads_Handler();
GloPTH = _tmp_GloPTH;
}
void ProxySQL_Main_init_Admin_module(const bootstrap_info_t& bootstrap_info) {
// cluster module needs to be initialized before
GloProxyCluster = new ProxySQL_Cluster();
GloProxyCluster->init();
GloProxyCluster->print_version();
GloProxyStats = new ProxySQL_Statistics();
//GloProxyStats->init();
GloProxyStats->print_version();
GloAdmin = new ProxySQL_Admin();
GloAdmin->init(bootstrap_info);
GloAdmin->print_version();
if (binary_sha1) {
proxy_info("ProxySQL SHA1 checksum: %s\n", binary_sha1);
}
}
void ProxySQL_Main_init_Auth_module() {
GloMyAuth = new MySQL_Authentication();
GloMyAuth->print_version();
GloPgAuth = new PgSQL_Authentication();
GloPgAuth->print_version();
GloAdmin->init_users();
GloAdmin->init_pgsql_users();
//GloMyLdapAuth = create_MySQL_LDAP_Authentication();
if (GloMyLdapAuth) {
GloMyLdapAuth->print_version();
}
}
void ProxySQL_Main_init_Query_module() {
GloMyQPro = new MySQL_Query_Processor();
GloMyQPro->print_version();
GloPgQPro = new PgSQL_Query_Processor();
GloPgQPro->print_version();
GloAdmin->init_mysql_query_rules();
GloAdmin->init_mysql_firewall();
GloAdmin->init_pgsql_query_rules();
GloAdmin->init_pgsql_firewall();
// if (GloWebInterface) {
// GloWebInterface->print_version();
// }
}
void ProxySQL_Main_init_MySQL_Threads_Handler_module() {
unsigned int i;
GloMTH->init();
load_ = 1;
load_ += GloMTH->num_threads;
#ifdef IDLE_THREADS
if (GloVars.global.idle_threads) {
load_ += GloMTH->num_threads;
} else {
proxy_warning("proxysql instance running without --idle-threads : most workloads benefit from this option\n");
proxy_warning("proxysql instance running without --idle-threads : enabling it can potentially improve performance\n");
}
#endif // IDLE_THREADS
for (i=0; i<GloMTH->num_threads; i++) {
GloMTH->create_thread(i,mysql_worker_thread_func, false);
#ifdef IDLE_THREADS
if (GloVars.global.idle_threads) {
GloMTH->create_thread(i,mysql_worker_thread_func_idles, true);
}
#endif // IDLE_THREADS
}
}
void ProxySQL_Main_init_PgSQL_Threads_Handler_module() {
unsigned int i;
GloPTH->init();
//load_ = 1;
load_ += GloPTH->num_threads;
#ifdef IDLE_THREADS
if (GloVars.global.idle_threads) {
load_ += GloPTH->num_threads;
}
else {
proxy_warning("proxysql instance running without --idle-threads : most workloads benefit from this option\n");
proxy_warning("proxysql instance running without --idle-threads : enabling it can potentially improve performance\n");
}
#endif // IDLE_THREADS
for (i = 0; i < GloPTH->num_threads; i++) {
GloPTH->create_thread(i, pgsql_worker_thread_func, false);
#ifdef IDLE_THREADS
if (GloVars.global.idle_threads) {
GloPTH->create_thread(i, pgsql_worker_thread_func_idles, true);
}
#endif // IDLE_THREADS
}
}
void ProxySQL_Main_init_Query_Cache_module() {
GloMyQC = new MySQL_Query_Cache();
GloMyQC->print_version();
GloPgQC = new PgSQL_Query_Cache();
GloPgQC->print_version();
pthread_t purge_thread_id;
pthread_create(&purge_thread_id, NULL, unified_query_cache_purge_thread, NULL);
GloMyQC->purge_thread_id = purge_thread_id;
GloPgQC->purge_thread_id = purge_thread_id;
}
void ProxySQL_Main_init_MySQL_Monitor_module() {
// start MySQL_Monitor
// GloMyMon = new MySQL_Monitor();
if (MyMon_thread == NULL) { // only if not created yet
MyMon_thread = new std::thread(&MySQL_Monitor::run,GloMyMon);
GloMyMon->print_version();
}
}
void ProxySQL_Main_init_SQLite3Server() {
// start SQLite3Server
GloSQLite3Server = new SQLite3_Server();
GloSQLite3Server->init();
// NOTE: Always perform the 'load_*_to_runtime' after module start, otherwise values won't be properly
// loaded from disk at ProxySQL startup.
GloAdmin->load_sqliteserver_variables_to_runtime();
GloAdmin->init_sqliteserver_variables();
GloSQLite3Server->print_version();
}
#ifdef PROXYSQLCLICKHOUSE
void ProxySQL_Main_init_ClickHouseServer() {
// start SQServer
GloClickHouseServer = new ClickHouse_Server();
GloClickHouseServer->init();
// NOTE: Always perform the 'load_*_to_runtime' after module start, otherwise values won't be properly
// loaded from disk at ProxySQL startup.
GloAdmin->load_clickhouse_variables_to_runtime();
GloAdmin->init_clickhouse_variables();
GloClickHouseServer->print_version();
GloClickHouseAuth = new ClickHouse_Authentication();
GloClickHouseAuth->print_version();
GloAdmin->init_clickhouse_users();
}
#endif /* PROXYSQLCLICKHOUSE */
void ProxySQL_Main_join_all_threads() {
cpu_timer t;
if (GloMTH) {
cpu_timer t;
GloMTH->shutdown_threads();
#ifdef DEBUG
std::cerr << "GloMTH joined in ";
#endif
}
if (GloPTH) {
cpu_timer t;
GloPTH->shutdown_threads();
#ifdef DEBUG
std::cerr << "GloPTH joined in ";
#endif
}
if (GloMyQC) {
GloMyQC->shutting_down=true;
}
if (GloPgQC) {
GloPgQC->shutting_down=true;
}
if (GloMyMon) {
GloMyMon->shutdown=true;
}
if (GloPgMon) {
GloPgMon->shutdown=true;
}
// join GloMyMon thread
if (GloMyMon && MyMon_thread) {
cpu_timer t;
MyMon_thread->join();
delete MyMon_thread;
MyMon_thread = NULL;
#ifdef DEBUG
std::cerr << "GloMyMon joined in ";
#endif
}
if (GloPgMon && pgsql_monitor_thread) {
cpu_timer t;
pgsql_monitor_thread->join();
delete pgsql_monitor_thread;
pgsql_monitor_thread = NULL;
#ifdef DEBUG
std::cerr << "GloPgMon joined in ";
#endif
}
/* Unified QC Purge Thread for both MySQL and PgSQL query cache
// join GloMyQC thread
if (GloMyQC) {
cpu_timer t;
pthread_join(GloMyQC->purge_thread_id, NULL);
#ifdef DEBUG
std::cerr << "GloMyQC joined in ";
#endif
}
// join GloPgQC thread
if (GloPgQC) {
cpu_timer t;
pthread_join(GloPgQC->purge_thread_id, NULL);
#ifdef DEBUG
std::cerr << "GloPgQC joined in ";
#endif
}*/
if (GloMyQC || GloPgQC) {
cpu_timer t;
// The purge_thread_id is shared by both MySQL and PgSQL.
// use either one to join the thread.
pthread_join(GloMyQC->purge_thread_id, NULL);
#ifdef DEBUG
std::cerr << "GloMyQC and GloPgQC joined in ";
#endif
}
#ifdef DEBUG
std::cerr << "All threads joined in ";
#endif
}
void ProxySQL_Main_shutdown_all_modules() {
if (GloMyMon) {
cpu_timer t;
delete GloMyMon;
GloMyMon=NULL;
#ifdef DEBUG
std::cerr << "GloMyMon shutdown in ";
#endif
}
if (GloPgMon) {
cpu_timer t;
delete GloPgMon;
GloPgMon=NULL;
#ifdef DEBUG
std::cerr << "GloPgMon shutdown in ";
#endif
}
if (GloMyQC) {
cpu_timer t;
delete GloMyQC;
GloMyQC=NULL;
#ifdef DEBUG
std::cerr << "GloMyQC shutdown in ";
#endif
}
if (GloPgQC) {
cpu_timer t;
delete GloPgQC;
GloPgQC=NULL;
#ifdef DEBUG
std::cerr << "GloPgQC shutdown in ";
#endif
}
if (GloMyQPro) {
cpu_timer t;
delete GloMyQPro;
GloMyQPro=NULL;
#ifdef DEBUG
std::cerr << "GloMyQPro shutdown in ";
#endif
}
if (GloPgQPro) {
cpu_timer t;
delete GloPgQPro;
GloPgQPro=NULL;
#ifdef DEBUG
std::cerr << "GloPgQPro shutdown in ";
#endif
}
#ifdef PROXYSQLCLICKHOUSE
if (GloClickHouseAuth) {
cpu_timer t;
delete GloClickHouseAuth;
GloClickHouseAuth=NULL;
#ifdef DEBUG
std::cerr << "GloClickHouseAuth shutdown in ";
#endif
}
if (GloClickHouseServer) {
cpu_timer t;
delete GloClickHouseServer;
GloClickHouseServer=NULL;
#ifdef DEBUG
std::cerr << "GloClickHouseServer shutdown in ";
#endif
}
#endif /* PROXYSQLCLICKHOUSE */
if (GloSQLite3Server) {
cpu_timer t;
delete GloSQLite3Server;
GloSQLite3Server=NULL;
#ifdef DEBUG
std::cerr << "GloSQLite3Server shutdown in ";
#endif
}
if (GloMyAuth) {
cpu_timer t;
delete GloMyAuth;
GloMyAuth=NULL;
#ifdef DEBUG
std::cerr << "GloMyAuth shutdown in ";
#endif
}
if (GloPgAuth) {
cpu_timer t;
delete GloPgAuth;
GloPgAuth = NULL;
#ifdef DEBUG
std::cerr << "GloPgAuth shutdown in ";
#endif
}
if (GloMTH) {
cpu_timer t;
pthread_mutex_lock(&GloVars.global.ext_glomth_mutex);
delete GloMTH;
GloMTH=NULL;
pthread_mutex_unlock(&GloVars.global.ext_glomth_mutex);
#ifdef DEBUG
std::cerr << "GloMTH shutdown in ";
#endif
}
if (GloMyLogger) {
cpu_timer t;
delete GloMyLogger;
GloMyLogger=NULL;
#ifdef DEBUG
std::cerr << "GloMyLogger shutdown in ";
#endif
}
if (GloPgSQL_Logger) {
cpu_timer t;
delete GloPgSQL_Logger;
GloPgSQL_Logger = NULL;
#ifdef DEBUG
std::cerr << "GloPgSQL_Logger shutdown in ";
#endif
}
{
#ifdef TEST_WITHASAN
pthread_mutex_lock(&GloAdmin->sql_query_global_mutex);
#endif
cpu_timer t;
delete GloAdmin;
#ifdef DEBUG
std::cerr << "GloAdmin shutdown in ";
#endif
}
{
cpu_timer t;
MyHGM->shutdown();
delete MyHGM;
#ifdef DEBUG
std::cerr << "GloHGM shutdown in ";
#endif
}
if (GloMyStmt) {
delete GloMyStmt;
GloMyStmt=NULL;
}
}
void ProxySQL_Main_init() {
#ifdef DEBUG
GloVars.global.gdbg=false;
glovars.has_debug=true;
#else
glovars.has_debug=false;
#endif /* DEBUG */
// __thr_sfp=l_mem_init();
proxysql_init_debug_prometheus_metrics();
}
static void LoadPlugins() {
GloMyLdapAuth = NULL;
if (proxy_sqlite3_open_v2 == nullptr) {
SQLite3DB::LoadPlugin(GloVars.sqlite3_plugin);
}
if (GloVars.web_interface_plugin) {
dlerror();
char * dlsym_error = NULL;
dlerror();
dlsym_error=NULL;
__web_interface = dlopen(GloVars.web_interface_plugin, RTLD_NOW);
if (!__web_interface) {
cerr << "Cannot load library: " << dlerror() << '\n';
exit(EXIT_FAILURE);
} else {
dlerror();
create_Web_Interface = (create_Web_Interface_t *) dlsym(__web_interface, "create_Web_Interface_func");
dlsym_error = dlerror();
if (dlsym_error!=NULL) {
cerr << "Cannot load symbol create_Web_Interface: " << dlsym_error << '\n';
exit(EXIT_FAILURE);
}
}
if (__web_interface==NULL || dlsym_error) {
proxy_error("Unable to load Web_Interface from %s\n", GloVars.web_interface_plugin);
exit(EXIT_FAILURE);
} else {
GloWebInterface = create_Web_Interface();
if (GloWebInterface) {
//GloAdmin->init_WebInterfacePlugin();
//GloAdmin->load_ldap_variables_to_runtime();
} else {
proxy_error("Failed to load 'Web_Interface' plugin\n");
}
}
}
if (GloVars.ldap_auth_plugin) {
dlerror();
char * dlsym_error = NULL;
dlerror();
dlsym_error=NULL;
__mysql_ldap_auth = dlopen(GloVars.ldap_auth_plugin, RTLD_NOW);
if (!__mysql_ldap_auth) {
cerr << "Cannot load library: " << dlerror() << '\n';
exit(EXIT_FAILURE);
} else {
dlerror();
create_MySQL_LDAP_Authentication = (create_MySQL_LDAP_Authentication_t *) dlsym(__mysql_ldap_auth, "create_MySQL_LDAP_Authentication_func");
dlsym_error = dlerror();
if (dlsym_error!=NULL) {
cerr << "Cannot load symbol create_MySQL_LDAP_Authentication: " << dlsym_error << '\n';
exit(EXIT_FAILURE);
}
}
if (__mysql_ldap_auth==NULL || dlsym_error) {
proxy_error("Unable to load MySQL_LDAP_Authentication from %s\n", GloVars.ldap_auth_plugin);
exit(EXIT_FAILURE);
} else {
GloMyLdapAuth = create_MySQL_LDAP_Authentication();
if (!GloMyLdapAuth) {
proxy_error("Failed to load 'MySQL_LDAP_Authentication' plugin\n");
}
// we are removing this from here, and copying in
// ProxySQL_Main_init_phase2___not_started
// the keep record of these two lines to make sure we don't
// do a similar mistakes with other plugins
//
//if (GloMyLdapAuth) {
// GloAdmin->init_ldap();
// GloAdmin->load_ldap_variables_to_runtime();
//}
}
}
}
/**
* @brief Unloads all the plugins that hold some resources that
* need to be deallocated.
*/
void UnloadPlugins() {
if (GloWebInterface) {
GloWebInterface->stop();
}
}
void ProxySQL_Main_init_phase2___not_started(const bootstrap_info_t& boostrap_info) {
std::string msg;
ProxySQL_create_or_load_TLS(false, msg);
LoadPlugins();
ProxySQL_Main_init_main_modules();
ProxySQL_Main_init_Admin_module(boostrap_info);
GloMTH->print_version();
{
cpu_timer t;
GloMyLogger->events_set_datadir(GloVars.datadir);
GloMyLogger->audit_set_datadir(GloVars.datadir);
#ifdef DEBUG
std::cerr << "Main phase3 : GloMyLogger initialized in ";
#endif
}
{
cpu_timer t;
GloPgSQL_Logger->events_set_datadir(GloVars.datadir);
GloPgSQL_Logger->audit_set_datadir(GloVars.datadir);
#ifdef DEBUG
std::cerr << "Main phase3 : GloPgSQL_Logger initialized in ";
#endif
}
if (GloVars.configfile_open) {
GloVars.confFile->CloseFile();
}
if (GloMyLdapAuth) {
GloAdmin->load_ldap_variables_to_runtime();
}
ProxySQL_Main_init_Auth_module();
if (GloVars.global.nostart) {
pthread_mutex_lock(&GloVars.global.start_mutex);
}
}
void ProxySQL_Main_init_phase3___start_all() {
{
cpu_timer t;
GloMyLogger->events_set_datadir(GloVars.datadir);
GloMyLogger->audit_set_datadir(GloVars.datadir);
#ifdef DEBUG
std::cerr << "Main phase3 : GloMyLogger initialized in ";
#endif
}
{
cpu_timer t;
GloPgSQL_Logger->events_set_datadir(GloVars.datadir);
GloPgSQL_Logger->audit_set_datadir(GloVars.datadir);
#ifdef DEBUG
std::cerr << "Main phase3 : GloPgSQL_Logger initialized in ";
#endif
}
// Initialized monitor, no matter if it will be started or not
GloMyMon = new MySQL_Monitor();
GloPgMon = new PgSQL_Monitor();
// load all mysql servers to GloHGH
{
cpu_timer t;
GloAdmin->init_mysql_servers();
GloAdmin->init_pgsql_servers();
GloAdmin->init_proxysql_servers();
GloAdmin->load_scheduler_to_runtime();
#ifdef DEBUG
std::cerr << "Main phase3 : GloAdmin initialized in ";
#endif
}
{
cpu_timer t;
ProxySQL_Main_init_Query_module();
#ifdef DEBUG
std::cerr << "Main phase3 : Query Processor initialized in ";
#endif
}
{
cpu_timer t;
ProxySQL_Main_init_MySQL_Threads_Handler_module();
#ifdef DEBUG
std::cerr << "Main phase3 : MySQL Threads Handler initialized in ";
#endif
}
{
cpu_timer t;
ProxySQL_Main_init_PgSQL_Threads_Handler_module();
#ifdef DEBUG
std::cerr << "Main phase3 : PgSQL Threads Handler initialized in ";
#endif
}
{
cpu_timer t;
ProxySQL_Main_init_Query_Cache_module();
#ifdef DEBUG
std::cerr << "Main phase3 : Query Cache initialized in ";
#endif
}
unsigned int iter = 0;
do { sleep_iter(++iter); } while (load_ != 1);
load_ = 0;
__sync_fetch_and_add(&GloMTH->status_variables.threads_initialized, 1);
__sync_fetch_and_add(&GloPTH->status_variables.threads_initialized, 1);
{
cpu_timer t;
GloMTH->start_listeners();
#ifdef DEBUG
std::cerr << "Main phase3 : MySQL Threads Handler listeners started in ";
#endif
}
{
cpu_timer t;
GloPTH->start_listeners();
#ifdef DEBUG
std::cerr << "Main phase3 : PgSQL Threads Handler listeners started in ";
#endif
}
if ( GloVars.global.sqlite3_server == true ) {
cpu_timer t;
ProxySQL_Main_init_SQLite3Server();
sleep(1);
#ifdef DEBUG
std::cerr << "Main phase3 : SQLite3 Server initialized in ";
#endif
}
if (GloVars.global.my_monitor==true)
{
cpu_timer t;
ProxySQL_Main_init_MySQL_Monitor_module();
#ifdef DEBUG
std::cerr << "Main phase3 : MySQL Monitor initialized in ";
#endif
}
if (GloVars.global.pg_monitor==true)
{
cpu_timer t;
pgsql_monitor_thread = new std::thread(&PgSQL_monitor_scheduler_thread);
#ifdef DEBUG
std::cerr << "Main phase3 : PgSQL Monitor initialized in ";
#endif
}
#ifdef PROXYSQLCLICKHOUSE
if ( GloVars.global.clickhouse_server == true ) {
cpu_timer t;
ProxySQL_Main_init_ClickHouseServer();
#ifdef DEBUG
std::cerr << "Main phase3 : ClickHouse Server initialized in ";
#endif
}
#endif /* PROXYSQLCLICKHOUSE */
// LDAP
if (GloMyLdapAuth) {
GloAdmin->init_ldap_variables();
}
// HTTP Server should be initialized after other modules. See #4510
GloAdmin->init_http_server();
GloAdmin->proxysql_restapi().load_restapi_to_runtime();
// Signal ProxySQL_Admin that all modules have been started
GloAdmin->all_modules_started = true;
// Load the config not previously loaded for these modules
GloAdmin->load_http_server();
GloAdmin->load_restapi_server();
}
void ProxySQL_Main_init_phase4___shutdown() {
cpu_timer t;
ProxySQL_Main_join_all_threads();
//write(GloAdmin->pipefd[1], &GloAdmin->pipefd[1], 1); // write a random byte
if (GloVars.global.nostart) {
pthread_mutex_unlock(&GloVars.global.start_mutex);
}
ProxySQL_Main_shutdown_all_modules();
#ifdef DEBUG
std::cerr << "Main init phase4 shutdown completed in ";
#endif
}
/**
* @brief Phase 1 of the daemonization process for ProxySQL.
*
* This function performs the first phase of the daemonization process for ProxySQL. It sets up essential parameters
* and checks for conditions necessary for daemonization. If any of the conditions are not met or if an error occurs,
* the function logs an error message and exits with a failure status code.
*
* @param argv0 The name of the executable file used to start ProxySQL.
* @return void.
* @note This function does not return if an error occurs; it exits the process.
*/
void ProxySQL_daemonize_phase1(char *argv0) {
int rc; // Variable to store the return code of system calls
// Set the PID file identification to the global PID variable
daemon_pid_file_ident=GloVars.pid;
// Set the log identification based on the executable file name
daemon_log_ident=daemon_ident_from_argv0(argv0);
// Change the current working directory to the data directory
rc=chdir(GloVars.datadir);
if (rc) {
// Log an error message if changing the directory fails and exit with failure status
daemon_log(LOG_ERR, "Could not chdir into datadir: %s . Error: %s", GloVars.datadir, strerror(errno));
exit(EXIT_FAILURE);
}
// Set the PID file process to the ProxySQL PID file
daemon_pid_file_proc = proxysql_pid_file;
// Check if ProxySQL is already running by checking the PID file
pid=daemon_pid_file_is_running();
if (pid>=0) {
// Log an error message if ProxySQL is already running and exit with failure status
daemon_log(LOG_ERR, "Daemon already running on PID file %u", pid);
exit(EXIT_FAILURE);
}
if (daemon_retval_init() < 0) {
// Initialize the return value for daemonization; log an error if initialization fails
daemon_log(LOG_ERR, "Failed to create pipe.");
exit(EXIT_FAILURE);
}
}
/**
* @brief Wait for the return value from the daemon process.
*
* This function waits for the return value from the daemon process for a specified duration. If the return value is
* received within the specified time, the function logs the return value. If an error occurs during the waiting process,
* the function logs an error message and exits with a failure status code.
*
* @return void.
* @note This function does not return if an error occurs; it exits the process.
*/
void ProxySQL_daemonize_wait_daemon() {
int ret; // Variable to store the return value from daemon_retval_wait()
// Wait for 20 seconds for the return value passed from the daemon process
if ((ret = daemon_retval_wait(20)) < 0) {
// Log an error message if waiting for the return value fails and exit with failure status
daemon_log(LOG_ERR, "Could not receive return value from daemon process: %s", strerror(errno));
exit(EXIT_FAILURE);
}
// If a return value is received, log it
if (ret) {
daemon_log(LOG_ERR, "Daemon returned %i as return value.", ret);
}
// Exit with the return value received from the daemon process
exit(ret);
}
bool ProxySQL_daemonize_phase2() {
int rc;
/*
// we DO NOT close FDs anymore. See:
// https://github.com/sysown/proxysql/issues/2628
//
// Close FDs
if (daemon_close_all(-1) < 0) {
daemon_log(LOG_ERR, "Failed to close all file descriptors: %s", strerror(errno));
// Send the error condition to the parent process
daemon_retval_send(1);
return false;
}
*/
rc=chdir(GloVars.datadir);
if (rc) {
daemon_log(LOG_ERR, "Could not chdir into datadir: %s . Error: %s", GloVars.datadir, strerror(errno));
exit(EXIT_FAILURE);
}
/* Create the PID file */
if (daemon_pid_file_create() < 0) {
daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno));
daemon_retval_send(2);
return false;
}
/* Send OK to parent process */
daemon_retval_send(0);
GloAdmin->flush_error_log();
//daemon_log(LOG_INFO, "Starting ProxySQL\n");
//daemon_log(LOG_INFO, "Sucessfully started");
proxy_info("Starting ProxySQL\n");
proxy_info("Successfully started\n");
return true;
}
/**
* @brief Calls an external script upon exit failure.
*
* This function attempts to execute an external script specified in the global variable `GloVars.execute_on_exit_failure`
* if the program exits due to failure. It first checks if the variable is set, and if not, returns without further action.
* If the variable is set, it attempts to fork a child process to execute the script. If forking fails, the function exits
* with failure status. If forking succeeds, the child process attempts to execute the script using the `system` function.
* If the script execution fails, an error message is logged, and the child process exits with failure status. Otherwise, the
* child process exits with success status. Additionally, the function creates a detached thread to wait for the child process
* to exit, ensuring that the parent process does not block. If thread creation fails, the function logs an error message and
* exits with failure status.
*
* @return void.
* @note This function does not return if an error occurs; it exits the process.
*/
void call_execute_on_exit_failure() {
// Log a message indicating the attempt to call the external script
proxy_info("Trying to call external script after exit failure: %s\n", GloVars.execute_on_exit_failure ? GloVars.execute_on_exit_failure : "(null)");
// Check if the global variable execute_on_exit_failure is NULL
if (GloVars.execute_on_exit_failure == NULL) {
// Exit the function if the variable is not set
return;
}
// Fork a child process
pid_t cpid;
cpid = fork();
// Check for fork failure
if (cpid == -1) {
// Exit with failure status if fork fails
exit(EXIT_FAILURE);
}
// Child process
if (cpid == 0) {
int rc;
// Execute the external script
rc = system(GloVars.execute_on_exit_failure);
// Check if script execution failed
if (rc) {
// Log an error message and exit with failure status if execution fails
proxy_error("Execute on EXIT_FAILURE: Failed to run %s\n", GloVars.execute_on_exit_failure);
perror("system()");
exit(EXIT_FAILURE);
} else {
// Exit with success status if script execution succeeds
exit(EXIT_SUCCESS);
}
}
// Parent process
else {
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_attr_setstacksize (&attr, 64*1024);
pid_t *cpid_ptr=(pid_t *)malloc(sizeof(pid_t));
*cpid_ptr=cpid;
pthread_t thr;
// Create a detached thread to wait for the child process to exit
if (pthread_create(&thr, &attr, waitpid_thread, (void *)cpid_ptr) !=0 ) {
perror("Thread creation");
exit(EXIT_FAILURE);
}
}
}
bool ProxySQL_daemonize_phase3() {
int rc;
int status;
//daemon_log(LOG_INFO, "Angel process started ProxySQL process %d\n", pid);
parent_open_error_log();
proxy_info("Angel process started ProxySQL process %d\n", pid);
parent_close_error_log();
rc=waitpid(pid, &status, 0);
if (rc==-1) {
parent_open_error_log();
perror("waitpid");
//proxy_error("[FATAL]: waitpid: %s\n", perror("waitpid"));
exit(EXIT_FAILURE);
}
rc=WIFEXITED(status);
if (rc) { // client exit()ed
rc=WEXITSTATUS(status);
if (rc==0) {
//daemon_log(LOG_INFO, "Shutdown angel process\n");
parent_open_error_log();
proxy_info("Shutdown angel process\n");
exit(EXIT_SUCCESS);
} else {
//daemon_log(LOG_INFO, "ProxySQL exited with code %d . Restarting!\n", rc);
parent_open_error_log();
proxy_error("ProxySQL exited with code %d . Restarting!\n", rc);
call_execute_on_exit_failure();
parent_close_error_log();
return false;
}
} else {
//daemon_log(LOG_INFO, "ProxySQL crashed. Restarting!\n");
parent_open_error_log();
proxy_error("ProxySQL crashed. Restarting!\n");
proxy_info("ProxySQL version %s\n", PROXYSQL_VERSION);
if (binary_sha1) {
proxy_info("ProxySQL SHA1 checksum: %s\n", binary_sha1);
}
call_execute_on_exit_failure();
// automatic reload of TLS certificates after a crash , see #4658
std::string msg;
ProxySQL_create_or_load_TLS(false, msg);
// Honor --initial after a crash , see #4659
if (GloVars.__cmd_proxysql_initial==true) {
std::cerr << "Renaming database file " << GloVars.admindb << endl;
char *newpath=(char *)malloc(strlen(GloVars.admindb)+8);
sprintf(newpath,"%s.bak",GloVars.admindb);
rename(GloVars.admindb,newpath); // FIXME: should we check return value, or ignore whatever it successed or not?
}
parent_close_error_log();
return false;
}
return true;
}
void my_terminate(void) {
proxy_error("ProxySQL crashed due to exception\n");
print_backtrace();
}
namespace {
static const bool SET_TERMINATE = std::set_terminate(my_terminate);
}
/**
* @brief Regex for parsing URI connections.
* @details Groups explanation:
* + HierPart doesn't hold '//' making previous non-capturing group optional. E.g:
* - '127.0.0.1:3306'
* - 'mysql-server-1:3306'
* + UserInfo is inside a non-capturing group to avoid matching '@'.
* + Host,Port groups are inside a non-capturing group to allow URI like: 'mysql://user:pass@'
* + RegName matches any valid Ipv4 or domain name.
* + Ipv6 matches Ipv6, it's NOT spec conforming, we don't verify the supplied Ip in the regex.
* + Port matches the supplied port.
* + Optionally match a supplied (/).
* + Ensure match termination in HierPart group, forcing conditional subgroups matching.
*/
const char CONN_URI_REGEX[] {
"^(:?(?P<Scheme>[a-z][a-z0-9\\+\\-\\.]*):\\/\\/)?"
"(?P<HierPart>"
"(?:(?P<UserInfo>(?:\\%[0-9a-f][0-9a-f]|[a-z0-9\\-\\.\\_\\~]|[\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\=]|\\:)*)\\@)?"
"(:?"
"(?P<Host>"
"(?P<RegName>(?:\\%[0-9a-f][0-9a-f]|[a-z0-9\\-\\.\\_\\~]|[\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\=]])*)|"
"(?P<Ipv6>\\[(?:[0-9a-f]|[\\:])*\\]))"
"(?:\\:(?P<Port>[0-9]+)?)?"
")?"
"(?:\\/)?"
")?$"
};
/**
* @brief Holds each of the groups matched in a string by 'CONN_URI_REGEX'.
*/
struct conn_uri_t {
string scheme;
string hierpart;
string user;
string pass;
string host;
uint32_t port;
};
/**
* @brief Uses Regex 'CONN_URI_REGEX' to parse the supplied string.
* @details Tries to perform a 'PartialMatchN' over the 'CONN_URI_REGEX'. Right now doesn't perform a *full*
* check on the validity of the semantics of the URI itself. It does perform some checks.
* @param conn_uri A connection URI.
* @return On success `{EXIT_SUCCESS, conn_uri_t}`, otherwise `{EXIT_FAILURE, conn_uri_t{}}`. Error cause is
* logged.
*/
pair<int,conn_uri_t> parse_conn_uri(const string& conn_uri) {
re2::RE2::Options opts(RE2::Quiet);
opts.set_case_sensitive(false);
re2::RE2 re(CONN_URI_REGEX, opts);;
if (re.error_code()) {
proxy_error("Regex creation failed - %s\n", re.error().c_str());
assert(0);
}
const int num_groups = re.NumberOfCapturingGroups();
std::vector<std::string> str_args(num_groups, std::string {});
std::vector<RE2::Arg> re2_args {};
for (std::string& str_arg : str_args) {
re2_args.push_back(RE2::Arg(&str_arg));
}
std::vector<const RE2::Arg*> matches {};
for (RE2::Arg& re2_arg : re2_args) {
matches.push_back(&re2_arg);
}
const re2::RE2::Arg* const* args = &matches[0];
RE2::PartialMatchN(conn_uri, re, args, num_groups);
const map<string, int>& groups = re.NamedCapturingGroups();
map<string,int>::const_iterator group_it;
string scheme {};
string hierpart {};
string userinfo {};
string host {};
uint32_t port = 0;
if ((group_it = groups.find("Scheme")) != groups.end()) { scheme = str_args[group_it->second - 1]; }
if ((group_it = groups.find("HierPart")) != groups.end()) { hierpart = str_args[group_it->second - 1]; }
if ((group_it = groups.find("UserInfo")) != groups.end()) { userinfo = str_args[group_it->second - 1]; }
if ((group_it = groups.find("Host")) != groups.end()) { host = str_args[group_it->second - 1]; }
// Remove the enclosing(`[]`) from IPv6 addresses
if (host.empty() == false && host.size() > 2) {
if (host[0] == '[') {
host = host.substr(1, host.size()-2);
}
}
string user {};
string pass {};
int32_t match_err = EXIT_SUCCESS;
// Extract supplied info for user:pass
vector<string> v_userinfo = split_str(userinfo, ':');
if (v_userinfo.size() == 1) {
user = v_userinfo[0];
} else if (v_userinfo.size() == 2) {
user = v_userinfo[0];
pass = v_userinfo[1];
} else if (v_userinfo.size() > 2) {
proxy_error(
"Invalid UserInfo '%s' supplied in connection URI. UserInfo should contain at max two fields 'user:pass'\n",
userinfo.c_str()
);
match_err = EXIT_FAILURE;
}
if ((group_it = groups.find("Port")) != groups.end()) {
const string s_port { str_args[group_it->second - 1] };
if (!s_port.empty()) {
char* p_end = nullptr;
port = std::strtol(s_port.c_str(), &p_end, 10);
if (errno == ERANGE || p_end == s_port.c_str()) {
proxy_error("Invalid Port '%s' supplied in connection URI.\n", s_port.c_str());
match_err = EXIT_FAILURE;
}
}
}
struct conn_uri_t uri_data { scheme, hierpart, user, pass, host, port };
return { match_err, uri_data };
}
/**
* @brief Helper function to serialize 'conn_uri_t' for debugging purposes.
*/
string to_string(const conn_uri_t& conn_uri) {
nlohmann::ordered_json j;
j["scheme"] = conn_uri.scheme;
j["user"] = conn_uri.user;
#ifdef DEBUG
j["pass"] = conn_uri.pass;
#endif
j["host"] = conn_uri.host;
j["port"] = conn_uri.port;
return j.dump();
}
/**
* @brief Query for fetching MySQL users during bootstrapping.
* @details For security reasons, users matching the following names are excluded:
* - `mysql.%`
* - `root`
* - `bt_proxysql_%`
* Users starting with `bt_proxysql_` are considered `ProxySQL` created used during `bootstrap` for
* monitoring purposes. A user, could create it's own monitoring accounts under this prefix, to avoid
* ProxySQL fetching them as regular users.
*/
const char BOOTSTRAP_SELECT_USERS[] {
"SELECT DISTINCT user,ssl_type,authentication_string,plugin,password_expired FROM mysql.user"
" WHERE user NOT LIKE 'mysql.%' AND user NOT LIKE 'bt_proxysql_%'"
#ifndef DEBUG
" AND user != 'root'"
#endif
};
/**
* @brief Query for fetching MySQL servers during bootstrapping.
* @details As the regular GR monitoring queries, makes use of `replication_group_members` table.
*/
const char BOOTSTRAP_SELECT_SERVERS[] {
"SELECT MEMBER_ID,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION FROM"
" performance_schema.replication_group_members"
};
/**
* @brief Stores credentials for monitoring created accounts during bootstrap.
*/
struct acct_creds_t {
string user;
string pass;
};
/**
* @brief Minimal set of permissions for a created GR monitoring account.
* @details Right now we **do not grant** permissions to `mysql_innodb_cluster_metadata` tables, since for now
* we don't make any use of them.
*/
const vector<string> t_grant_perms_queries {
"GRANT USAGE ON *.* TO `%s`@`%%`",
// NOTE: For now, we don't make use of any `mysql_innodb_cluster_metadata` tables
// "GRANT SELECT, EXECUTE ON `mysql_innodb_cluster_metadata`.* TO `%s`@`%%`",
// NOTE: For now, we don't make use of 'routers' and 'v2_routers' table
// "GRANT INSERT, UPDATE, DELETE ON `mysql_innodb_cluster_metadata`.`routers` TO `%s`@`%%`",
// "GRANT INSERT, UPDATE, DELETE ON `mysql_innodb_cluster_metadata`.`v2_routers` TO `%s`@`%%`",
"GRANT SELECT ON `performance_schema`.`global_variables` TO `%s`@`%%`",
"GRANT SELECT ON `performance_schema`.`replication_group_member_stats` TO `%s`@`%%`",
"GRANT SELECT ON `performance_schema`.`replication_group_members` TO `%s`@`%%`"
};
/**
* @brief Grants the minimal set of permissions for GR monitoring to a supplies user.
* @details All permissions will be granted for host `%`.
* @param mysql An already opened MySQL connection.
* @param user The username to grant permissions to.
* @return Either `0` for success, or the corresponding `mysql_errno` for failure.
*/
int grant_user_perms(MYSQL* mysql, const string& user) {
for (const string& t_query : t_grant_perms_queries) {
const string query { cstr_format(t_query.c_str(), user.c_str()).str };
proxy_info("GRANT permissions '%s' to user\n", query.c_str());
int myerr = mysql_query(mysql, query.c_str());
if (myerr) {
return mysql_errno(mysql);
}
}
return 0;
}
/**
* @brief Generates a random password conforming with MySQL 'MEDIUM' policy.
* @param size The target password size.
* @return The random password generated.
*/
string gen_rand_password(std::size_t size) {
const string lowercase { "abcdefghijklmnopqrstuvwxyz" };
const string uppercase { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" };
const string digits { "0123456789" };
const string symbols { "~@#$^&*]}[{()|-=+;:.>,</?" };
string allphabet { lowercase + uppercase + digits + symbols };
std::random_device rd {};
std::mt19937 gen { rd() };
string pass {};
if (size == 0) {
return pass;
} else if (size <= 4) {
std::shuffle(allphabet.begin(), allphabet.end(), gen);
pass = allphabet.substr(0, size);
} else {
// 1 numeric character
pass += digits[gen() % digits.size()];
// 1 lowercase character
pass += lowercase[gen() % lowercase.size()];
// 1 uppercase character
pass += toupper(lowercase[gen() % lowercase.size()]);
// 1 special (nonalphanumeric) character
pass += symbols[gen() % symbols.size()];
std::shuffle(allphabet.begin(), allphabet.end(), gen);
std::size_t remains = size - 4;
if (remains < allphabet.size()) {
pass += allphabet.substr(0, remains);
} else {
std::size_t req_modulus = static_cast<std::size_t>(remains / allphabet.size());
std::size_t req_reminder = remains % allphabet.size();
for (std::size_t i = 0; i < req_modulus; i++) {
std::shuffle(allphabet.begin(), allphabet.end(), gen);
pass += allphabet;
}
std::shuffle(allphabet.begin(), allphabet.end(), gen);
pass += allphabet.substr(0, req_reminder);
}
}
return pass;
}
/**
* @brief Creates a random monitoring account for bootstrap with a random generated password.
* @param mysql An already opened MySQL connection.
* @param max_retries Maximum number of attempts for creating the user.
* @return On success `{0, acct_creds_t}`, otherwise `{mysql_errno, acct_creds_t{}}`. Error cause is logged.
*/
pair<int32_t,acct_creds_t> create_random_bootstrap_account(MYSQL* mysql, uint32_t max_retries) {
// Random username
const string monitor_user { "bt_proxysql_" + rand_str(12) };
string monitor_pass {};
int myerr = ER_NOT_VALID_PASSWORD;
uint32_t retries = 0;
while (retries < max_retries && (myerr == ER_NOT_VALID_PASSWORD)) {
monitor_pass = gen_rand_password(16);
const string user_create {
"CREATE USER IF NOT EXISTS '" + monitor_user + "'@'%' IDENTIFIED BY '" + monitor_pass + "'"
};
int myres = mysql_query(mysql, user_create.c_str());
myerr = mysql_errno(mysql);
if (myres || myerr) {
if (myerr != ER_NOT_VALID_PASSWORD) {
return { myerr, { "", "" } };
} else {
proxy_info(
"Bootstrap config, failed to create password for user '%s'. Retrying...\n", monitor_user.c_str()
);
retries += 1;
}
} else {
break;
}
}
if (myerr == 0) {
myerr = grant_user_perms(mysql, monitor_user);
}
return { myerr, { monitor_user, monitor_pass } };
}
/**
* @brief Creates a monitoring account for bootstrap with the supplied parameters.
* @param mysql An already opened MySQL connection.
* @param user The username for the new account.
* @param pass The password for the new account.
* @return On success `{0,acct_creds_t}`, otherwise `{mysql_errno,acct_creds_t}`. Doesn't log errors.
*/
pair<int32_t,acct_creds_t> create_bootstrap_account(MYSQL* mysql, const string& user, const string& pass) {
const string monitor_user { "'" + user + "'" };
const string user_create {
"CREATE USER IF NOT EXISTS " + monitor_user + "@'%' IDENTIFIED BY '" + pass + "'"
};
int myerr = mysql_query(mysql, user_create.c_str());
if (myerr == 0) {
myerr = grant_user_perms(mysql, user);
}
return { myerr, { user, pass } };
}
/**
* @brief This function handles the restart of a process based on certain conditions.
*
* If glovars.proxy_restart_on_error is true, the process restart logic is executed.
* The function implements exponential backoff and terminates if the maximum number of restart attempts is reached.
*/
void handleProcessRestart() {
// Maximum number of restart attempts allowed
constexpr int MAX_RESTART_ATTEMPTS = 5;
// Threshold in seconds for considering restarts too frequent
constexpr int EXECUTION_THRESHOLD = 10;
// Initialize restart attempts counter
time_t laststart = 0;
int restartAttempts = 0;
do {
// Check if laststart is set. It means that the process was already started at least once
if (laststart) {
// Get current time
time_t currenttime = time(NULL);
// Calculate elapsed time since laststart
time_t elapsed_seconds = currenttime - laststart;
// Check if elapsed time is less than threshold
if (elapsed_seconds < EXECUTION_THRESHOLD) {
// if restart is too frequent, something really bad is going on
// Implement exponential backoff - wait exponentially longer after each failed attempt
restartAttempts++;
// Check if restart attempts exceed the maximum allowed
if (restartAttempts > MAX_RESTART_ATTEMPTS) {
// the parent exits before the fork
parent_open_error_log();
proxy_error("Angel process already attempted to restart ProxySQL %d times and has no retries left. exit() with failure.\n", MAX_RESTART_ATTEMPTS);
exit(EXIT_FAILURE);
}
// Calculate wait time using exponential backoff
int waitTime = 1 << restartAttempts;
parent_open_error_log();
proxy_info("ProxySQL exited after only %ld seconds , below the %d seconds threshold. Restarting attempt %d\n", elapsed_seconds, EXECUTION_THRESHOLD, restartAttempts);
proxy_info("Angel process is waiting %d seconds before starting a new ProxySQL process\n", waitTime);
parent_close_error_log();
// Wait for the calculated time before next restart attempt
sleep(waitTime);
} else {
// Reset restart attempts counter if elapsed time is greater than or equal to threshold
restartAttempts = 0;
}
}
// Update laststart time to current time
laststart=time(NULL);
// Fork the process
pid = fork();
// Check for fork error
if (pid < 0) {
parent_open_error_log();
proxy_error("[FATAL]: Error in fork()\n");
// Exit with failure
exit(EXIT_FAILURE);
}
// Check if the process is the parent
if (pid) { // The parent
parent_close_error_log();
if (ProxySQL_daemonize_phase3()==true) {
break;
}
} else { // The daemon
// we open the files also on the child process
// this is required if the child process was created after a crash
parent_open_error_log();
GloVars.global.start_time=monotonic_time();
GloVars.install_signal_handler();
}
} while (pid > 0);
}
#ifndef NOJEM
int print_jemalloc_conf() {
int rc = 0;
bool xmalloc = 0;
bool prof_accum = 0;
bool prof_leak = 0;
size_t lg_cache_max = 0;
size_t lg_prof_sample = 0;
size_t lg_prof_interval = 0;
size_t bool_sz = sizeof(bool);
size_t size_sz = sizeof(size_t);
size_t ssize_sz = sizeof(ssize_t);
rc = mallctl("config.xmalloc", &xmalloc, &bool_sz, NULL, 0);
if (rc) { proxy_error("Failed to fetch 'config.xmalloc' with error %d", rc); return rc; }
rc = mallctl("opt.lg_tcache_max", &lg_cache_max, &size_sz, NULL, 0);
if (rc) { proxy_error("Failed to fetch 'opt.lg_tcache_max' with error %d", rc); return rc; }
rc = mallctl("opt.prof_accum", &prof_accum, &bool_sz, NULL, 0);
if (rc) { proxy_error("Failed to fetch 'opt.prof_accum' with error %d", rc); return rc; }
rc = mallctl("opt.prof_leak", &prof_leak, &bool_sz, NULL, 0);
if (rc) { proxy_error("Failed to fetch 'opt.prof_leak' with error %d", rc); return rc; }
rc = mallctl("opt.lg_prof_sample", &lg_prof_sample, &size_sz, NULL, 0);
if (rc) { proxy_error("Failed to fetch 'opt.lg_prof_sample' with error %d", rc); return rc; }
rc = mallctl("opt.lg_prof_interval", &lg_prof_interval, &ssize_sz, NULL, 0);
if (rc) { proxy_error("Failed to fetch 'opt.lg_prof_interval' with error %d", rc); return rc; }
proxy_info(
"Using jemalloc with MALLOC_CONF:"
" config.xmalloc:%d, lg_tcache_max:%lu, opt.prof_accum:%d, opt.prof_leak:%d,"
" opt.lg_prof_sample:%lu, opt.lg_prof_interval:%lu, rc:%d\n",
xmalloc, lg_cache_max, prof_accum, prof_leak, lg_prof_sample, lg_prof_interval, rc
);
return 0;
}
#else
int print_jemalloc_conf() {
return 0;
}
#endif
int main(int argc, const char * argv[]) {
if (check_openssl_version() == false) {
exit(EXIT_FAILURE);
}
#ifdef DEBUG
{
// This run some ProxyProtocolInfo tests.
// It will assert() if any test fails
ProxyProtocolInfo ppi;
ppi.run_tests();
}
#endif // DEBUG
{
MYSQL *my = mysql_init(NULL);
mysql_close(my);
// cpu_timer t;
ProxySQL_Main_init();
#ifdef DEBUG
// std::cerr << "Main init phase0 completed in ";
#endif
}
#ifdef DEBUG
{
// Automated testing
MySQL_Set_Stmt_Parser parser("");
parser.test_parse_USE_query();
}
#endif // DEBUG
{
cpu_timer t;
ProxySQL_Main_process_global_variables(argc, argv);
GloVars.global.start_time=monotonic_time(); // always initialize it
srand(GloVars.global.start_time*thread_id());
randID = rand();
#ifdef DEBUG
std::cerr << "Main init global variables completed in ";
#endif
}
// Output current jemalloc conf; no action taken when disabled
{
int rc = print_jemalloc_conf();
if (rc) { exit(EXIT_FAILURE); }
}
struct rlimit nlimit;
{
int rc = getrlimit(RLIMIT_NOFILE, &nlimit);
if (rc == 0) {
proxy_info("Current RLIMIT_NOFILE: %lu\n", nlimit.rlim_cur);
if (nlimit.rlim_cur <= 1024) {
proxy_error("Current RLIMIT_NOFILE is very low: %lu . Tune RLIMIT_NOFILE correctly before running ProxySQL\n", nlimit.rlim_cur);
if (nlimit.rlim_max > nlimit.rlim_cur) {
if (nlimit.rlim_max >= 102400) {
nlimit.rlim_cur = 102400;
} else {
nlimit.rlim_cur = nlimit.rlim_max;
}
proxy_warning("Automatically setting RLIMIT_NOFILE to %lu\n", nlimit.rlim_cur);
rc = setrlimit(RLIMIT_NOFILE, &nlimit);
if (rc) {
proxy_error("Unable to increase RLIMIT_NOFILE: %s: \n", strerror(errno));
}
} else {
proxy_error("Unable to increase RLIMIT_NOFILE because rlim_max is low: %lu\n", nlimit.rlim_max);
}
}
} else {
proxy_error("Call to getrlimit failed: %s\n", strerror(errno));
}
}
bootstrap_info_t bootstrap_info {};
// Try to connect to MySQL for performing the bootstrapping process:
// - If data isn't found we perform the bootstrap process.
// - If non-empty datadir is present, reconfiguration should be performed.
if (GloVars.global.gr_bootstrap_mode == 1) {
// Check the other required arguments for performing the bootstrapping process:
// - Username
// - Password - asked by prompt or supplied
// - Connection string parsing is required
const string conn_uri { GloVars.global.gr_bootstrap_uri };
const pair<int32_t,conn_uri_t> parse_uri_res { parse_conn_uri(conn_uri) };
const conn_uri_t uri_data = parse_uri_res.second;
if (parse_uri_res.first == EXIT_FAILURE) {
proxy_info("Aborting bootstrap due to failed to parse or match URI - `%s`\n", to_string(uri_data).c_str());
exit(parse_uri_res.first);
} else {
proxy_info("Bootstrap connection data supplied via URI - `%s`\n", to_string(uri_data).c_str());
}
const char* c_host = uri_data.host.c_str();
const char* c_user = uri_data.user.empty() ? "root" : uri_data.user.c_str();
const char* c_pass = nullptr;
uint32_t port = uri_data.port == 0 ? 3306 : uri_data.port;
uint32_t flags = CLIENT_SSL;
nlohmann::ordered_json conn_data { { "host", c_host }, { "user", c_user }, { "port", port } };
proxy_info("Performing bootstrap connection using URI data and defaults - `%s`\n", conn_data.dump().c_str());
if (uri_data.pass.empty()) {
c_pass = getpass("Enter password: ");
} else {
c_pass = uri_data.pass.c_str();
}
MYSQL* mysql = mysql_init(NULL);
// SSL explicitly disabled by user for backend connections
if (GloVars.global.gr_bootstrap_ssl_mode) {
if (!strcasecmp(GloVars.global.gr_bootstrap_ssl_mode, "DISABLED")) {
flags = 0;
}
}
if (flags == CLIENT_SSL) {
mysql_ssl_set(
mysql,
GloVars.global.gr_bootstrap_ssl_key,
GloVars.global.gr_bootstrap_ssl_cert,
GloVars.global.gr_bootstrap_ssl_ca,
GloVars.global.gr_bootstrap_ssl_capath,
GloVars.global.gr_bootstrap_ssl_cipher
);
}
if (!mysql_real_connect(mysql, c_host, c_user, c_pass, nullptr, port, NULL, flags)) {
proxy_error("Bootstrap failed, connection error '%s'\n", mysql_error(mysql));
exit(EXIT_FAILURE);
}
if (uri_data.pass.empty()) {
uint32_t passlen = strlen(c_pass);
memset(static_cast<void*>(const_cast<char*>(c_pass)), 0, passlen);
}
// Get server default collation and version directly from initial handshake
bootstrap_info.server_language = mysql->server_language;
bootstrap_info.server_version = mysql->server_version;
// Fetch all required data for Bootstrap
int myrc = mysql_query(mysql, BOOTSTRAP_SELECT_SERVERS);
if (myrc) {
proxy_error("Bootstrap failed, query failed with error - %s\n", mysql_error(mysql));
exit(EXIT_FAILURE);
}
MYSQL_RES* myres_members = mysql_store_result(mysql);
if (myres_members == nullptr || mysql_num_rows(myres_members) == 0) {
proxy_error("Bootstrap failed, expected server %s:%d to have Group Replication configured\n", c_host, port);
exit(EXIT_FAILURE);
}
myrc = mysql_query(mysql, BOOTSTRAP_SELECT_USERS);
if (myrc) {
proxy_error("Bootstrap failed, query failed with error - %s\n", mysql_error(mysql));
exit(EXIT_FAILURE);
}
MYSQL_RES* myres_users = mysql_store_result(mysql);
if (myres_users == nullptr) {
proxy_error("Bootstrap failed, storing resultset failed with error - %s\n", mysql_error(mysql));
exit(EXIT_FAILURE);
}
// TODO-NOTE: Maybe further data verification should be performed here; bootstrap-info holding final types
bootstrap_info.servers = myres_members;
bootstrap_info.users = myres_users;
// Setup a bootstrap account - monitoring
const string account_create_policy {
GloVars.global.gr_bootstrap_account_create == nullptr ? "if-not-exists" :
GloVars.global.gr_bootstrap_account_create
};
if (GloVars.global.gr_bootstrap_account == nullptr && GloVars.global.gr_bootstrap_account_create != nullptr) {
proxy_error("Bootstrap failed, option '--account-create' can only be used in combination with '--account'\n");
exit(EXIT_FAILURE);
}
const uint32_t password_retries = GloVars.global.gr_bootstrap_password_retries;
string new_mon_user {};
string new_mon_pass {};
if (GloVars.global.gr_bootstrap_account != nullptr) {
const vector<string> valid_policies { "if-not-exists", "always", "never" };
if (std::find(valid_policies.begin(), valid_policies.end(), account_create_policy) == valid_policies.end()) {
proxy_error("Bootstrap failed, unknown '--account-create' option '%s'\n", account_create_policy.c_str());
exit(EXIT_FAILURE);
}
// Since an account has been specified, we require the password for the account
const string mon_user { GloVars.global.gr_bootstrap_account };
const string get_acc_pass_msg { "Please enter MySQL password for " + mon_user + ": " };
// Get the account pass directly from user input
const string mon_pass = getpass(get_acc_pass_msg.c_str());
// 1. Check if account exists
const string get_user_cnt { "SELECT COUNT(*) FROM mysql.user WHERE user='" + mon_user + "'" };
int cnt_err = mysql_query(mysql, get_user_cnt.c_str());
MYSQL_RES* myres = mysql_store_result(mysql);
if (cnt_err || myres == nullptr) {
proxy_error("Bootstrap failed, detecting count existence failed with error - %s\n", mysql_error(mysql));
exit(EXIT_FAILURE);
}
MYSQL_ROW myrow = mysql_fetch_row(myres);
uint32_t acc_exists = atoi(myrow[0]);
mysql_free_result(myres);
if (account_create_policy == "if-not-exists") {
// 2. Account doesn't exists, create new account. Otherwise reuse current
if (acc_exists == 0) {
pair<int32_t,acct_creds_t> new_creds { create_bootstrap_account(mysql, mon_user, mon_pass) };
if (new_creds.first) {
proxy_error("Bootstrap failed, user creation failed with error - %s\n", mysql_error(mysql));
exit(EXIT_FAILURE);
} {
// Store the credentials as the new 'monitor' ones.
new_mon_user = mon_user;
new_mon_pass = new_creds.second.pass;
}
} else {
new_mon_user = mon_user;
new_mon_pass = mon_pass;
}
} else if (account_create_policy == "always") {
if (acc_exists == 0) {
pair<int32_t,acct_creds_t> new_creds { create_bootstrap_account(mysql, mon_user, mon_pass) };
if (new_creds.first) {
proxy_error("Bootstrap failed, user creation failed with error - %s\n", mysql_error(mysql));
exit(EXIT_FAILURE);
}
} else {
proxy_error(
"Bootstrap failed, account '%s' already exists but supplied option '--account-create=\"always\"'\n",
mon_user.c_str()
);
exit(EXIT_FAILURE);
}
new_mon_user = mon_user;
new_mon_pass = mon_pass;
} else if (account_create_policy == "never") {
if (acc_exists == 0) {
proxy_error(
"Bootstrap failed, account '%s' doesn't exists but supplied option '--account-create=\"never\"'\n",
mon_user.c_str()
);
exit(EXIT_FAILURE);
}
new_mon_user = mon_user;
new_mon_pass = mon_pass;
} else {
proxy_error("Bootstrap failed, unknown '--account-create' option '%s'\n", account_create_policy.c_str());
exit(EXIT_FAILURE);
}
} else {
string prev_bootstrap_user {};
string prev_bootstrap_pass {};
if (Proxy_file_exists(GloVars.admindb)) {
SQLite3DB::LoadPlugin(GloVars.sqlite3_plugin);
SQLite3DB* configdb = new SQLite3DB();
configdb->open((char*)GloVars.admindb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX);
{
const char check_table[] {
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='bootstrap_variables'"
};
int table_exists = 0;
char* error = nullptr;
int cols = 0;
int affected_rows = 0;
SQLite3_result* resultset = NULL;
configdb->execute_statement(check_table, &error , &cols , &affected_rows , &resultset);
if (error == nullptr && resultset) {
table_exists = atoi(resultset->rows[0]->fields[0]);
delete resultset;
} else {
const char* err_msg = error != nullptr ? error : "Empty resultset";
proxy_error("Bootstrap failed, query failed with error - %s", err_msg);
exit(EXIT_FAILURE);
}
if (table_exists != 0) {
const char query_user_pass[] {
"SELECT variable_name,variable_value FROM bootstrap_variables"
" WHERE variable_name='bootstrap_username' OR variable_name='bootstrap_password'"
" ORDER BY variable_name"
};
configdb->execute_statement(query_user_pass, &error, &cols, &affected_rows, &resultset);
if (resultset->rows.size() != 0) {
prev_bootstrap_user = resultset->rows[1]->fields[1];
prev_bootstrap_pass = resultset->rows[0]->fields[1];
}
if (resultset) {
delete resultset;
}
}
}
delete configdb;
}
if (!prev_bootstrap_pass.empty() && !prev_bootstrap_user.empty()) {
proxy_info(
"Bootstrap info, detected previous bootstrap user '%s' reusing account...\n",
prev_bootstrap_user.c_str()
);
new_mon_user = prev_bootstrap_user;
new_mon_pass = prev_bootstrap_pass;
} else {
// Create random account with random password
pair<int32_t,acct_creds_t> mon_creds { create_random_bootstrap_account(mysql, password_retries) };
if (mon_creds.first) {
proxy_error(
"Bootstrap failed, user creation '%s' failed with error - %s\n",
mon_creds.second.user.c_str(), mysql_error(mysql)
);
exit(EXIT_FAILURE);
} else {
new_mon_user = mon_creds.second.user;
new_mon_pass = mon_creds.second.pass;
bootstrap_info.rand_gen_user = true;
}
}
}
bootstrap_info.mon_user = new_mon_user;
bootstrap_info.mon_pass = new_mon_pass;
mysql_close(mysql);
}
{
cpu_timer t;
ProxySQL_Main_init_SSL_module();
#ifdef DEBUG
std::cerr << "Main SSL init variables completed in ";
#endif
}
{
cpu_timer t;
int fd = -1;
char buff[PATH_MAX+1];
ssize_t len = -1;
#if defined(__FreeBSD__)
len = readlink("/proc/curproc/file", buff, sizeof(buff)-1);
#else
len = readlink("/proc/self/exe", buff, sizeof(buff)-1);
#endif
if (len != -1) {
buff[len] = '\0';
fd = open(buff, O_RDONLY);
}
if(fd >= 0) {
struct stat statbuf;
if(fstat(fd, &statbuf) == 0) {
unsigned char *fb = (unsigned char *)mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (fb != MAP_FAILED) {
unsigned char temp[SHA_DIGEST_LENGTH];
SHA1(fb, statbuf.st_size, temp);
binary_sha1 = (char *)malloc(SHA_DIGEST_LENGTH*2+1);
memset(binary_sha1, 0, SHA_DIGEST_LENGTH*2+1);
char buf[SHA_DIGEST_LENGTH*2 + 1];
for (int i=0; i < SHA_DIGEST_LENGTH; i++) {
sprintf((char*)&(buf[i*2]), "%02x", temp[i]);
}
memcpy(binary_sha1, buf, SHA_DIGEST_LENGTH*2);
munmap(fb,statbuf.st_size);
} else {
proxy_error("Unable to mmap %s: %s\n", buff, strerror(errno));
}
} else {
proxy_error("Unable to fstat %s: %s\n", buff, strerror(errno));
}
} else {
proxy_error("Unable to open %s: %s\n", argv[0], strerror(errno));
}
#ifdef DEBUG
std::cerr << "SHA1 generated in ";
#endif
}
if (GloVars.global.foreground==false) {
{
cpu_timer t;
ProxySQL_daemonize_phase1((char *)argv[0]);
#ifdef DEBUG
std::cerr << "Main daemonize phase1 completed in ";
#endif
}
/* Do the fork */
if ((pid = daemon_fork()) < 0) {
/* Exit on error */
daemon_retval_done();
exit(EXIT_FAILURE);
} else if (pid) { /* The parent */
ProxySQL_daemonize_wait_daemon();
} else { /* The daemon */
cpu_timer t;
GloVars.global.start_time=monotonic_time();
GloVars.install_signal_handler();
if (ProxySQL_daemonize_phase2()==false) {
goto finish;
}
#ifdef DEBUG
std::cerr << "Main daemonize phase1 completed in ";
#endif
}
if (glovars.proxy_restart_on_error) {
handleProcessRestart();
}
} else {
GloAdmin->flush_error_log();
GloVars.install_signal_handler();
}
__start_label:
{
cpu_timer t;
ProxySQL_Main_init_phase2___not_started(bootstrap_info);
#ifdef DEBUG
std::cerr << "Main init phase2 completed in ";
#endif
}
if (glovars.shutdown) {
goto __shutdown;
}
{
cpu_timer t;
ProxySQL_Main_init_phase3___start_all();
#ifdef DEBUG
std::cerr << "Main init phase3 completed in ";
#endif
}
#ifdef DEBUG
std::cerr << "WARNING: this is a DEBUG release and can be slow or perform poorly. Do not use it in production" << std::endl;
#endif
proxy_info("For information about products and services visit: https://proxysql.com/\n");
proxy_info("For online documentation visit: https://proxysql.com/documentation/\n");
proxy_info("For support visit: https://proxysql.com/services/support/\n");
proxy_info("For consultancy visit: https://proxysql.com/services/consulting/\n");
{
#if 0
{
// the following commented code is here only to manually test handleProcessRestart()
// DO NOT ENABLE
//proxy_info("Service is up\n");
//sleep(2);
//assert(0);
}
#endif
unsigned int missed_heartbeats = 0;
unsigned long long previous_time = monotonic_time();
unsigned int inner_loops = 0;
unsigned long long time_next_version_check = 0;
while (glovars.shutdown==0) {
usleep(200000);
if (disable_watchdog) {
continue;
}
unsigned long long curtime = monotonic_time();
if (GloVars.global.version_check) {
if (curtime > time_next_version_check) {
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_t thr;
if (pthread_create(&thr, &attr, main_check_latest_version_thread, NULL) !=0 ) {
perror("Thread creation");
exit(EXIT_FAILURE);
}
if (time_next_version_check == 0)
time_next_version_check = curtime;
unsigned long long inter = 24*3600*1000;
inter *= 1000;
time_next_version_check += inter;
}
}
inner_loops++;
if (curtime >= inner_loops*300000 + previous_time ) {
// if this happens, it means that this very simple loop is blocked
// probably we are running under gdb
previous_time = curtime;
inner_loops = 0;
continue;
}
if (GloMTH) {
unsigned long long atomic_curtime = 0;
unsigned long long poll_timeout = (unsigned int)GloMTH->variables.poll_timeout;
unsigned int threads_missing_heartbeat = 0;
poll_timeout += 1000; // add 1 second (rounding up)
poll_timeout *= 1000; // convert to us
if (curtime < previous_time + poll_timeout) {
continue;
}
previous_time = curtime;
inner_loops = 0;
unsigned int i;
if (GloMTH->mysql_threads) {
for (i=0; i<GloMTH->num_threads; i++) {
if (GloMTH->mysql_threads[i].worker) {
atomic_curtime = GloMTH->mysql_threads[i].worker->atomic_curtime;
if (curtime > atomic_curtime + poll_timeout) {
threads_missing_heartbeat++;
}
}
}
}
#ifdef IDLE_THREADS
if (GloVars.global.idle_threads) {
if (GloMTH->mysql_threads) {
for (i=0; i<GloMTH->num_threads; i++) {
if (GloMTH->mysql_threads_idles[i].worker) {
atomic_curtime = GloMTH->mysql_threads_idles[i].worker->atomic_curtime;
if (curtime > atomic_curtime + poll_timeout) {
threads_missing_heartbeat++;
}
}
}
}
}
#endif
if (threads_missing_heartbeat) {
proxy_error("Watchdog: %u threads missed a heartbeat\n", threads_missing_heartbeat);
missed_heartbeats++;
if (missed_heartbeats >= (unsigned int)GloVars.restart_on_missing_heartbeats) {
#ifdef RUNNING_ON_VALGRIND
proxy_error("Watchdog: reached %u missed heartbeats. Not aborting because running under Valgrind\n", missed_heartbeats);
#else
if (GloVars.restart_on_missing_heartbeats) {
proxy_error("Watchdog: reached %u missed heartbeats. Aborting!\n", missed_heartbeats);
proxy_error("Watchdog: see details at https://github.com/sysown/proxysql/wiki/Watchdog\n");
assert(0);
}
#endif
}
} else {
missed_heartbeats = 0;
}
}
}
}
__shutdown:
proxy_info("Starting shutdown...\n");
// First shutdown step is to unload plugins
UnloadPlugins();
ProxySQL_Main_init_phase4___shutdown();
proxy_info("Shutdown completed!\n");
if (glovars.reload) {
if (glovars.reload==2) {
GloVars.global.nostart=true;
}
glovars.reload=0;
glovars.shutdown=0;
goto __start_label;
}
finish:
//daemon_log(LOG_INFO, "Exiting...");
proxy_info("Exiting...\n");
daemon_retval_send(255);
daemon_signal_done();
daemon_pid_file_remove();
// l_mem_destroy(__thr_sfp);
#ifdef RUNNING_ON_VALGRIND
if (RUNNING_ON_VALGRIND==0) {
if (__web_interface) {
dlclose(__web_interface);
}
if (__mysql_ldap_auth) {
dlclose(__mysql_ldap_auth);
}
}
#endif
return 0;
}