diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index af2617ebe071..58d4b3a31ac1 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -219,6 +219,7 @@ IF (INSTALL_EXTRA_HEADERS) ../sql/sql_bitmap.h ../sql/sql_cmd.h ../sql/sql_const.h + ../sql/sql_db.h ../sql/sql_error.h ../sql/sql_list.h ../sql/sql_plist.h diff --git a/mysql-test/include/have_thread_pool_plugin.inc b/mysql-test/include/have_thread_pool_plugin.inc new file mode 100644 index 000000000000..1bf120960d59 --- /dev/null +++ b/mysql-test/include/have_thread_pool_plugin.inc @@ -0,0 +1,9 @@ +disable_query_log; + +--let $thread_pool_plugin_installed= `SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'THREAD_POOL'` +if (!$thread_pool_plugin_installed) +{ + --skip Use --thread-pool to run this test +} + +enable_query_log; diff --git a/mysql-test/suite/thread_pool/r/max_db_connections.result b/mysql-test/suite/thread_pool/r/max_db_connections.result new file mode 100644 index 000000000000..d5f6d286876d --- /dev/null +++ b/mysql-test/suite/thread_pool/r/max_db_connections.result @@ -0,0 +1,306 @@ +== Setup +create database test_db; +create user test_user@localhost; +grant all on test.* to test_user@localhost; +grant all on test_db.* to test_user@localhost; +use test_db; +create user super_user@localhost; +grant all on *.* to super_user@localhost with grant option; +SET @start_value = @@global.thread_pool_max_db_connections; +SET @@global.thread_pool_max_db_connections = 10; +SELECT @@global.thread_pool_max_db_connections; +@@global.thread_pool_max_db_connections +10 +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' and connections <> 0 order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +connection default; +== Fill up thread_pool_max_db_connections +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +== New non-admin connection will be rejected +ERROR HY000: Maximum connections reached for `test on localhost` +== Existing connection can switch to same db, another db or empty db +connection con10; +use test_db; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 9 1 +test_db 0 0 0 0 1 0 +use test; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 1 +test_db 0 0 0 0 0 0 +use test; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 1 +test_db 0 0 0 0 0 0 +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 9 1 +test_db 0 0 0 0 1 0 +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 1 +test_db 0 0 0 0 0 0 +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 9 1 +test_db 0 0 0 0 0 0 +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 9 1 +test_db 0 0 0 0 0 0 +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 1 +test_db 0 0 0 0 0 0 +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 1 +test_db 0 0 0 0 0 0 +== Admin user connection is not limited by thread_pool_max_db_connections +connect con_root, localhost, root,,test; +connection con_root; +SELECT @@global.thread_pool_max_db_connections; +@@global.thread_pool_max_db_connections +10 +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 1 +test_db 0 0 0 0 0 0 +disconnect con_root; +connection default; +== Test another admin super_user +connect con_super, localhost, super_user,,test; +connection con_super; +SELECT @@global.thread_pool_max_db_connections; +@@global.thread_pool_max_db_connections +10 +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 1 +test_db 0 0 0 0 0 0 +== Change admin user to regular user on new connection will fail +== because thread_pool_max_db_connections is already reached +mysqltest: At line 1: Query 'change_user test_user,,test' failed. +ERROR 50039 (HY000): Maximum connections reached for `test on localhost` +== Change user to root is OK +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 2 +test_db 0 0 0 0 0 0 +disconnect con_super; +== Change regular user to root will free up a connection +== so we will be able to connect another regular user +connection con10; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 9 2 +test_db 0 0 0 0 0 0 +connect con11, localhost, test_user,,test; +disconnect con11; +== Change con10 back to regular user +connection con10; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 2 +test_db 0 0 0 0 0 0 +== No new regular connection can be accepted +ERROR HY000: Maximum connections reached for `test on localhost` +== Connections to test_db independently can reach thread_pool_max_db_connections +connect con2_$i, localhost, test_user,,test_db; +connect con2_$i, localhost, test_user,,test_db; +connect con2_$i, localhost, test_user,,test_db; +connect con2_$i, localhost, test_user,,test_db; +connect con2_$i, localhost, test_user,,test_db; +connect con2_$i, localhost, test_user,,test_db; +connect con2_$i, localhost, test_user,,test_db; +connect con2_$i, localhost, test_user,,test_db; +connect con2_$i, localhost, test_user,,test_db; +connect con2_$i, localhost, test_user,,test_db; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 3 +test_db 0 0 0 0 10 0 +== New non-admin connection to test_db will be rejected +ERROR HY000: Maximum connections reached for `test_db on localhost` +== Use test_db that reached limit should fail +connection con10; +use test_db; +ERROR HY000: Maximum connections reached for `test_db on localhost` +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 3 +test_db 0 0 0 0 10 2 +== Change_user to test_db that reached limit should fail +connection default; +disconnect con10; +mysqltest: At line 1: Query 'change_user test_user,,test_db' failed. +ERROR 50039 (HY000): Maximum connections reached for `test_db on localhost` +connect con10, localhost, test_user,,test; +== Connections with no db are not limited by thread_pool_max_db_connections +connection default; +connect con3_$i, localhost, test_user,,*NO-ONE*; +connect con3_$i, localhost, test_user,,*NO-ONE*; +connect con3_$i, localhost, test_user,,*NO-ONE*; +connect con3_$i, localhost, test_user,,*NO-ONE*; +connect con3_$i, localhost, test_user,,*NO-ONE*; +connect con3_$i, localhost, test_user,,*NO-ONE*; +connect con3_$i, localhost, test_user,,*NO-ONE*; +connect con3_$i, localhost, test_user,,*NO-ONE*; +connect con3_$i, localhost, test_user,,*NO-ONE*; +connect con3_$i, localhost, test_user,,*NO-ONE*; +connect con3_$i, localhost, test_user,,*NO-ONE*; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 3 +test_db 0 0 0 0 10 3 +disconnect con3_11; +== Decrement user connection counts +connection default; +disconnect con10; +disconnect con2_10; +disconnect con3_10; +disconnect con9; +disconnect con2_9; +disconnect con3_9; +disconnect con8; +disconnect con2_8; +disconnect con3_8; +disconnect con7; +disconnect con2_7; +disconnect con3_7; +disconnect con6; +disconnect con2_6; +disconnect con3_6; +disconnect con5; +disconnect con2_5; +disconnect con3_5; +disconnect con4; +disconnect con2_4; +disconnect con3_4; +disconnect con3; +disconnect con2_3; +disconnect con3_3; +disconnect con2; +disconnect con2_2; +disconnect con3_2; +disconnect con1; +disconnect con2_1; +disconnect con3_1; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 0 3 +test_db 0 0 0 0 0 3 +== Verify that counter is not affected when db doesn't exist or access is denied +ERROR 42000: Access denied for user 'test_user'@'localhost' to database 'bogus_db' +ERROR 42000: Access denied for user 'test_user'@'localhost' to database 'bogus_db' +ERROR 42000: Access denied for user 'test_user'@'localhost' to database 'bogus_db' +ERROR 42000: Access denied for user 'test_user'@'localhost' to database 'bogus_db' +ERROR 42000: Access denied for user 'test_user'@'localhost' to database 'bogus_db' +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 0 3 +test_db 0 0 0 0 0 3 +== Able to refill up thread_pool_max_db_connections +connection default; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 10 3 +test_db 0 0 0 0 0 3 +ERROR HY000: Maximum connections reached for `test on localhost` +== Increase thread_pool_max_db_connections +connection default; +SET @@global.thread_pool_max_db_connections = 15; +SELECT @@global.thread_pool_max_db_connections; +@@global.thread_pool_max_db_connections +15 +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +connect con$i, localhost, test_user,,test; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 15 4 +test_db 0 0 0 0 0 3 +ERROR HY000: Maximum connections reached for `test on localhost` +== Decrease thread_pool_max_db_connections +connection default; +SET @@global.thread_pool_max_db_connections = 5; +SELECT @@global.thread_pool_max_db_connections; +@@global.thread_pool_max_db_connections +5 +disconnect con15; +disconnect con14; +disconnect con13; +disconnect con12; +disconnect con11; +disconnect con10; +disconnect con9; +disconnect con8; +disconnect con7; +disconnect con6; +disconnect con5; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 4 5 +test_db 0 0 0 0 0 3 +connect con5, localhost, test_user,,test; +ERROR HY000: Maximum connections reached for `test on localhost` +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 5 6 +test_db 0 0 0 0 0 3 +connection default; +disconnect con5; +== Drop database with connections +connect con2_1, localhost, test_user,,test_db; +connect con2_2, localhost, test_user,,test_db; +drop database test_db; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 4 6 +use test; +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 5 6 +connection default; +disconnect con2_1; +disconnect con2_2; +== Cleanup +connection default; +SET @@global.thread_pool_max_db_connections = @start_value; +SELECT @@global.thread_pool_max_db_connections; +@@global.thread_pool_max_db_connections +0 +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test 0 0 0 0 4 6 +rejected_connections = 9 +drop user test_user@localhost; +drop user super_user@localhost; +disconnect con4; +disconnect con3; +disconnect con2; +disconnect con1; diff --git a/mysql-test/suite/thread_pool/r/max_db_connections_stress.result b/mysql-test/suite/thread_pool/r/max_db_connections_stress.result new file mode 100644 index 000000000000..a20b9a47e71d --- /dev/null +++ b/mysql-test/suite/thread_pool/r/max_db_connections_stress.result @@ -0,0 +1,45 @@ +create user test_user@localhost identified with 'mysql_native_password' BY ''; +grant all on test.* to test_user@localhost; +create database test_db10; +grant all on test_db10.* to test_user@localhost; +use test_db10; +create database test_db9; +grant all on test_db9.* to test_user@localhost; +use test_db9; +create database test_db8; +grant all on test_db8.* to test_user@localhost; +use test_db8; +create database test_db7; +grant all on test_db7.* to test_user@localhost; +use test_db7; +create database test_db6; +grant all on test_db6.* to test_user@localhost; +use test_db6; +create database test_db5; +grant all on test_db5.* to test_user@localhost; +use test_db5; +create database test_db4; +grant all on test_db4.* to test_user@localhost; +use test_db4; +create database test_db3; +grant all on test_db3.* to test_user@localhost; +use test_db3; +create database test_db2; +grant all on test_db2.* to test_user@localhost; +use test_db2; +create database test_db1; +grant all on test_db1.* to test_user@localhost; +use test_db1; +drop database test_db10; +drop database test_db9; +drop database test_db8; +drop database test_db7; +drop database test_db6; +drop database test_db5; +drop database test_db4; +drop database test_db3; +drop database test_db2; +drop database test_db1; +drop user test_user@localhost; +errors present +1 diff --git a/mysql-test/suite/thread_pool/t/max_db_connections.test b/mysql-test/suite/thread_pool/t/max_db_connections.test new file mode 100644 index 000000000000..55e45c51667b --- /dev/null +++ b/mysql-test/suite/thread_pool/t/max_db_connections.test @@ -0,0 +1,269 @@ +--source include/have_thread_pool_plugin.inc + +--echo == Setup +create database test_db; +create user test_user@localhost; +grant all on test.* to test_user@localhost; +grant all on test_db.* to test_user@localhost; +use test_db; + +create user super_user@localhost; +grant all on *.* to super_user@localhost with grant option; + +let $ac_entities_query=select * from information_schema.tp_admission_control_entities where schema_name like 'test%' order by schema_name; + +let $start_rejected_connections = query_get_value(show global status like "Thread_pool_admission_control_rejected_connections", Value, 1); + +SET @start_value = @@global.thread_pool_max_db_connections; +SET @@global.thread_pool_max_db_connections = 10; +SELECT @@global.thread_pool_max_db_connections; +# Ideally, we'd be able to avoid repeating most the query in ac_entities_query. +select * from information_schema.tp_admission_control_entities where schema_name like 'test%' and connections <> 0 order by schema_name; + +enable_connect_log; +connection default; + +--echo == Fill up thread_pool_max_db_connections +let $i = 10; +while ($i) +{ + connect (con$i, localhost, test_user,,test); + dec $i; +} + +--echo == New non-admin connection will be rejected +disable_query_log; +--error ER_MULTI_TENANCY_MAX_CONNECTION +connect (con11, localhost, test_user,,test); +enable_query_log; + +--echo == Existing connection can switch to same db, another db or empty db +connection con10; +use test_db; +eval $ac_entities_query; +use test; +eval $ac_entities_query; +use test; +eval $ac_entities_query; +change_user test_user,,test_db; +eval $ac_entities_query; +change_user test_user,,test; +eval $ac_entities_query; +change_user test_user,,; +eval $ac_entities_query; +change_user test_user,,; +eval $ac_entities_query; +change_user test_user,,test; +eval $ac_entities_query; +change_user test_user,,test; +eval $ac_entities_query; + +--echo == Admin user connection is not limited by thread_pool_max_db_connections +connect (con_root, localhost, root,,test); +connection con_root; +SELECT @@global.thread_pool_max_db_connections; +eval $ac_entities_query; + +disconnect con_root; +connection default; + +--echo == Test another admin super_user +connect (con_super, localhost, super_user,,test); +connection con_super; +SELECT @@global.thread_pool_max_db_connections; +eval $ac_entities_query; + +--echo == Change admin user to regular user on new connection will fail +--echo == because thread_pool_max_db_connections is already reached +--error 1 +--exec echo "--change_user test_user,,test" | $MYSQL_TEST 2>&1 + +--echo == Change user to root is OK +change_user root,,test; +eval $ac_entities_query; +disconnect con_super; + +--echo == Change regular user to root will free up a connection +--echo == so we will be able to connect another regular user +connection con10; +change_user root,,test; +eval $ac_entities_query; +connect (con11, localhost, test_user,,test); +disconnect con11; + +--echo == Change con10 back to regular user +connection con10; +# wait for con11 to be disconnected +let $wait_condition= select count(*)=9 from information_schema.processlist where user='test_user'; +source include/wait_condition.inc; +# now change user in con10 +change_user test_user,,test; +eval $ac_entities_query; +disable_query_log; + +--echo == No new regular connection can be accepted +--error ER_MULTI_TENANCY_MAX_CONNECTION +connect (con11, localhost, test_user,,test); +enable_query_log; + +--echo == Connections to test_db independently can reach thread_pool_max_db_connections +let $i = 10; +while ($i) +{ + connect (con2_$i, localhost, test_user,,test_db); + dec $i; +} +eval $ac_entities_query; + +--echo == New non-admin connection to test_db will be rejected +disable_query_log; +--error ER_MULTI_TENANCY_MAX_CONNECTION +connect (con2_11, localhost, test_user,,test_db); +enable_query_log; + +--echo == Use test_db that reached limit should fail +connection con10; +--error ER_MULTI_TENANCY_MAX_CONNECTION +use test_db; +eval $ac_entities_query; + +--echo == Change_user to test_db that reached limit should fail +# The only way to test error of change_user is from external connection +# otherwise it cannot be caught here with 'error' statement. +# Free up con10 for it. +connection default; +disconnect con10; +# Wait for con10 to go away +let $wait_condition = select count(*) = 19 from information_schema.processlist where user = 'test_user'; +source include/wait_condition.inc; +# Now create external connection. First change_user to test should succeed, +# second one to test_db should fail. +--error 1 +--exec echo "change_user test_user,,test; change_user test_user,,test_db;" | $MYSQL_TEST 2>&1 +# Reconnect con10 +connect (con10, localhost, test_user,,test); + +--echo == Connections with no db are not limited by thread_pool_max_db_connections +connection default; +let $i = 11; +while ($i) +{ + connect (con3_$i, localhost, test_user,,*NO-ONE*); + dec $i; +} +eval $ac_entities_query; +disconnect con3_11; + +--echo == Decrement user connection counts +connection default; +let $i = 10; +while ($i) +{ + disconnect con$i; + disconnect con2_$i; + disconnect con3_$i; + dec $i; +} +# wait for connections to go away +let $wait_condition = select count(*) = 0 from information_schema.processlist where user = 'test_user'; +source include/wait_condition.inc; +eval $ac_entities_query; + +--echo == Verify that counter is not affected when db doesn't exist or access is denied +disable_query_log; +let $i = 5; +while ($i) +{ + --error ER_DBACCESS_DENIED_ERROR + connect (con_denied_$i, localhost, test_user,,bogus_db); + dec $i; +} +enable_query_log; +eval $ac_entities_query; + +--echo == Able to refill up thread_pool_max_db_connections +connection default; +let $i = 10; +while ($i) +{ + connect (con$i, localhost, test_user,,test); + dec $i; +} +eval $ac_entities_query; +disable_query_log; +--error ER_MULTI_TENANCY_MAX_CONNECTION +connect (con11, localhost, test_user,,test); +enable_query_log; + +--echo == Increase thread_pool_max_db_connections +connection default; +SET @@global.thread_pool_max_db_connections = 15; +SELECT @@global.thread_pool_max_db_connections; +let $i = 15; +while ($i > 10) +{ + connect (con$i, localhost, test_user,,test); + dec $i; +} +eval $ac_entities_query; +disable_query_log; +--error ER_MULTI_TENANCY_MAX_CONNECTION +connect (con16, localhost, test_user,,test); +enable_query_log; + +--echo == Decrease thread_pool_max_db_connections +connection default; +SET @@global.thread_pool_max_db_connections = 5; +SELECT @@global.thread_pool_max_db_connections; +let $i = 15; +while ($i > 4) +{ + disconnect con$i; + dec $i; +} +# wait for connections to go away +let $wait_condition = select count(*) = 4 from information_schema.processlist where user = 'test_user'; +source include/wait_condition.inc; +eval $ac_entities_query; +connect (con5, localhost, test_user,,test); +disable_query_log; +--error ER_MULTI_TENANCY_MAX_CONNECTION +connect (con6, localhost, test_user,,test); +enable_query_log; +eval $ac_entities_query; +# Release one slot on test for the test below. +connection default; +disconnect con5; +let $wait_condition = select count(*) = 4 from information_schema.processlist where user = 'test_user'; +source include/wait_condition.inc; + +--echo == Drop database with connections +connect (con2_1, localhost, test_user,,test_db); +connect (con2_2, localhost, test_user,,test_db); +drop database test_db; +eval $ac_entities_query; +use test; +eval $ac_entities_query; +connection default; +disconnect con2_1; +disconnect con2_2; +let $wait_condition = select count(*) = 4 from information_schema.processlist where user = 'test_user'; +source include/wait_condition.inc; + +--echo == Cleanup +connection default; +SET @@global.thread_pool_max_db_connections = @start_value; +SELECT @@global.thread_pool_max_db_connections; +eval $ac_entities_query; +let $rejected_connections = query_get_value(show global status like "Thread_pool_admission_control_rejected_connections", Value, 1); +let $rejected_connections = `select $rejected_connections - $start_rejected_connections`; +--echo rejected_connections = $rejected_connections + +drop user test_user@localhost; +drop user super_user@localhost; +let $i= 4; +while ($i) +{ + disconnect con$i; + dec $i; +} diff --git a/mysql-test/suite/thread_pool/t/max_db_connections_stress.test b/mysql-test/suite/thread_pool/t/max_db_connections_stress.test new file mode 100644 index 000000000000..4fd2a35a6073 --- /dev/null +++ b/mysql-test/suite/thread_pool/t/max_db_connections_stress.test @@ -0,0 +1,37 @@ +source include/have_thread_pool_plugin.inc; +source include/big_test.inc; +source include/not_valgrind.inc; + +create user test_user@localhost identified with 'mysql_native_password' BY ''; +grant all on test.* to test_user@localhost; + +let $i = 10; +while ($i) +{ + eval create database test_db$i; + eval grant all on test_db$i.* to test_user@localhost; + + eval use test_db$i; + + dec $i; +} + +let $error_query = sum_error_raised as "errors present" from performance_schema.events_errors_summary_global_by_error where error_name = "ER_MULTI_TENANCY_MAX_CONNECTION"; +let $error_count_init = `select $error_query`; + +let $exec = /usr/bin/python3 $MYSQL_TEST_DIR/t/max_db_connections_stress.py --user='test_user' --host=127.0.0.1 --port=$MASTER_MYPORT --db_prefix='test_db' --db_count=10; +exec $exec > $MYSQLTEST_VARDIR/tmp/tp_max_db_connections_stress.output; + +let $i = 10; +while ($i) +{ + eval drop database test_db$i; + dec $i; +} + +drop user test_user@localhost; +remove_file $MYSQLTEST_VARDIR/tmp/tp_max_db_connections_stress.output; + +disable_query_log; +eval select $error_count_init < $error_query; +enable_query_log; diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 5b26186c14cf..a266ca5c9973 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -117,6 +117,8 @@ const char *del_exts[] = {".frm", ".BAK", ".TMD", ".opt", static TYPELIB deletable_extentions = {array_elements(del_exts) - 1, "del_exts", del_exts, nullptr}; +static Change_db_callback change_db_callback{nullptr}; + static bool find_unknown_and_remove_deletable_files(THD *thd, MY_DIR *dirp, const char *path); @@ -1163,6 +1165,11 @@ bool mysql_rm_db(THD *thd, const LEX_CSTRING &db, bool if_exists) { if (!error) { db_ac->remove(db.str); + + Change_db_callback callback = change_db_callback; + if (callback) { + callback(thd, db, true /*drop*/); + } } thd->server_status |= SERVER_STATUS_DB_DROPPED; @@ -1784,6 +1791,15 @@ bool mysql_opt_change_db(THD *thd, const LEX_CSTRING &new_db_name, return mysql_change_db(thd, new_db_name, force_switch); } +/** + Set change_db_callback notified when db of connection is set or changed. + + @param new_callback New callback to set. +*/ +void set_change_db_callback(Change_db_callback new_callback) { + change_db_callback = new_callback; +} + /** * This function is called for the following commands to set * the session's default database: COM_INIT_DB, SQLCOM_CHANGE_DB, @@ -1796,26 +1812,34 @@ bool mysql_opt_change_db(THD *thd, const LEX_CSTRING &new_db_name, * 1 failed to set session db */ bool set_session_db_helper(THD *thd, const LEX_CSTRING &new_db) { - Ac_switch_guard switch_guard(thd); - bool error = switch_guard.add_connection(new_db.str); - - if (error) { - // Connection rejected/throttled. Push error message. - std::string entity(new_db.str); - if (thd->security_context()->host_or_ip().str) { - if (!entity.empty()) entity += " on "; - entity += thd->security_context()->host_or_ip().str; - } + bool error = false; + Change_db_callback callback = change_db_callback; + if (callback) { + error = callback(thd, new_db, false /*drop*/); + } - my_error(ER_MULTI_TENANCY_MAX_CONNECTION, MYF(0), entity.c_str()); - } else { - // Switch to new db if it is specified. - if (new_db.length) - error = mysql_change_db(thd, new_db, false); // Do not force switch. + if (!error) { + Ac_switch_guard switch_guard(thd); + error = switch_guard.add_connection(new_db.str); - if (!error) - // No db, or successful switch, so mark guard to keep the changes. - switch_guard.commit(); + if (error) { + // Connection rejected/throttled. Push error message. + std::string entity(new_db.str); + if (thd->security_context()->host_or_ip().str) { + if (!entity.empty()) entity += " on "; + entity += thd->security_context()->host_or_ip().str; + } + + my_error(ER_MULTI_TENANCY_MAX_CONNECTION, MYF(0), entity.c_str()); + } else { + // Switch to new db if it is specified. + if (new_db.length) + error = mysql_change_db(thd, new_db, false); // Do not force switch. + + if (!error) + // No db, or successful switch, so mark guard to keep the changes. + switch_guard.commit(); + } } // Switch guard either commits or rolls back the switch operation. diff --git a/sql/sql_db.h b/sql/sql_db.h index f6b6534785f7..01f570bab0e0 100644 --- a/sql/sql_db.h +++ b/sql/sql_db.h @@ -52,5 +52,8 @@ bool check_schema_readonly(THD *thd, const char *schema_name, TABLE_SHARE *share = nullptr); bool is_thd_db_read_only_by_name(THD *thd, const char *db); enum_db_read_only get_db_read_only(const dd::Schema &schema); + +using Change_db_callback = bool (*)(THD *, const LEX_CSTRING &, bool); bool set_session_db_helper(THD *thd, const LEX_CSTRING &new_db); +void set_change_db_callback(Change_db_callback new_callback); #endif /* SQL_DB_INCLUDED */ diff --git a/sql/sql_thd_internal_api.cc b/sql/sql_thd_internal_api.cc index f15b97a3e00e..ad1444d19218 100644 --- a/sql/sql_thd_internal_api.cc +++ b/sql/sql_thd_internal_api.cc @@ -48,6 +48,7 @@ #include "mysql/psi/mysql_socket.h" #include "mysql/thread_type.h" #include "nulls.h" +#include "sql/auth/auth_acls.h" // SUPER_ACL #include "sql/binlog.h" // mysql_bin_log #include "sql/current_thd.h" // current_thd #include "sql/mysqld.h" @@ -355,6 +356,14 @@ bool thd_is_strict_mode(const THD *thd) { return thd->is_strict_sql_mode(); } bool thd_is_error(const THD *thd) { return thd->is_error(); } +bool thd_is_super(const THD *thd) { + return thd->security_context()->check_access(SUPER_ACL); +} + +LEX_CSTRING thd_host_or_ip(THD *thd) { + return thd->security_context()->host_or_ip(); +} + bool is_mysql_datadir_path(const char *path) { if (path == nullptr || strlen(path) >= FN_REFLEN) return false; diff --git a/sql/sql_thd_internal_api.h b/sql/sql_thd_internal_api.h index bdd373431634..902cd136e779 100644 --- a/sql/sql_thd_internal_api.h +++ b/sql/sql_thd_internal_api.h @@ -233,6 +233,22 @@ bool thd_is_strict_mode(const THD *thd); */ bool thd_is_error(const THD *thd); +/** + Does current user have access of SUPER_ACL? + + @param thd Thread object + @return true if has access, false otherwise. +*/ +bool thd_is_super(const THD *thd); + +/** + Client host or IP address. + + @param thd Thread object + @return String with host or IP info. +*/ +LEX_CSTRING thd_host_or_ip(THD *thd); + /** Test a file path whether it is same as mysql data directory path.