diff --git a/doc/global_variables.md b/doc/global_variables.md index 9d325fbcf7..64bbf5bc18 100644 --- a/doc/global_variables.md +++ b/doc/global_variables.md @@ -12,6 +12,16 @@ Also, there are 2 types of global variables, depending on which part of ProxySQL These global variables are stored in a per-thread fashion inside of the proxy in order to speed up access to them, as they are used extremely frequently. They control the behaviour of the proxy in terms of memory footprint or the number of connections accepted, and other essential aspects. Whenever a `LOAD MYSQL VARIABLES TO RUNTIME` command is issued (see the [configuration system documentation](https://github.com/sysown/proxysql-0.2/blob/master/doc/configuration_system.md) for details on that command), all the threads using the variables are notified that they have to update their values. +To change the value of a global variable either use an `UPDATE` statement: +``` +UPDATE global_variables SET variable_value=1900 WHERE variable_name='admin-refresh_interval'; +``` +or the shorter `SET` statement, similar to MySQL's: +``` +SET admin-refresh_interval = 1700; +SET admin-version = '1.1.1beta8'; +``` + Next, we're going to explain each type of variable in detail. ### `admin-admin_credentials` diff --git a/doc/internal/global_variables.txt b/doc/internal/global_variables.txt index f7ab002bdc..d3406864dd 100644 --- a/doc/internal/global_variables.txt +++ b/doc/internal/global_variables.txt @@ -5,6 +5,7 @@ either very simple, or managed by a simple API. Trying to make a standard API, the proposed functions are: virtual char **get_variables_list() {return NULL;} + virtual bool has_variable(char *name) {return false;} virtual char *get_variable(char *name) {return NULL;}; virtual bool set_variable(char *name, char *value) {return false;}; virtual void commit() {}; @@ -14,6 +15,8 @@ Trying to make a standard API, the proposed functions are: In order: - get_variables_list() returns an array of pointer to variables name, where the last element is NULL; +- has_variable() returns true if the given name is the name of one of the module + variables; - get_variable() returns the value of a specific variable. If the variable was not previously set, the module should return the default value. If the variable doesn't exist, NULL should be returned; diff --git a/include/MySQL_Thread.h b/include/MySQL_Thread.h index 99f081633a..24ed8e17cf 100644 --- a/include/MySQL_Thread.h +++ b/include/MySQL_Thread.h @@ -311,6 +311,7 @@ class MySQL_Threads_Handler char *get_variable(char *name); bool set_variable(char *name, char *value); char **get_variables_list(); + bool has_variable(const char * name); MySQL_Threads_Handler(); ~MySQL_Threads_Handler(); diff --git a/include/gen_utils.h b/include/gen_utils.h index 260836db70..5a9cdb0cff 100644 --- a/include/gen_utils.h +++ b/include/gen_utils.h @@ -153,3 +153,4 @@ bool Proxy_file_exists(const char *); bool Proxy_file_regular(const char *); int remove_spaces(const char *); +char *trim_spaces_in_place(char *str); \ No newline at end of file diff --git a/include/proxysql_admin.h b/include/proxysql_admin.h index 1039b1219e..c0c609b0ee 100644 --- a/include/proxysql_admin.h +++ b/include/proxysql_admin.h @@ -92,6 +92,7 @@ class ProxySQL_Admin { bool init(); bool get_read_only() { return variables.admin_read_only; } bool set_read_only(bool ro) { variables.admin_read_only=ro; return variables.admin_read_only; } + bool has_variable(const char *name); void init_users(); void init_mysql_servers(); void init_mysql_query_rules(); diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index b6874ebf00..85f90d3843 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -292,7 +292,7 @@ int MySQL_Threads_Handler::listener_add(const char *iface) { while(!__sync_bool_compare_and_swap(&thr->mypolls.pending_listener_add,0,rc)) { usleep(100); // pause a bit } -/* +/* while(!__sync_bool_compare_and_swap(&thr->mypolls.pending_listener_change,0,1)) { cpu_relax_pa(); } while(__sync_fetch_and_add(&thr->mypolls.pending_listener_change,0)==1) { cpu_relax_pa(); } // spin_wrlock(&thr->thread_mutex); @@ -323,7 +323,7 @@ int MySQL_Threads_Handler::listener_del(const char *iface) { shutdown(fd,SHUT_RDWR); close(fd); } - + return 0; } @@ -618,7 +618,7 @@ char * MySQL_Threads_Handler::get_variable(char *name) { // this is the public f return strdup((variables.default_reconnect ? "true" : "false")); } return NULL; -} +} @@ -630,7 +630,7 @@ bool MySQL_Threads_Handler::set_variable(char *name, char *value) { // this is t // OUT: // false: unable to change the variable value, either because doesn't exist, or because out of range, or read only // true: variable value changed - // + // if (!value) return false; size_t vallen=strlen(value); @@ -1175,6 +1175,18 @@ char ** MySQL_Threads_Handler::get_variables_list() { return ret; } +// Returns true if the given name is the name of an existing mysql variable +bool MySQL_Threads_Handler::has_variable(const char *name) { + size_t no_vars = sizeof(mysql_thread_variables_names) / sizeof(char *); + for (unsigned int i = 0; i < no_vars, mysql_thread_variables_names[i] != NULL; ++i) { + size_t var_len = strlen(mysql_thread_variables_names[i]); + if (strlen(name) == var_len && !strncmp(name, mysql_thread_variables_names[i], var_len)) { + return true; + } + } + return false; +} + void MySQL_Threads_Handler::print_version() { fprintf(stderr,"Standard MySQL Threads Handler rev. %s -- %s -- %s\n", MYSQL_THREAD_VERSION, __FILE__, __TIMESTAMP__); } @@ -1345,8 +1357,8 @@ void MySQL_Thread::poll_listener_add(int sock) { listener_DS->fd=sock; proxy_debug(PROXY_DEBUG_NET,1,"Created listener %p for socket %d\n", listener_DS, sock); - //mypoll_add(&mypolls, POLLIN, sock, listener_DS); - mypolls.add(POLLIN, sock, listener_DS, monotonic_time()); + //mypoll_add(&mypolls, POLLIN, sock, listener_DS); + mypolls.add(POLLIN, sock, listener_DS, monotonic_time()); } void MySQL_Thread::poll_listener_del(int sock) { @@ -1489,7 +1501,7 @@ void MySQL_Thread::run() { proxy_debug(PROXY_DEBUG_NET,1,"Poll for DataStream=%p will be called with FD=%d and events=%d\n", mypolls.myds[n], mypolls.fds[n].fd, mypolls.fds[n].events); } - + spin_wrunlock(&thread_mutex); while ((n=__sync_add_and_fetch(&mypolls.pending_listener_add,0))) { // spin here @@ -1498,7 +1510,7 @@ void MySQL_Thread::run() { // if (n==1) { // __sync_add_and_fetch(&mypolls.pending_listener_change,1); // } - } + } if (mysql_thread___wait_timeout==0) { // we should be going into PAUSE mode @@ -1528,7 +1540,7 @@ void MySQL_Thread::run() { spin_wrlock(&thread_mutex); mypolls.poll_timeout=0; // always reset this to 0 . If a session needs a specific timeout, it will set this one - + curtime=monotonic_time(); if (curtime > last_maintenance_time + 200000) { // hardcoded value for now last_maintenance_time=curtime; @@ -1646,7 +1658,7 @@ void MySQL_Thread::process_data_on_data_stream(MySQL_Data_Stream *myds, unsigned myds->myconn->handler(mypolls.fds[n].revents); } } - if ( (mypolls.fds[n].events & POLLOUT) + if ( (mypolls.fds[n].events & POLLOUT) && ( (mypolls.fds[n].revents & POLLERR) || (mypolls.fds[n].revents & POLLHUP) ) ) { @@ -1654,7 +1666,7 @@ void MySQL_Thread::process_data_on_data_stream(MySQL_Data_Stream *myds, unsigned } myds->check_data_flow(); - + if (myds->active==FALSE) { if (myds->sess->client_myds==myds) { diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index 21603a5330..4699a013a9 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -212,7 +212,7 @@ class admin_main_loop_listeners { } return ifaces; } - + public: int nfds; @@ -434,6 +434,62 @@ bool admin_handler_command_proxysql(char *query_no_space, unsigned int query_no_ return true; } +// Returns true if the given name is either a know mysql or admin global variable. +bool is_valid_global_variable(const char *var_name) { + if (strlen(var_name) > 6 && !strncmp(var_name, "mysql-", 6) && GloMTH->has_variable(var_name + 6)) { + return true; + } else if (strlen(var_name) > 6 && !strncmp(var_name, "admin-", 6) && SPA->has_variable(var_name + 6)) { + return true; + } else { + return false; + } +} + +// This method translates a 'SET variable=value' command into an equivalent UPDATE. It doesn't yes support setting +// multiple variables at once. +// +// It modifies the original query. +bool admin_handler_command_set(char *query_no_space, unsigned int query_no_space_length, MySQL_Session *sess, ProxySQL_Admin *pa, char **q, unsigned int *ql) { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Received command %s\n", query_no_space); + proxy_info("Received command %s\n", query_no_space); + + // Get a pointer to the beginnig of var=value entry and split to get var name and value + char *set_entry = query_no_space + strlen("SET "); + char *untrimmed_var_name; + char *var_value; + c_split_2(set_entry, "=", &untrimmed_var_name, &var_value); + + // Trim spaces from var name to allow writing like 'var = value' + char *var_name = trim_spaces_in_place(untrimmed_var_name); + + bool run_query = false; + // Check if the command tries to set a non-existing variable. + if (!is_valid_global_variable(var_name)) { + char *err_msg_fmt = (char *) "Unknown global variable: '%s'."; + size_t buff_len = strlen(err_msg_fmt) + strlen(var_name) + 1; + char *buff = (char *) malloc(buff_len); + snprintf(buff, buff_len, err_msg_fmt, var_name); + SPA->send_MySQL_ERR(&sess->client_myds->myprot, buff); + free(buff); + run_query = false; + } else { + const char *update_format = (char *)"UPDATE global_variables SET variable_value=%s WHERE variable_name='%s'"; + // Computed length is more than needed since it also counts the format modifiers (%s). + size_t query_len = strlen(update_format) + strlen(var_name) + strlen(var_value) + 1; + char *query = (char *)l_alloc(query_len); + snprintf(query, query_len, update_format, var_value, var_name); + + run_query = true; + l_free(*ql,*q); + *q = query; + *ql = strlen(*q) + 1; + } + + free(var_name); + free(var_value); + return run_query; +} + /* Note: * This function can modify the original query */ @@ -1239,7 +1295,7 @@ void admin_session_handler(MySQL_Session *sess, ProxySQL_Admin *pa, PtrSize_t *p memcpy(query,(char *)pkt->ptr+sizeof(mysql_hdr)+1,query_length-1); query[query_length-1]=0; - char *query_no_space=(char *)l_alloc(query_length); + char *query_no_space=(char *)l_alloc(query_length); memcpy(query_no_space,query,query_length); unsigned int query_no_space_length=remove_spaces(query_no_space); @@ -1277,16 +1333,16 @@ void admin_session_handler(MySQL_Session *sess, ProxySQL_Admin *pa, PtrSize_t *p } if (sess->stats==false) { - if ((query_no_space_length>8) && (!strncasecmp("PROXYSQL ", query_no_space, 8))) { + if ((query_no_space_length>8) && (!strncasecmp("PROXYSQL ", query_no_space, 8))) { proxy_debug(PROXY_DEBUG_ADMIN, 4, "Received PROXYSQL command\n"); pthread_mutex_lock(&admin_mutex); run_query=admin_handler_command_proxysql(query_no_space, query_no_space_length, sess, pa); pthread_mutex_unlock(&admin_mutex); goto __run_query; } - if ((query_no_space_length>5) && ( (!strncasecmp("SAVE ", query_no_space, 5)) || (!strncasecmp("LOAD ", query_no_space, 5))) ) { + if ((query_no_space_length>5) && ( (!strncasecmp("SAVE ", query_no_space, 5)) || (!strncasecmp("LOAD ", query_no_space, 5))) ) { proxy_debug(PROXY_DEBUG_ADMIN, 4, "Received LOAD or SAVE command\n"); - run_query=admin_handler_command_load_or_save(query_no_space, query_no_space_length, sess, pa, &query, &query_length); + run_query=admin_handler_command_load_or_save(query_no_space, query_no_space_length, sess, pa, &query, &query_length); goto __run_query; } if ((query_no_space_length>16) && ( (!strncasecmp("KILL CONNECTION ", query_no_space, 16)) || (!strncasecmp("KILL CONNECTION ", query_no_space, 16))) ) { @@ -1420,6 +1476,13 @@ void admin_session_handler(MySQL_Session *sess, ProxySQL_Admin *pa, PtrSize_t *p sprintf(query,q,PROXYSQL_VERSION); goto __run_query; } + + if (!strncasecmp("SET ", query_no_space, 4)) { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Received SET\n"); + run_query = admin_handler_command_set(query_no_space, query_no_space_length, sess, pa, &query, &query_length); + goto __run_query; + } + if(!strncasecmp("CHECKSUM ", query_no_space, 9)){ proxy_debug(PROXYSQL_DEBUG_ADMIN, 4, "Received CHECKSUM command\n"); ProxySQL_Admin *SPA=(ProxySQL_Admin *)pa; @@ -1791,7 +1854,7 @@ void *child_mysql(void *arg) { MySQL_Data_Stream *myds=sess->client_myds; fds[0].fd=client; - fds[0].revents=0; + fds[0].revents=0; fds[0].events=POLLIN|POLLOUT; //sess->myprot_client.generate_pkt_initial_handshake(sess->client_myds,true,NULL,NULL); @@ -1799,14 +1862,14 @@ void *child_mysql(void *arg) { unsigned long oldtime=monotonic_time(); unsigned long curtime=monotonic_time(); - + while (__sync_fetch_and_add(&glovars.shutdown,0)==0) { if (myds->available_data_out()) { - fds[0].events=POLLIN|POLLOUT; + fds[0].events=POLLIN|POLLOUT; } else { - fds[0].events=POLLIN; + fds[0].events=POLLIN; } - fds[0].revents=0; + fds[0].revents=0; //rc=poll(fds,nfds,2000); rc=poll(fds,nfds,__sync_fetch_and_add(&__admin_refresh_interval,0)); { @@ -1842,13 +1905,13 @@ void *child_mysql(void *arg) { //delete sess; if (mysql_thread___default_schema) { free(mysql_thread___default_schema); mysql_thread___default_schema=NULL; } if (mysql_thread___server_version) { free(mysql_thread___server_version); mysql_thread___server_version=NULL; } - delete mysql_thr; -// l_mem_destroy(__thr_sfp); + delete mysql_thr; +// l_mem_destroy(__thr_sfp); return NULL; } void* child_telnet(void* arg) -{ +{ int bytes_read; //int i; // struct timeval tv; @@ -1861,7 +1924,7 @@ void* child_telnet(void* arg) memset(line,0,LINESIZE+1); while ((strncmp(line, "quit", 4) != 0) && glovars.shutdown==0) { bytes_read = recv(client, line, LINESIZE, 0); - if (bytes_read==-1) { + if (bytes_read==-1) { break; } char *eow = strchr(line, '\n'); @@ -1879,7 +1942,7 @@ void* child_telnet(void* arg) } void* child_telnet_also(void* arg) -{ +{ int bytes_read; //int i; // struct timeval tv; @@ -1892,7 +1955,7 @@ void* child_telnet_also(void* arg) memset(line,0,LINESIZE+1); while ((strncmp(line, "quit", 4) != 0) && glovars.shutdown==0) { bytes_read = recv(client, line, LINESIZE, 0); - if (bytes_read==-1) { + if (bytes_read==-1) { break; } char *eow = strchr(line, '\n'); @@ -1926,7 +1989,7 @@ static void * admin_main_loop(void *arg) volatile int *shutdown=((struct _main_args *)arg)->shutdown; char *socket_names[MAX_ADMIN_LISTENERS]; for (i=0;i0) { fds[nfds].fd=s; fds[nfds].events=POLLIN; fds[nfds].revents=0; callback_func[nfds]=2; socket_names[nfds]=strdup(sn); nfds++; } // } - S_amll.wrunlock(); + S_amll.wrunlock(); } - + } //if (__sync_add_and_fetch(shutdown,0)==0) __sync_add_and_fetch(shutdown,1); for (i=0; iexecute("ATTACH DATABASE 'file:mem_mydb?mode=memory&cache=shared' AS myhgm"); #endif /* DEBUG */ @@ -2176,16 +2239,16 @@ bool ProxySQL_Admin::init() { if (GloVars.__cmd_proxysql_reload || GloVars.__cmd_proxysql_initial) { if (GloVars.configfile_open) { - if (GloVars.confFile->cfg) { - Read_MySQL_Servers_from_configfile(); + if (GloVars.confFile->cfg) { + Read_MySQL_Servers_from_configfile(); Read_Global_Variables_from_configfile("admin"); Read_Global_Variables_from_configfile("mysql"); Read_MySQL_Users_from_configfile(); Read_MySQL_Query_Rules_from_configfile(); __insert_or_replace_disktable_select_maintable(); } else { - if (GloVars.confFile->OpenFile(GloVars.config_file)==true) { - Read_MySQL_Servers_from_configfile(); + if (GloVars.confFile->OpenFile(GloVars.config_file)==true) { + Read_MySQL_Servers_from_configfile(); Read_MySQL_Users_from_configfile(); Read_MySQL_Query_Rules_from_configfile(); Read_Global_Variables_from_configfile("admin"); @@ -2202,7 +2265,7 @@ bool ProxySQL_Admin::init() { S_amll.update_ifaces(variables.telnet_admin_ifaces, &S_amll.ifaces_telnet_admin); S_amll.update_ifaces(variables.telnet_stats_ifaces, &S_amll.ifaces_telnet_stats); - + // pthread_t admin_thr; struct _main_args *arg=(struct _main_args *)malloc(sizeof(struct _main_args)); @@ -2453,7 +2516,7 @@ void ProxySQL_Admin::flush_mysql_variables___runtime_to_database(SQLite3DB *db, } int l=strlen(a)+200; GloMTH->wrlock(); - char **varnames=GloMTH->get_variables_list(); + char **varnames=GloMTH->get_variables_list(); char *query=(char *)malloc(l); for (int i=0; varnames[i]; i++) { char *val=GloMTH->get_variable(varnames[i]); @@ -2479,6 +2542,19 @@ char **ProxySQL_Admin::get_variables_list() { return ret; } + +// Returns true if the given name is the name of an existing admin variable +bool ProxySQL_Admin::has_variable(const char *name) { + size_t no_vars = sizeof(admin_variables_names) / sizeof(char *); + for (unsigned int i = 0; i < no_vars, admin_variables_names[i] != NULL; ++i) { + size_t var_len = strlen(admin_variables_names[i]); + if (strlen(name) == var_len && !strncmp(name, admin_variables_names[i], var_len)) { + return true; + } + } + return false; +} + char * ProxySQL_Admin::get_variable(char *name) { #define INTBUFSIZE 4096 char intbuf[INTBUFSIZE]; @@ -2972,7 +3048,7 @@ void ProxySQL_Admin::flush_admin_variables___runtime_to_database(SQLite3DB *db, } int l=strlen(a)+200; - char **varnames=get_variables_list(); + char **varnames=get_variables_list(); char *query=(char *)malloc(l); for (int i=0; varnames[i]; i++) { char *val=get_variable(varnames[i]); diff --git a/lib/gen_utils.cpp b/lib/gen_utils.cpp index 407787f47c..770ac5a9bf 100644 --- a/lib/gen_utils.cpp +++ b/lib/gen_utils.cpp @@ -30,6 +30,36 @@ int remove_spaces(const char *s) { return strlen(s); } +// This function returns a pointer to a substring of the original string. It also +// modifies the original string by setting a null terminator to mark the end +// of the substring. +// +// If the given string was allocated dynamically, the caller must not overwrite +// that pointer with the returned value, since the original pointer must be +// deallocated using the same allocator with which it was allocated. The return +// value must NOT be deallocated using free() etc. +// +// Source: http://stackoverflow.com/a/122721 +char *trim_spaces_in_place(char *str) +{ + char *end; + + // Trim leading space + while(isspace(*str)) str++; + + if(*str == 0) // All spaces? + return str; + + // Trim trailing space + end = str + strlen(str) - 1; + while(end > str && isspace(*end)) end--; + + // Write new null terminator + *(end+1) = 0; + + return str; +} + #define MIN_ARRAY_LEN 8 #define MIN_ARRAY_DELETE_RATIO 8