Skip to content

Commit b73160e

Browse files
authored
Merge pull request #4901 from sysown/v3.0_wait_timeout
[WIP] Setting client side wait_timeout
2 parents 65dbe90 + 2b900b3 commit b73160e

File tree

6 files changed

+349
-10
lines changed

6 files changed

+349
-10
lines changed

include/Base_Session.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class Base_Session {
7777
int transaction_persistent_hostgroup;
7878
int to_process;
7979
enum proxysql_session_type session_type;
80+
int wait_timeout; //in milliseconds
8081

8182
// bool
8283
bool autocommit;

include/MySQL_Thread.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
#define MYSQL_DEFAULT_NET_WRITE_TIMEOUT "60"
3535
#define MYSQL_DEFAULT_MAX_JOIN_SIZE "18446744073709551615"
3636

37+
#define SESS_TO_SCAN_idle_thread 256
38+
3739
extern class MySQL_Variables mysql_variables;
3840

3941
#ifdef IDLE_THREADS
@@ -88,6 +90,8 @@ enum MySQL_Thread_status_variable {
8890
st_var_automatic_detected_sqli,
8991
st_var_mysql_whitelisted_sqli_fingerprint,
9092
st_var_client_host_error_killed_connections,
93+
st_var_set_wait_timeout_commands,
94+
st_var_timeout_terminated_connections,
9195
MY_st_var_END
9296
};
9397

@@ -279,6 +283,8 @@ struct p_th_counter {
279283
hostgroup_locked_set_cmds,
280284
hostgroup_locked_queries,
281285
mysql_unexpected_frontend_packets,
286+
mysql_set_wait_timeout_commands,
287+
mysql_timeout_terminated_connections,
282288
aws_aurora_replicas_skipped_during_query,
283289
automatic_detected_sql_injection,
284290
mysql_whitelisted_sqli_fingerprint,

lib/MySQL_Session.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@ MySQL_Session::MySQL_Session() {
684684
last_HG_affected_rows = -1; // #1421 : advanced support for LAST_INSERT_ID()
685685
proxysql_node_address = NULL;
686686
use_ldap_auth = false;
687+
this->wait_timeout = mysql_thread___wait_timeout;
687688
backend_closed_in_fast_forward = false;
688689
fast_forward_grace_start_time = 0;
689690
}
@@ -1089,6 +1090,7 @@ void MySQL_Session::generate_proxysql_internal_session_json(json &j) {
10891090
j["active_transactions"] = active_transactions;
10901091
j["transaction_time_ms"] = thread->curtime - transaction_started_at;
10911092
j["warning_in_hg"] = warning_in_hg;
1093+
j["wait_timeout"] = this->wait_timeout;
10921094
j["gtid"]["hid"] = gtid_hid;
10931095
j["gtid"]["last"] = ( strlen(gtid_buf) ? gtid_buf : "" );
10941096
json& jqpo = j["qpo"];
@@ -6594,6 +6596,60 @@ bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C
65946596
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection charset to %d\n", c->nr);
65956597
client_myds->myconn->set_charset(c->nr, NAMES);
65966598
}
6599+
} else if (var == "wait_timeout") {
6600+
std::string value = *values++;
6601+
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Client requested SET wait_timeout = %s\n", value.c_str());
6602+
6603+
// Increment counter for SET wait_timeout commands
6604+
thread->status_variables.stvar[st_var_set_wait_timeout_commands]++;
6605+
6606+
unsigned long long client_timeout = 0;
6607+
try {
6608+
client_timeout = std::stoull(value) * 1000;
6609+
} catch (const std::exception& e) {
6610+
char errmsg[128];
6611+
snprintf(errmsg, sizeof(errmsg),
6612+
"Incorrect argument type wait_timeout value: %s", value.c_str());
6613+
client_myds->DSS = STATE_QUERY_SENT_NET;
6614+
client_myds->myprot.generate_pkt_ERR(true, nullptr, nullptr, 1, 1231, (char *)"42000", errmsg, true);
6615+
client_myds->DSS = STATE_SLEEP;
6616+
status = WAITING_CLIENT_DATA;
6617+
return true;
6618+
}
6619+
6620+
// Apply ProxySQL's safe limits: clamp between 1 second (1000ms) and 20 days (20*24*3600*1000ms)
6621+
const unsigned long long MIN_WAIT_TIMEOUT = 1000; // 1 second minimum
6622+
const unsigned long long MAX_WAIT_TIMEOUT = 20 * 24 * 3600 * 1000; // 20 days maximum
6623+
6624+
unsigned long long original_timeout = client_timeout;
6625+
if (client_timeout < MIN_WAIT_TIMEOUT) {
6626+
client_timeout = MIN_WAIT_TIMEOUT;
6627+
} else if (client_timeout > MAX_WAIT_TIMEOUT) {
6628+
client_timeout = MAX_WAIT_TIMEOUT;
6629+
}
6630+
6631+
// Warn if value was clamped due to ProxySQL limits
6632+
if (original_timeout != client_timeout) {
6633+
proxy_warning("Client [%s:%d] (user: %s) requested wait_timeout = %llu ms, clamped to %llu ms (ProxySQL limits: 1s to 20 days)\n",
6634+
client_myds->addr.addr, client_myds->addr.port,
6635+
client_myds->myconn->userinfo->username,
6636+
original_timeout,
6637+
client_timeout);
6638+
}
6639+
6640+
// Warn if client's value exceeds current global timeout (after clamping)
6641+
if (client_timeout > static_cast<unsigned long long>(mysql_thread___wait_timeout)) {
6642+
proxy_warning("Client [%s:%d] (user: %s) requested wait_timeout = %llu ms, exceeds the global mysql-wait_timeout = %d ms. Global timeout will still be enforced.\n",
6643+
client_myds->addr.addr, client_myds->addr.port,
6644+
client_myds->myconn->userinfo->username,
6645+
client_timeout,
6646+
mysql_thread___wait_timeout);
6647+
}
6648+
6649+
if (static_cast<unsigned long long>(this->wait_timeout) != client_timeout) {
6650+
this->wait_timeout = client_timeout;
6651+
proxy_debug(PROXY_DEBUG_MYSQL_COM, 8, "Changing connection wait_timeout to %llu ms\n", client_timeout);
6652+
}
65976653
} else if (var == "tx_isolation") {
65986654
std::string value1 = *values;
65996655
proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Processing SET tx_isolation value %s\n", value1.c_str());

lib/MySQL_Thread.cpp

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ mythr_st_vars_t MySQL_Thread_status_variables_counter_array[] {
165165
{ st_var_max_connect_timeout_err, p_th_counter::max_connect_timeouts, (char *)"max_connect_timeouts" },
166166
{ st_var_generated_pkt_err, p_th_counter::generated_error_packets, (char *)"generated_error_packets" },
167167
{ st_var_client_host_error_killed_connections, p_th_counter::client_host_error_killed_connections, (char *)"client_host_error_killed_connections" },
168+
{ st_var_set_wait_timeout_commands, p_th_counter::mysql_set_wait_timeout_commands, (char *)"mysql_set_wait_timeout_commands" },
169+
{ st_var_timeout_terminated_connections, p_th_counter::mysql_timeout_terminated_connections, (char *)"mysql_timeout_terminated_connections" },
168170
};
169171

170172
mythr_g_st_vars_t MySQL_Thread_status_variables_gauge_array[] {
@@ -807,6 +809,18 @@ th_metrics_map = std::make_tuple(
807809
"proxysql_client_host_error_killed_connections",
808810
"Killed client connections because address exceeded 'client_host_error_counts'.",
809811
metric_tags {}
812+
),
813+
std::make_tuple (
814+
p_th_counter::mysql_set_wait_timeout_commands,
815+
"proxysql_mysql_set_wait_timeout_commands_total",
816+
"Number of SET wait_timeout commands received from clients.",
817+
metric_tags {}
818+
),
819+
std::make_tuple (
820+
p_th_counter::mysql_timeout_terminated_connections,
821+
"proxysql_mysql_timeout_terminated_connections_total",
822+
"Number of client connections terminated due to wait_timeout.",
823+
metric_tags {}
810824
)
811825
},
812826
th_gauge_vector {
@@ -3431,20 +3445,32 @@ void MySQL_Thread::run() {
34313445
* initialized and are accessible within the MySQL Thread.
34323446
*/
34333447
void MySQL_Thread::idle_thread_to_kill_idle_sessions() {
3434-
#define SESS_TO_SCAN 128
3435-
if (mysess_idx + SESS_TO_SCAN > mysql_sessions->len) {
3448+
if (mysess_idx + SESS_TO_SCAN_idle_thread > mysql_sessions->len) {
34363449
mysess_idx=0;
34373450
}
34383451
unsigned int i;
3439-
unsigned long long min_idle = 0;
3440-
if (curtime > (unsigned long long)mysql_thread___wait_timeout*1000) {
3441-
min_idle = curtime - (unsigned long long)mysql_thread___wait_timeout*1000;
3452+
if (curtime < (unsigned long long)mysql_thread___wait_timeout*1000) {
3453+
return; // this should never happen
3454+
//min_idle = curtime - (unsigned long long)mysql_thread___wait_timeout*1000;
34423455
}
3443-
for (i=0;i<SESS_TO_SCAN && mysess_idx < mysql_sessions->len; i++) {
3456+
for (i=0 ; i < SESS_TO_SCAN_idle_thread && mysess_idx < mysql_sessions->len; i++) {
34443457
uint32_t sess_pos=mysess_idx;
34453458
MySQL_Session *mysess=(MySQL_Session *)mysql_sessions->index(sess_pos);
3446-
if (mysess->idle_since < min_idle || mysess->killed==true) {
3459+
unsigned long long effective_wait_timeout = std::min(
3460+
static_cast<unsigned long long>(mysql_thread___wait_timeout),
3461+
static_cast<unsigned long long>(mysess->wait_timeout)
3462+
);
3463+
unsigned long long min_idle = 0;
3464+
min_idle = curtime - (unsigned long long)effective_wait_timeout*1000;
3465+
if (mysess->idle_since < min_idle) {
3466+
unsigned long long sess_time = curtime - mysess->idle_since;
3467+
proxy_warning("Killing client connection %s:%d because inactive for %llums\n", mysess->client_myds->addr.addr, mysess->client_myds->addr.port, sess_time/1000);
34473468
mysess->killed=true;
3469+
3470+
// Increment counter for timeout-terminated connections
3471+
mysess->thread->status_variables.stvar[st_var_timeout_terminated_connections]++;
3472+
}
3473+
if (mysess->killed==true) { // because idle or for any other reason
34483474
MySQL_Data_Stream *tmp_myds=mysess->client_myds;
34493475
int dsidx=tmp_myds->poll_fds_idx;
34503476
//fprintf(stderr,"Removing session %p, DS %p idx %d\n",mysess,tmp_myds,dsidx);
@@ -3841,7 +3867,12 @@ void MySQL_Thread::ProcessAllSessions_MaintenanceLoop(MySQL_Session *sess, unsig
38413867
* @param curtime The current time, in milliseconds.
38423868
* @param sess The MySQL session to handle.
38433869
*/
3844-
if ( (sess_time/1000 > (unsigned long long)mysql_thread___max_transaction_idle_time) || (sess_time/1000 > (unsigned long long)mysql_thread___wait_timeout) ) {
3870+
unsigned long long effective_wait_timeout = std::min(
3871+
static_cast<unsigned long long>(mysql_thread___wait_timeout),
3872+
static_cast<unsigned long long>(sess->wait_timeout)
3873+
);
3874+
if ((sess_time/1000 > static_cast<unsigned long long>(mysql_thread___max_transaction_idle_time)) ||
3875+
(sess_time/1000 > effective_wait_timeout)) {
38453876
//numTrx = sess->NumActiveTransactions();
38463877
numTrx = sess->active_transactions;
38473878
if (numTrx) {
@@ -3854,11 +3885,14 @@ void MySQL_Thread::ProcessAllSessions_MaintenanceLoop(MySQL_Session *sess, unsig
38543885
}
38553886
} else {
38563887
// the session is idle, kill it
3857-
if (sess_time/1000 > (unsigned long long)mysql_thread___wait_timeout) {
3888+
if (sess_time/1000 > effective_wait_timeout) {
38583889
sess->killed=true;
38593890
if (sess->client_myds) {
38603891
proxy_warning("Killing client connection %s:%d because inactive for %llums\n",sess->client_myds->addr.addr,sess->client_myds->addr.port, sess_time/1000);
38613892
}
3893+
3894+
// Increment counter for timeout-terminated connections
3895+
sess->thread->status_variables.stvar[st_var_timeout_terminated_connections]++;
38623896
}
38633897
}
38643898
} else {
@@ -4048,10 +4082,17 @@ void MySQL_Thread::process_all_sessions() {
40484082
#ifdef IDLE_THREADS
40494083
else
40504084
{
4051-
if ( (sess_time/1000 > (unsigned long long)mysql_thread___wait_timeout) ) {
4085+
unsigned long long effective_wait_timeout = std::min(
4086+
static_cast<unsigned long long>(mysql_thread___wait_timeout),
4087+
static_cast<unsigned long long>(sess->wait_timeout)
4088+
);
4089+
if ( (sess_time/1000 > effective_wait_timeout) ) {
40524090
sess->killed=true;
40534091
sess->to_process=1;
40544092
proxy_warning("Killing client connection %s:%d because inactive for %llums\n", sess->client_myds->addr.addr, sess->client_myds->addr.port, sess_time/1000);
4093+
4094+
// Increment counter for timeout-terminated connections
4095+
sess->thread->status_variables.stvar[st_var_timeout_terminated_connections]++;
40554096
}
40564097
}
40574098
#endif // IDLE_THREADS
@@ -5353,6 +5394,16 @@ unsigned long long MySQL_Threads_Handler::get_status_variable(
53535394
q+=__sync_fetch_and_add(&thr->status_variables.stvar[v_idx],0);
53545395
}
53555396
}
5397+
#ifdef IDLE_THREADS
5398+
if (GloVars.global.idle_threads)
5399+
for (i=0;i<num_threads;i++) {
5400+
if (mysql_threads_idles) {
5401+
MySQL_Thread *thr=(MySQL_Thread *)mysql_threads_idles[i].worker;
5402+
if (thr)
5403+
q+=__sync_fetch_and_add(&thr->status_variables.stvar[v_idx],0);
5404+
}
5405+
}
5406+
#endif // IDLE_THREADS
53565407
if (m_idx != p_th_counter::__size) {
53575408
const auto& cur_val = status_variables.p_counter_array[m_idx]->Value();
53585409
double final_val = 0;
@@ -5384,6 +5435,16 @@ unsigned long long MySQL_Threads_Handler::get_status_variable(
53845435
q+=__sync_fetch_and_add(&thr->status_variables.stvar[v_idx],0);
53855436
}
53865437
}
5438+
#ifdef IDLE_THREADS
5439+
if (GloVars.global.idle_threads)
5440+
for (i=0;i<num_threads;i++) {
5441+
if (mysql_threads_idles) {
5442+
MySQL_Thread *thr=(MySQL_Thread *)mysql_threads_idles[i].worker;
5443+
if (thr)
5444+
q+=__sync_fetch_and_add(&thr->status_variables.stvar[v_idx],0);
5445+
}
5446+
}
5447+
#endif // IDLE_THREADS
53875448
if (m_idx != p_th_gauge::__size) {
53885449
double final_val = 0;
53895450

test/tap/groups/groups.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
"test_com_register_slave_enables_fast_forward-t" : [ "default-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
145145
"test_com_reset_connection_com_change_user-t" : [ "default-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
146146
"test_connection_annotation-t" : [ "default-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
147+
"mysql-set_wait_timeout-t" : [ "default-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
147148
"test_csharp_connector_support-t" : [ "default-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
148149
"test_debug_filters-t" : [ "default-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
149150
"test_default_conn_collation-t" : [ "default-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],

0 commit comments

Comments
 (0)