Skip to content

Commit 3b633ba

Browse files
author
Ajo Robert
committed
Bug #20201006 spamming show processlist prevents old
connection threads from cleaning up. Analysis ========= Issue here is, delay in connection cleanup for which global connection counter is already decremented, makes room for new connections. Hence more than expected connections are observed in the server. Connections running statement "SHOW PROCESSLIST" or "SELECT on INFORMATION_SCHEMA.PROCESSLIST" acquires mutex LOCK_thd_remove for reading information of all the connections in server. Connections in cleanup phase, acquires mutex to remove thread from global thread list. Many such concurrent connections increases contention on mutex LOCK_thd_remove. In connection cleanup phase, connection count is decreased first and then the thd is removed from global thd list. This order makes new connection (above max_connections) possible while existing connections removal is still pending because of mutex LOCK_thd_remove being held by show processlist. Fix: ===== In connection clean phase, thd is removed from the global thd list first and then global connection count is decremented. Added code to wait for connection_count be be zero during shutdown to ensure connection threads are done with their task.
1 parent 30fba0a commit 3b633ba

File tree

7 files changed

+158
-6
lines changed

7 files changed

+158
-6
lines changed

mysql-test/r/connect_debug.result

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
# -- Bug#20201006: Spamming show processlist prevents old connection
3+
# -- threads from cleaning up.
4+
SET @saved_max_connections = @@global.max_connections;
5+
SET GLOBAL max_connections = 2;
6+
7+
# -- Check that we allow only max_connections + 1 connections here
8+
connect con_1, localhost, root;
9+
connect con_2, localhost, root;
10+
connect(localhost,root,,test,MYSQL_PORT,MYSQL_SOCK);
11+
connect con_3, localhost, root;
12+
ERROR HY000: Too many connections
13+
14+
# -- Ensure we have max_connections + 1 connections.
15+
SELECT count(*)= @@global.max_connections + 1 FROM information_schema.processlist;
16+
count(*)= @@global.max_connections + 1
17+
1
18+
19+
# -- Take LOCK_thd_remove and close one connection then
20+
# attempt new one [should fail]...
21+
SET DEBUG_SYNC='fill_schema_processlist_after_copying_threads SIGNAL disconnect_connection WAIT_FOR continue';
22+
SELECT user FROM INFORMATION_SCHEMA.PROCESSLIST GROUP BY user;;
23+
connection default;
24+
SET DEBUG_SYNC='now WAIT_FOR disconnect_connection';
25+
disconnect con_1;
26+
connect(localhost,root,,test,MYSQL_PORT,MYSQL_SOCK);
27+
connect con_3, localhost, root;
28+
ERROR HY000: Too many connections
29+
30+
# -- Release the lock. Now new connection should go through
31+
SET DEBUG_SYNC='now SIGNAL continue';
32+
connection con_2;
33+
user
34+
root
35+
SET DEBUG_SYNC='RESET';
36+
37+
# -- Waiting for connection to close...
38+
connect con_3, localhost, root;
39+
40+
# -- Closing connections...
41+
disconnect con_3;
42+
disconnect con_2;
43+
connection default;
44+
45+
# -- Resetting variables...
46+
SET GLOBAL max_connections= @saved_max_connections;
47+
48+
# -- End of Bug#20201006.
49+

mysql-test/suite/perfschema/r/dml_setup_instruments.result

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ where name like 'Wait/Synch/Cond/sql/%'
3636
'wait/synch/cond/sql/DEBUG_SYNC::cond')
3737
order by name limit 10;
3838
NAME ENABLED TIMED
39+
wait/synch/cond/sql/COND_connection_count YES YES
3940
wait/synch/cond/sql/COND_flush_thread_cache YES YES
4041
wait/synch/cond/sql/COND_manager YES YES
4142
wait/synch/cond/sql/COND_queue_state YES YES
@@ -45,7 +46,6 @@ wait/synch/cond/sql/COND_thread_count YES YES
4546
wait/synch/cond/sql/Delayed_insert::cond YES YES
4647
wait/synch/cond/sql/Delayed_insert::cond_client YES YES
4748
wait/synch/cond/sql/Event_scheduler::COND_state YES YES
48-
wait/synch/cond/sql/Gtid_state YES YES
4949
select * from performance_schema.setup_instruments
5050
where name='Wait';
5151
select * from performance_schema.setup_instruments

mysql-test/t/connect_debug.test

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
2+
# This test makes no sense with the embedded server
3+
--source include/not_embedded.inc
4+
5+
--source include/have_debug_sync.inc
6+
7+
# Save the initial number of concurrent sessions
8+
--source include/count_sessions.inc
9+
10+
--echo
11+
--echo # -- Bug#20201006: Spamming show processlist prevents old connection
12+
--echo # -- threads from cleaning up.
13+
14+
--enable_connect_log
15+
SET @saved_max_connections = @@global.max_connections;
16+
SET GLOBAL max_connections = 2;
17+
18+
--echo
19+
--echo # -- Check that we allow only max_connections + 1 connections here
20+
--connect (con_1, localhost, root)
21+
--connect (con_2, localhost, root)
22+
--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK
23+
--error ER_CON_COUNT_ERROR
24+
--connect (con_3, localhost, root)
25+
26+
--echo
27+
--echo # -- Ensure we have max_connections + 1 connections.
28+
SELECT count(*)= @@global.max_connections + 1 FROM information_schema.processlist;
29+
30+
--echo
31+
--echo # -- Take LOCK_thd_remove and close one connection then
32+
--echo # attempt new one [should fail]...
33+
SET DEBUG_SYNC='fill_schema_processlist_after_copying_threads SIGNAL disconnect_connection WAIT_FOR continue';
34+
--send SELECT user FROM INFORMATION_SCHEMA.PROCESSLIST GROUP BY user;
35+
36+
--connection default
37+
SET DEBUG_SYNC='now WAIT_FOR disconnect_connection';
38+
--disconnect con_1
39+
40+
--replace_result $MASTER_MYPORT MYSQL_PORT $MASTER_MYSOCK MYSQL_SOCK
41+
--error ER_CON_COUNT_ERROR
42+
--connect (con_3, localhost, root)
43+
44+
--echo
45+
--echo # -- Release the lock. Now new connection should go through
46+
SET DEBUG_SYNC='now SIGNAL continue';
47+
--connection con_2
48+
reap;
49+
50+
SET DEBUG_SYNC='RESET';
51+
52+
--echo
53+
--echo # -- Waiting for connection to close...
54+
let $wait_condition =
55+
SELECT COUNT(*) = 2
56+
FROM information_schema.processlist;
57+
--source include/wait_condition.inc
58+
59+
--connect (con_3, localhost, root)
60+
61+
--echo
62+
--echo # -- Closing connections...
63+
--disconnect con_3
64+
--disconnect con_2
65+
--source include/wait_until_disconnected.inc
66+
67+
--connection default
68+
69+
--echo
70+
--echo # -- Resetting variables...
71+
SET GLOBAL max_connections= @saved_max_connections;
72+
73+
--disable_connect_log
74+
75+
--echo
76+
--echo # -- End of Bug#20201006.
77+
--echo
78+
79+
# Wait till all disconnects are completed
80+
--source include/wait_until_count_sessions.inc

sql/mysqld.cc

+23-3
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,7 @@ struct st_VioSSLFd *ssl_acceptor_fd;
12651265
LOCK_connection_count.
12661266
*/
12671267
uint connection_count= 0;
1268+
mysql_cond_t COND_connection_count;
12681269

12691270
/* Function declarations */
12701271

@@ -1544,6 +1545,17 @@ static void close_connections(void)
15441545
}
15451546
mysql_mutex_unlock(&LOCK_thread_count);
15461547

1548+
/*
1549+
Connection threads might take a little while to go down after removing from
1550+
global thread list. Give it some time.
1551+
*/
1552+
mysql_mutex_lock(&LOCK_connection_count);
1553+
while (connection_count > 0)
1554+
{
1555+
mysql_cond_wait(&COND_connection_count, &LOCK_connection_count);
1556+
}
1557+
mysql_mutex_unlock(&LOCK_connection_count);
1558+
15471559
close_active_mi();
15481560
DBUG_PRINT("quit",("close_connections thread"));
15491561
DBUG_VOID_RETURN;
@@ -2020,6 +2032,7 @@ static void clean_up_mutexes()
20202032
mysql_cond_destroy(&COND_thread_cache);
20212033
mysql_cond_destroy(&COND_flush_thread_cache);
20222034
mysql_cond_destroy(&COND_manager);
2035+
mysql_cond_destroy(&COND_connection_count);
20232036
}
20242037
#endif /*EMBEDDED_LIBRARY*/
20252038

@@ -2642,6 +2655,8 @@ void dec_connection_count()
26422655
{
26432656
mysql_mutex_lock(&LOCK_connection_count);
26442657
--connection_count;
2658+
if (connection_count == 0)
2659+
mysql_cond_signal(&COND_connection_count);
26452660
mysql_mutex_unlock(&LOCK_connection_count);
26462661
}
26472662

@@ -2749,8 +2764,8 @@ bool one_thread_per_connection_end(THD *thd, bool block_pthread)
27492764
DBUG_PRINT("info", ("thd %p block_pthread %d", thd, (int) block_pthread));
27502765

27512766
thd->release_resources();
2752-
dec_connection_count();
27532767
remove_global_thread(thd);
2768+
dec_connection_count();
27542769
if (kill_blocked_pthreads_flag)
27552770
{
27562771
// Do not block if we are about to shut down
@@ -4241,6 +4256,7 @@ static int init_thread_environment()
42414256
mysql_rwlock_init(key_rwlock_LOCK_sys_init_slave, &LOCK_sys_init_slave);
42424257
mysql_rwlock_init(key_rwlock_LOCK_grant, &LOCK_grant);
42434258
mysql_cond_init(key_COND_thread_count, &COND_thread_count, NULL);
4259+
mysql_cond_init(key_COND_connection_count, &COND_connection_count, NULL);
42444260
mysql_cond_init(key_COND_thread_cache, &COND_thread_cache, NULL);
42454261
mysql_cond_init(key_COND_flush_thread_cache, &COND_flush_thread_cache, NULL);
42464262
mysql_cond_init(key_COND_manager, &COND_manager, NULL);
@@ -6077,6 +6093,8 @@ void create_thread_to_handle_connection(THD *thd)
60776093

60786094
mysql_mutex_lock(&LOCK_connection_count);
60796095
--connection_count;
6096+
if (connection_count == 0)
6097+
mysql_cond_signal(&COND_connection_count);
60806098
mysql_mutex_unlock(&LOCK_connection_count);
60816099

60826100
statistic_increment(aborted_connects,&LOCK_status);
@@ -9569,7 +9587,8 @@ PSI_cond_key key_BINLOG_update_cond,
95699587
key_relay_log_info_sleep_cond, key_cond_slave_parallel_pend_jobs,
95709588
key_cond_slave_parallel_worker,
95719589
key_TABLE_SHARE_cond, key_user_level_lock_cond,
9572-
key_COND_thread_count, key_COND_thread_cache, key_COND_flush_thread_cache;
9590+
key_COND_thread_count, key_COND_thread_cache, key_COND_flush_thread_cache,
9591+
key_COND_connection_count;
95739592
PSI_cond_key key_RELAYLOG_update_cond;
95749593
PSI_cond_key key_BINLOG_COND_done;
95759594
PSI_cond_key key_RELAYLOG_COND_done;
@@ -9615,7 +9634,8 @@ static PSI_cond_info all_server_conds[]=
96159634
{ &key_COND_thread_count, "COND_thread_count", PSI_FLAG_GLOBAL},
96169635
{ &key_COND_thread_cache, "COND_thread_cache", PSI_FLAG_GLOBAL},
96179636
{ &key_COND_flush_thread_cache, "COND_flush_thread_cache", PSI_FLAG_GLOBAL},
9618-
{ &key_gtid_ensure_index_cond, "Gtid_state", PSI_FLAG_GLOBAL}
9637+
{ &key_gtid_ensure_index_cond, "Gtid_state", PSI_FLAG_GLOBAL},
9638+
{ &key_COND_connection_count, "COND_connection_count", PSI_FLAG_GLOBAL}
96199639
};
96209640

96219641
PSI_thread_key key_thread_bootstrap, key_thread_delayed_insert,

sql/mysqld.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,8 @@ extern PSI_cond_key key_BINLOG_update_cond,
383383
key_relay_log_info_sleep_cond, key_cond_slave_parallel_pend_jobs,
384384
key_cond_slave_parallel_worker,
385385
key_TABLE_SHARE_cond, key_user_level_lock_cond,
386-
key_COND_thread_count, key_COND_thread_cache, key_COND_flush_thread_cache;
386+
key_COND_thread_count, key_COND_thread_cache, key_COND_flush_thread_cache,
387+
key_COND_connection_count;
387388
extern PSI_cond_key key_BINLOG_COND_done;
388389
extern PSI_cond_key key_RELAYLOG_COND_done;
389390
extern PSI_cond_key key_RELAYLOG_update_cond;

sql/scheduler.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
static bool no_threads_end(THD *thd, bool put_in_cache)
3535
{
3636
thd_release_resources(thd);
37-
dec_connection_count();
3837
remove_global_thread(thd);
38+
dec_connection_count();
3939
// THD is an incomplete type here, so use destroy_thd() to delete it.
4040
destroy_thd(thd);
4141

sql/sql_show.cc

+2
Original file line numberDiff line numberDiff line change
@@ -2222,6 +2222,8 @@ int fill_schema_processlist(THD* thd, TABLE_LIST* tables, Item* cond)
22222222
mysql_mutex_lock(&LOCK_thd_remove);
22232223
copy_global_thread_list(&global_thread_list_copy);
22242224

2225+
DEBUG_SYNC(thd,"fill_schema_processlist_after_copying_threads");
2226+
22252227
Thread_iterator it= global_thread_list_copy.begin();
22262228
Thread_iterator end= global_thread_list_copy.end();
22272229
for (; it != end; ++it)

0 commit comments

Comments
 (0)