mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
MDEV-15089 Ensure that connection ID is in 32bit range
This commit is contained in:
23
mysql-test/r/thread_id_overflow.result
Normal file
23
mysql-test/r/thread_id_overflow.result
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
connect con1, localhost,root;
|
||||||
|
disconnect con1;
|
||||||
|
connect con2, localhost,root;
|
||||||
|
connection con2;
|
||||||
|
connection default;
|
||||||
|
SET @orig_debug=@@debug_dbug;
|
||||||
|
SET GLOBAL DEBUG_DBUG='+d,thread_id_overflow';
|
||||||
|
connect con3, localhost,root;
|
||||||
|
connection con3;
|
||||||
|
SELECT CONNECTION_ID();
|
||||||
|
CONNECTION_ID()
|
||||||
|
4294967294
|
||||||
|
connection default;
|
||||||
|
SET GLOBAL DEBUG_DBUG=@orig_debug;
|
||||||
|
connect con4, localhost,root;
|
||||||
|
connection con4;
|
||||||
|
select IF(connection_id() - max_id = 1,'Good','Bad') as result;
|
||||||
|
result
|
||||||
|
Good
|
||||||
|
disconnect con4;
|
||||||
|
disconnect con3;
|
||||||
|
disconnect con2;
|
||||||
|
connection default;
|
35
mysql-test/t/thread_id_overflow.test
Normal file
35
mysql-test/t/thread_id_overflow.test
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# test 32bit overflow with connection id
|
||||||
|
--source include/have_debug.inc
|
||||||
|
|
||||||
|
|
||||||
|
# Connect and disconnect once, to create a "hole" in connection ids
|
||||||
|
connect con1, localhost,root;
|
||||||
|
disconnect con1;
|
||||||
|
|
||||||
|
connect con2, localhost,root;
|
||||||
|
connection con2;
|
||||||
|
|
||||||
|
connection default;
|
||||||
|
let $max_id = `SELECT MAX(ID) FROM INFORMATION_SCHEMA.PROCESSLIST`;
|
||||||
|
|
||||||
|
SET @orig_debug=@@debug_dbug;
|
||||||
|
SET GLOBAL DEBUG_DBUG='+d,thread_id_overflow';
|
||||||
|
|
||||||
|
connect con3, localhost,root;
|
||||||
|
connection con3;
|
||||||
|
# next line gives UINT32_MAX -1;
|
||||||
|
SELECT CONNECTION_ID();
|
||||||
|
|
||||||
|
connection default;
|
||||||
|
SET GLOBAL DEBUG_DBUG=@orig_debug;
|
||||||
|
|
||||||
|
connect con4, localhost,root;
|
||||||
|
connection con4;
|
||||||
|
--replace_result $max_id max_id
|
||||||
|
eval select IF(connection_id() - $max_id = 1,'Good','Bad') as result;
|
||||||
|
|
||||||
|
disconnect con4;
|
||||||
|
disconnect con3;
|
||||||
|
disconnect con2;
|
||||||
|
|
||||||
|
connection default;
|
109
sql/mysqld.cc
109
sql/mysqld.cc
@ -559,7 +559,7 @@ ulong max_prepared_stmt_count;
|
|||||||
statements.
|
statements.
|
||||||
*/
|
*/
|
||||||
ulong prepared_stmt_count=0;
|
ulong prepared_stmt_count=0;
|
||||||
my_thread_id global_thread_id= 1;
|
my_thread_id global_thread_id= 0;
|
||||||
ulong current_pid;
|
ulong current_pid;
|
||||||
ulong slow_launch_threads = 0;
|
ulong slow_launch_threads = 0;
|
||||||
uint sync_binlog_period= 0, sync_relaylog_period= 0,
|
uint sync_binlog_period= 0, sync_relaylog_period= 0,
|
||||||
@ -766,6 +766,9 @@ mysql_mutex_t LOCK_stats, LOCK_global_user_client_stats,
|
|||||||
/* This protects against changes in master_info_index */
|
/* This protects against changes in master_info_index */
|
||||||
mysql_mutex_t LOCK_active_mi;
|
mysql_mutex_t LOCK_active_mi;
|
||||||
|
|
||||||
|
/* This protects connection id.*/
|
||||||
|
mysql_mutex_t LOCK_thread_id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The below lock protects access to two global server variables:
|
The below lock protects access to two global server variables:
|
||||||
max_prepared_stmt_count and prepared_stmt_count. These variables
|
max_prepared_stmt_count and prepared_stmt_count. These variables
|
||||||
@ -933,6 +936,7 @@ PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_xid_list,
|
|||||||
key_LOCK_thread_count, key_LOCK_thread_cache,
|
key_LOCK_thread_count, key_LOCK_thread_cache,
|
||||||
key_PARTITION_LOCK_auto_inc;
|
key_PARTITION_LOCK_auto_inc;
|
||||||
PSI_mutex_key key_RELAYLOG_LOCK_index;
|
PSI_mutex_key key_RELAYLOG_LOCK_index;
|
||||||
|
PSI_mutex_key key_LOCK_thread_id;
|
||||||
PSI_mutex_key key_LOCK_slave_state, key_LOCK_binlog_state,
|
PSI_mutex_key key_LOCK_slave_state, key_LOCK_binlog_state,
|
||||||
key_LOCK_rpl_thread, key_LOCK_rpl_thread_pool, key_LOCK_parallel_entry;
|
key_LOCK_rpl_thread, key_LOCK_rpl_thread_pool, key_LOCK_parallel_entry;
|
||||||
|
|
||||||
@ -970,6 +974,7 @@ static PSI_mutex_info all_server_mutexes[]=
|
|||||||
{ &key_hash_filo_lock, "hash_filo::lock", 0},
|
{ &key_hash_filo_lock, "hash_filo::lock", 0},
|
||||||
{ &key_LOCK_active_mi, "LOCK_active_mi", PSI_FLAG_GLOBAL},
|
{ &key_LOCK_active_mi, "LOCK_active_mi", PSI_FLAG_GLOBAL},
|
||||||
{ &key_LOCK_connection_count, "LOCK_connection_count", PSI_FLAG_GLOBAL},
|
{ &key_LOCK_connection_count, "LOCK_connection_count", PSI_FLAG_GLOBAL},
|
||||||
|
{ &key_LOCK_thread_id, "LOCK_thread_id", PSI_FLAG_GLOBAL},
|
||||||
{ &key_LOCK_crypt, "LOCK_crypt", PSI_FLAG_GLOBAL},
|
{ &key_LOCK_crypt, "LOCK_crypt", PSI_FLAG_GLOBAL},
|
||||||
{ &key_LOCK_delayed_create, "LOCK_delayed_create", PSI_FLAG_GLOBAL},
|
{ &key_LOCK_delayed_create, "LOCK_delayed_create", PSI_FLAG_GLOBAL},
|
||||||
{ &key_LOCK_delayed_insert, "LOCK_delayed_insert", PSI_FLAG_GLOBAL},
|
{ &key_LOCK_delayed_insert, "LOCK_delayed_insert", PSI_FLAG_GLOBAL},
|
||||||
@ -2048,8 +2053,8 @@ pthread_handler_t kill_server_thread(void *arg __attribute__((unused)))
|
|||||||
extern "C" sig_handler print_signal_warning(int sig)
|
extern "C" sig_handler print_signal_warning(int sig)
|
||||||
{
|
{
|
||||||
if (global_system_variables.log_warnings)
|
if (global_system_variables.log_warnings)
|
||||||
sql_print_warning("Got signal %d from thread %ld", sig,
|
sql_print_warning("Got signal %d from thread %u", sig,
|
||||||
(ulong) my_thread_id());
|
my_thread_id());
|
||||||
#ifdef SIGNAL_HANDLER_RESET_ON_DELIVERY
|
#ifdef SIGNAL_HANDLER_RESET_ON_DELIVERY
|
||||||
my_sigset(sig,print_signal_warning); /* int. thread system calls */
|
my_sigset(sig,print_signal_warning); /* int. thread system calls */
|
||||||
#endif
|
#endif
|
||||||
@ -2335,6 +2340,7 @@ static void clean_up_mutexes()
|
|||||||
mysql_mutex_destroy(&LOCK_crypt);
|
mysql_mutex_destroy(&LOCK_crypt);
|
||||||
mysql_mutex_destroy(&LOCK_user_conn);
|
mysql_mutex_destroy(&LOCK_user_conn);
|
||||||
mysql_mutex_destroy(&LOCK_connection_count);
|
mysql_mutex_destroy(&LOCK_connection_count);
|
||||||
|
mysql_mutex_destroy(&LOCK_thread_id);
|
||||||
mysql_mutex_destroy(&LOCK_stats);
|
mysql_mutex_destroy(&LOCK_stats);
|
||||||
mysql_mutex_destroy(&LOCK_global_user_client_stats);
|
mysql_mutex_destroy(&LOCK_global_user_client_stats);
|
||||||
mysql_mutex_destroy(&LOCK_global_table_stats);
|
mysql_mutex_destroy(&LOCK_global_table_stats);
|
||||||
@ -4690,6 +4696,8 @@ static int init_thread_environment()
|
|||||||
&LOCK_short_uuid_generator, MY_MUTEX_INIT_FAST);
|
&LOCK_short_uuid_generator, MY_MUTEX_INIT_FAST);
|
||||||
mysql_mutex_init(key_LOCK_connection_count,
|
mysql_mutex_init(key_LOCK_connection_count,
|
||||||
&LOCK_connection_count, MY_MUTEX_INIT_FAST);
|
&LOCK_connection_count, MY_MUTEX_INIT_FAST);
|
||||||
|
mysql_mutex_init(key_LOCK_thread_id,
|
||||||
|
&LOCK_thread_id, MY_MUTEX_INIT_FAST);
|
||||||
mysql_mutex_init(key_LOCK_stats, &LOCK_stats, MY_MUTEX_INIT_FAST);
|
mysql_mutex_init(key_LOCK_stats, &LOCK_stats, MY_MUTEX_INIT_FAST);
|
||||||
mysql_mutex_init(key_LOCK_global_user_client_stats,
|
mysql_mutex_init(key_LOCK_global_user_client_stats,
|
||||||
&LOCK_global_user_client_stats, MY_MUTEX_INIT_FAST);
|
&LOCK_global_user_client_stats, MY_MUTEX_INIT_FAST);
|
||||||
@ -8760,7 +8768,7 @@ static int mysql_init_variables(void)
|
|||||||
denied_connections= 0;
|
denied_connections= 0;
|
||||||
executed_events= 0;
|
executed_events= 0;
|
||||||
global_query_id= 1;
|
global_query_id= 1;
|
||||||
global_thread_id= 1;
|
global_thread_id= 0;
|
||||||
strnmov(server_version, MYSQL_SERVER_VERSION, sizeof(server_version)-1);
|
strnmov(server_version, MYSQL_SERVER_VERSION, sizeof(server_version)-1);
|
||||||
threads.empty();
|
threads.empty();
|
||||||
thread_cache.empty();
|
thread_cache.empty();
|
||||||
@ -10420,3 +10428,96 @@ void init_server_psi_keys(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif /* HAVE_PSI_INTERFACE */
|
#endif /* HAVE_PSI_INTERFACE */
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Connection ID allocation.
|
||||||
|
|
||||||
|
We need to maintain thread_ids in the 32bit range,
|
||||||
|
because this is how it is passed to the client in the protocol.
|
||||||
|
|
||||||
|
The idea is to maintain a id range, initially set to
|
||||||
|
(0,UINT32_MAX). Whenever new id is needed, we increment the
|
||||||
|
lower limit and return its new value.
|
||||||
|
|
||||||
|
On "overflow", if id can not be generated anymore(i.e lower == upper -1),
|
||||||
|
we recalculate the range boundaries.
|
||||||
|
To do that, we first collect thread ids that are in use, by traversing
|
||||||
|
THD list, and find largest region within (0,UINT32_MAX), that is still free.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
static my_thread_id thread_id_max= UINT_MAX32;
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Find largest unused thread_id range.
|
||||||
|
|
||||||
|
i.e for every number N within the returned range,
|
||||||
|
there is no existing connection with thread_id equal to N.
|
||||||
|
|
||||||
|
The range is exclusive, lower bound is always >=0 and
|
||||||
|
upper bound <=MAX_UINT32.
|
||||||
|
|
||||||
|
@param[out] low - lower bound for the range
|
||||||
|
@param[out] high - upper bound for the range
|
||||||
|
*/
|
||||||
|
static void recalculate_thread_id_range(my_thread_id *low, my_thread_id *high)
|
||||||
|
{
|
||||||
|
std::vector<my_thread_id> ids;
|
||||||
|
|
||||||
|
// Add sentinels
|
||||||
|
ids.push_back(0);
|
||||||
|
ids.push_back(UINT_MAX32);
|
||||||
|
|
||||||
|
mysql_mutex_lock(&LOCK_thread_count);
|
||||||
|
|
||||||
|
I_List_iterator<THD> it(threads);
|
||||||
|
THD *thd;
|
||||||
|
while ((thd=it++))
|
||||||
|
ids.push_back(thd->thread_id);
|
||||||
|
|
||||||
|
mysql_mutex_unlock(&LOCK_thread_count);
|
||||||
|
|
||||||
|
std::sort(ids.begin(), ids.end());
|
||||||
|
my_thread_id max_gap= 0;
|
||||||
|
for (size_t i= 0; i < ids.size() - 1; i++)
|
||||||
|
{
|
||||||
|
my_thread_id gap= ids[i+1] - ids[i];
|
||||||
|
if (gap > max_gap)
|
||||||
|
{
|
||||||
|
*low= ids[i];
|
||||||
|
*high= ids[i+1];
|
||||||
|
max_gap= gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_gap < 2)
|
||||||
|
{
|
||||||
|
/* Can't find free id. This is not really possible,
|
||||||
|
we'd need 2^32 connections for this to happen.*/
|
||||||
|
sql_print_error("Cannot find free connection id.");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
my_thread_id next_thread_id(void)
|
||||||
|
{
|
||||||
|
my_thread_id retval;
|
||||||
|
DBUG_EXECUTE_IF("thread_id_overflow", global_thread_id= thread_id_max-2;);
|
||||||
|
|
||||||
|
mysql_mutex_lock(&LOCK_thread_id);
|
||||||
|
|
||||||
|
if (unlikely(global_thread_id == thread_id_max - 1))
|
||||||
|
{
|
||||||
|
recalculate_thread_id_range(&global_thread_id, &thread_id_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
retval= ++global_thread_id;
|
||||||
|
|
||||||
|
mysql_mutex_unlock(&LOCK_thread_id);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
10
sql/mysqld.h
10
sql/mysqld.h
@ -722,15 +722,7 @@ inline query_id_t get_query_id()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* increment global_thread_id and return it. */
|
/* increment global_thread_id and return it. */
|
||||||
inline __attribute__((warn_unused_result)) my_thread_id next_thread_id()
|
extern __attribute__((warn_unused_result)) my_thread_id next_thread_id(void);
|
||||||
{
|
|
||||||
return my_atomic_add64_explicit((int64*) &global_thread_id, 1, MY_MEMORY_ORDER_RELAXED);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(MYSQL_DYNAMIC_PLUGIN) && defined(_WIN32)
|
|
||||||
extern "C" my_thread_id next_thread_id_noinline();
|
|
||||||
#define next_thread_id() next_thread_id_noinline()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO: Replace this with an inline function.
|
TODO: Replace this with an inline function.
|
||||||
|
Reference in New Issue
Block a user