diff --git a/mysql-test/r/admin_max_connections.result b/mysql-test/r/admin_max_connections.result new file mode 100644 index 000000000000..51fddba285a4 --- /dev/null +++ b/mysql-test/r/admin_max_connections.result @@ -0,0 +1,75 @@ +# +# 1. Status variables exist and start at a clean baseline +# +SHOW GLOBAL STATUS LIKE 'Admin_connection%'; +Variable_name Value +Admin_connection_errors_max_connections 0 +Admin_connections 0 +# +# 2. Create a user allowed to use the administrative interface +# +CREATE USER u_admin; +GRANT SERVICE_CONNECTION_ADMIN ON *.* TO u_admin; +# +# 3. With the default of 0 there is no limit (previous behaviour), +# and Admin_connections counts administrative connections only +# +SHOW GLOBAL STATUS LIKE 'Admin_connections'; +Variable_name Value +Admin_connections 3 +# An ordinary connection must NOT change Admin_connections +SHOW GLOBAL STATUS LIKE 'Admin_connections'; +Variable_name Value +Admin_connections 3 +# All administrative connections are gone again +SHOW GLOBAL STATUS LIKE 'Admin_connections'; +Variable_name Value +Admin_connections 0 +# +# 4. With admin_max_connections = 2 the third administrative +# connection is rejected, and the rejection is counted in +# Admin_connection_errors_max_connections only +# +SET GLOBAL admin_max_connections = 2; +# Remember the normal-connection error counter to prove it stays put +SHOW GLOBAL STATUS LIKE 'Admin_connections'; +Variable_name Value +Admin_connections 2 +connect(127.0.0.1,u_admin,,test,ADMIN_PORT,SOURCE_SOCKET); +ERROR HY000: Too many connections +SHOW GLOBAL STATUS LIKE 'Admin_connection_errors_max_connections'; +Variable_name Value +Admin_connection_errors_max_connections 1 +# The rejection did not leak into Connection_errors_max_connections +# Connection_errors_max_connections unchanged: OK +# +# 5. Ordinary connections are not affected by the admin cap +# +SELECT 1 AS ordinary_connection_works; +ordinary_connection_works +1 +# +# 6. Closing an administrative connection frees a slot +# +SELECT 1 AS admin_slot_freed; +admin_slot_freed +1 +# +# 7. Lowering the cap below the number of open administrative +# connections affects only new connections +# +SET GLOBAL admin_max_connections = 1; +SELECT 1 AS existing_admin_conn_alive; +existing_admin_conn_alive +1 +connect(127.0.0.1,u_admin,,test,ADMIN_PORT,SOURCE_SOCKET); +ERROR HY000: Too many connections +# +# 8. Setting the value back to 0 removes the limit again +# +SET GLOBAL admin_max_connections = 0; +# +# Cleanup +# +DROP USER u_admin; +SET GLOBAL admin_max_connections = DEFAULT; diff --git a/mysql-test/r/all_persisted_variables.result b/mysql-test/r/all_persisted_variables.result index f94b2d56b76b..2f2fc1822c5f 100644 --- a/mysql-test/r/all_persisted_variables.result +++ b/mysql-test/r/all_persisted_variables.result @@ -46,7 +46,7 @@ include/assert.inc [Expect 500+ variables in the table. Due to open Bugs, we are # Test SET PERSIST -include/assert.inc [Expect 450 persisted variables in the table.] +include/assert.inc [Expect 451 persisted variables in the table.] ************************************************************ * 3. Restart server, it must preserve the persisted variable @@ -54,9 +54,9 @@ include/assert.inc [Expect 450 persisted variables in the table.] ************************************************************ # restart -include/assert.inc [Expect 450 persisted variables in persisted_variables table.] -include/assert.inc [Expect 450 persisted variables shown as PERSISTED in variables_info table.] -include/assert.inc [Expect 450 persisted variables with matching peristed and global values.] +include/assert.inc [Expect 451 persisted variables in persisted_variables table.] +include/assert.inc [Expect 451 persisted variables shown as PERSISTED in variables_info table.] +include/assert.inc [Expect 451 persisted variables with matching peristed and global values.] ************************************************************ * 4. Test RESET PERSIST IF EXISTS. Verify persisted variable diff --git a/mysql-test/r/mysqld--help-notwin.result b/mysql-test/r/mysqld--help-notwin.result index 6bb72c9aa43f..f3e060c6045b 100644 --- a/mysql-test/r/mysqld--help-notwin.result +++ b/mysql-test/r/mysqld--help-notwin.result @@ -29,6 +29,11 @@ The following options may be given as the first argument: System. Attempt to specify a network namespace for a platform that doesn't support it results in error during socket creation. + --admin-max-connections=# + The maximum number of concurrent client connections + permitted on the administrative interface + (admin_address/admin_port). 0 (the default) means no + limit. --admin-port=# Port number to use for service connection, built-in default (33062) --admin-ssl-ca=name CA file in PEM format (check OpenSSL docs) for @@ -1642,6 +1647,7 @@ Variables (--variable-name=value) activate-all-roles-on-login FALSE activate-mandatory-roles TRUE admin-address (No default value) +admin-max-connections 0 admin-port 33062 admin-ssl-ca (No default value) admin-ssl-capath (No default value) diff --git a/mysql-test/suite/sys_vars/r/admin_max_connections_basic.result b/mysql-test/suite/sys_vars/r/admin_max_connections_basic.result new file mode 100644 index 000000000000..36906faaa80a --- /dev/null +++ b/mysql-test/suite/sys_vars/r/admin_max_connections_basic.result @@ -0,0 +1,128 @@ +SET @start_value = @@global.admin_max_connections; +SELECT @start_value; +@start_value +0 +'#--------------------FN_DYNVARS_XXX_01------------------------#' +SET @@global.admin_max_connections = 5000; +SET @@global.admin_max_connections = DEFAULT; +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +0 +'#---------------------FN_DYNVARS_XXX_02-------------------------#' +SET @@global.admin_max_connections = @start_value; +SELECT @@global.admin_max_connections = 0; +@@global.admin_max_connections = 0 +1 +'#--------------------FN_DYNVARS_XXX_03------------------------#' +SET @@global.admin_max_connections = 100000; +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +100000 +SET @@global.admin_max_connections = 99999; +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +99999 +SET @@global.admin_max_connections = 65536; +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +65536 +SET @@global.admin_max_connections = 0; +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +0 +SET @@global.admin_max_connections = 1; +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +1 +'#--------------------FN_DYNVARS_XXX_04-------------------------#' +SET @@global.admin_max_connections = -1; +Warnings: +Warning 1292 Truncated incorrect admin_max_connections value: '-1' +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +0 +SET @@global.admin_max_connections = 100000000000; +Warnings: +Warning 1292 Truncated incorrect admin_max_connections value: '100000000000' +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +100000 +SET @@global.admin_max_connections = 10000.01; +ERROR 42000: Incorrect argument type to variable 'admin_max_connections' +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +100000 +SET @@global.admin_max_connections = -1024; +Warnings: +Warning 1292 Truncated incorrect admin_max_connections value: '-1024' +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +0 +SET @@global.admin_max_connections = 100001; +Warnings: +Warning 1292 Truncated incorrect admin_max_connections value: '100001' +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +100000 +SET @@global.admin_max_connections = ON; +ERROR 42000: Incorrect argument type to variable 'admin_max_connections' +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +100000 +SET @@global.admin_max_connections = 'test'; +ERROR 42000: Incorrect argument type to variable 'admin_max_connections' +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +100000 +'#-------------------FN_DYNVARS_XXX_05----------------------------#' +SET @@session.admin_max_connections = 4096; +ERROR HY000: Variable 'admin_max_connections' is a GLOBAL variable and should be set with SET GLOBAL +SELECT @@session.admin_max_connections; +ERROR HY000: Variable 'admin_max_connections' is a GLOBAL variable +'#----------------------FN_DYNVARS_XXX_06------------------------#' +SELECT @@global.admin_max_connections = VARIABLE_VALUE +FROM performance_schema.global_variables +WHERE VARIABLE_NAME='admin_max_connections'; +@@global.admin_max_connections = VARIABLE_VALUE +1 +SELECT @@admin_max_connections = VARIABLE_VALUE +FROM performance_schema.session_variables +WHERE VARIABLE_NAME='admin_max_connections'; +@@admin_max_connections = VARIABLE_VALUE +1 +'#---------------------FN_DYNVARS_XXX_07----------------------#' +SET @@global.admin_max_connections = TRUE; +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +1 +SET @@global.admin_max_connections = FALSE; +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +0 +'#---------------------FN_DYNVARS_XXX_08----------------------#' +SET @@global.admin_max_connections = 5000; +SELECT @@admin_max_connections = @@global.admin_max_connections; +@@admin_max_connections = @@global.admin_max_connections +1 +'#---------------------FN_DYNVARS_XXX_09----------------------#' +SET admin_max_connections = 6000; +ERROR HY000: Variable 'admin_max_connections' is a GLOBAL variable and should be set with SET GLOBAL +SELECT @@admin_max_connections; +@@admin_max_connections +5000 +SET local.admin_max_connections = 7000; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'local.admin_max_connections = 7000' at line 1 +SELECT local.admin_max_connections; +ERROR 42S02: Unknown table 'local' in field list +SET global.admin_max_connections = 8000; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'global.admin_max_connections = 8000' at line 1 +SELECT global.admin_max_connections; +ERROR 42S02: Unknown table 'global' in field list +SELECT admin_max_connections; +ERROR 42S22: Unknown column 'admin_max_connections' in 'field list' +SELECT @@session.admin_max_connections; +ERROR HY000: Variable 'admin_max_connections' is a GLOBAL variable +SET @@global.admin_max_connections = @start_value; +SELECT @@global.admin_max_connections; +@@global.admin_max_connections +0 diff --git a/mysql-test/suite/sys_vars/t/admin_max_connections_basic.test b/mysql-test/suite/sys_vars/t/admin_max_connections_basic.test new file mode 100644 index 000000000000..f51ee3d081ba --- /dev/null +++ b/mysql-test/suite/sys_vars/t/admin_max_connections_basic.test @@ -0,0 +1,159 @@ +############## mysql-test\suite\sys_vars\t\admin_max_connections_basic.test ### +# # +# Variable Name: admin_max_connections # +# Scope: GLOBAL # +# Access Type: Dynamic # +# Data Type: numeric # +# Default Value: 0 # +# Range: 0-100000 # +# # +# Description: Test Cases of Dynamic System Variable admin_max_connections # +# that checks the behavior of this variable in the following # +# ways: # +# * Default Value # +# * Valid & Invalid values # +# * Scope & Access method # +# * Data Integrity # +# # +# Reference: https://bugs.mysql.com/bug.php?id=99917 # +# # +############################################################################### + +--source include/load_sysvars.inc + +###################################################################### +# Saving initial value of admin_max_connections in a temporary variable # +###################################################################### + +SET @start_value = @@global.admin_max_connections; +SELECT @start_value; + +--echo '#--------------------FN_DYNVARS_XXX_01------------------------#' +###################################################################### +# Display the DEFAULT value of admin_max_connections # +###################################################################### + +SET @@global.admin_max_connections = 5000; +SET @@global.admin_max_connections = DEFAULT; +SELECT @@global.admin_max_connections; + +--echo '#---------------------FN_DYNVARS_XXX_02-------------------------#' +############################################### +# Verify default value of variable # +############################################### + +SET @@global.admin_max_connections = @start_value; +SELECT @@global.admin_max_connections = 0; + +--echo '#--------------------FN_DYNVARS_XXX_03------------------------#' +###################################################################### +# Change the value of admin_max_connections to a valid value # +###################################################################### + +SET @@global.admin_max_connections = 100000; +SELECT @@global.admin_max_connections; +SET @@global.admin_max_connections = 99999; +SELECT @@global.admin_max_connections; +SET @@global.admin_max_connections = 65536; +SELECT @@global.admin_max_connections; +SET @@global.admin_max_connections = 0; +SELECT @@global.admin_max_connections; +SET @@global.admin_max_connections = 1; +SELECT @@global.admin_max_connections; + +--echo '#--------------------FN_DYNVARS_XXX_04-------------------------#' +##################################################################### +# Change the value of admin_max_connections to invalid value # +##################################################################### + +SET @@global.admin_max_connections = -1; +SELECT @@global.admin_max_connections; +SET @@global.admin_max_connections = 100000000000; +SELECT @@global.admin_max_connections; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.admin_max_connections = 10000.01; +SELECT @@global.admin_max_connections; +SET @@global.admin_max_connections = -1024; +SELECT @@global.admin_max_connections; +SET @@global.admin_max_connections = 100001; +SELECT @@global.admin_max_connections; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.admin_max_connections = ON; +SELECT @@global.admin_max_connections; +--Error ER_WRONG_TYPE_FOR_VAR +SET @@global.admin_max_connections = 'test'; +SELECT @@global.admin_max_connections; + +--echo '#-------------------FN_DYNVARS_XXX_05----------------------------#' +##################################################################### +# Test if accessing session admin_max_connections gives error # +##################################################################### + +--Error ER_GLOBAL_VARIABLE +SET @@session.admin_max_connections = 4096; +--Error ER_INCORRECT_GLOBAL_LOCAL_VAR +SELECT @@session.admin_max_connections; + +--echo '#----------------------FN_DYNVARS_XXX_06------------------------#' +############################################################################## +# Check if the value in GLOBAL & SESSION Tables matches values in variable # +############################################################################## + +--disable_warnings +SELECT @@global.admin_max_connections = VARIABLE_VALUE +FROM performance_schema.global_variables +WHERE VARIABLE_NAME='admin_max_connections'; +SELECT @@admin_max_connections = VARIABLE_VALUE +FROM performance_schema.session_variables +WHERE VARIABLE_NAME='admin_max_connections'; +--enable_warnings + +--echo '#---------------------FN_DYNVARS_XXX_07----------------------#' +################################################################### +# Check if TRUE and FALSE values can be used on variable # +################################################################### + +SET @@global.admin_max_connections = TRUE; +SELECT @@global.admin_max_connections; +SET @@global.admin_max_connections = FALSE; +SELECT @@global.admin_max_connections; + +--echo '#---------------------FN_DYNVARS_XXX_08----------------------#' +######################################################################################################## +# Check if accessing variable with SESSION,LOCAL and without SCOPE points to same global variable # +######################################################################################################## + +SET @@global.admin_max_connections = 5000; +SELECT @@admin_max_connections = @@global.admin_max_connections; + +--echo '#---------------------FN_DYNVARS_XXX_09----------------------#' +############################################################################## +# Check if admin_max_connections can be accessed with and without @@ sign # +############################################################################## + +--Error ER_GLOBAL_VARIABLE +SET admin_max_connections = 6000; +SELECT @@admin_max_connections; +--Error ER_PARSE_ERROR +SET local.admin_max_connections = 7000; +--Error ER_UNKNOWN_TABLE +SELECT local.admin_max_connections; +--Error ER_PARSE_ERROR +SET global.admin_max_connections = 8000; +--Error ER_UNKNOWN_TABLE +SELECT global.admin_max_connections; +--Error ER_BAD_FIELD_ERROR +SELECT admin_max_connections; +--Error ER_INCORRECT_GLOBAL_LOCAL_VAR +SELECT @@session.admin_max_connections; + +############################## +# Restore initial value # +############################## + +SET @@global.admin_max_connections = @start_value; +SELECT @@global.admin_max_connections; + +######################################################## +# END OF admin_max_connections TESTS # +######################################################## diff --git a/mysql-test/t/admin_max_connections-master.opt b/mysql-test/t/admin_max_connections-master.opt new file mode 100644 index 000000000000..7e41a4f780c2 --- /dev/null +++ b/mysql-test/t/admin_max_connections-master.opt @@ -0,0 +1,2 @@ +--admin-address=127.0.0.1 +--admin-port=$MASTER_ADMINPORT diff --git a/mysql-test/t/admin_max_connections.test b/mysql-test/t/admin_max_connections.test new file mode 100644 index 000000000000..03e9d60c8e5a --- /dev/null +++ b/mysql-test/t/admin_max_connections.test @@ -0,0 +1,147 @@ +######################################################################## +# Functional test for the admin_max_connections system variable # +# (Bug#99917). # +# # +# admin_max_connections caps the number of concurrent connections # +# accepted on the administrative interface (admin_address/admin_port). # +# 0 (the default) means no limit, matching previous behaviour. # +# # +# Related status variables (administrative connections only): # +# Admin_connections - currently open # +# Admin_connection_errors_max_connections - refused by the cap # +# # +# The administrative interface is enabled for this test through # +# admin_max_connections-master.opt. Connecting to it requires the # +# SERVICE_CONNECTION_ADMIN privilege. # +######################################################################## + +--source include/count_sessions.inc + +--echo # +--echo # 1. Status variables exist and start at a clean baseline +--echo # +SHOW GLOBAL STATUS LIKE 'Admin_connection%'; + +--echo # +--echo # 2. Create a user allowed to use the administrative interface +--echo # +CREATE USER u_admin; +GRANT SERVICE_CONNECTION_ADMIN ON *.* TO u_admin; +--disable_query_log +CALL mtr.add_suppression("Failed to initialize TLS for channel: mysql_admin"); +CALL mtr.add_suppression("Failed to set up SSL because of the following SSL library error"); +--enable_query_log + +--echo # +--echo # 3. With the default of 0 there is no limit (previous behaviour), +--echo # and Admin_connections counts administrative connections only +--echo # +connect (admin_nolimit_1,127.0.0.1,u_admin,,,$MASTER_ADMINPORT,,TCP); +connect (admin_nolimit_2,127.0.0.1,u_admin,,,$MASTER_ADMINPORT,,TCP); +connect (admin_nolimit_3,127.0.0.1,u_admin,,,$MASTER_ADMINPORT,,TCP); +connection default; +SHOW GLOBAL STATUS LIKE 'Admin_connections'; +--echo # An ordinary connection must NOT change Admin_connections +connect (ordinary_probe,localhost,u_admin,,,,,); +connection default; +SHOW GLOBAL STATUS LIKE 'Admin_connections'; +connection ordinary_probe; +disconnect ordinary_probe; +--source include/wait_until_disconnected.inc +connection admin_nolimit_3; +disconnect admin_nolimit_3; +--source include/wait_until_disconnected.inc +connection admin_nolimit_2; +disconnect admin_nolimit_2; +--source include/wait_until_disconnected.inc +connection admin_nolimit_1; +disconnect admin_nolimit_1; +--source include/wait_until_disconnected.inc +connection default; +--source include/wait_until_count_sessions.inc +--echo # All administrative connections are gone again +--let $wait_condition= SELECT VARIABLE_VALUE = 0 FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Admin_connections' +--source include/wait_condition.inc +SHOW GLOBAL STATUS LIKE 'Admin_connections'; + +--echo # +--echo # 4. With admin_max_connections = 2 the third administrative +--echo # connection is rejected, and the rejection is counted in +--echo # Admin_connection_errors_max_connections only +--echo # +SET GLOBAL admin_max_connections = 2; +--echo # Remember the normal-connection error counter to prove it stays put +--let $normal_errors_before = query_get_value(SHOW GLOBAL STATUS LIKE 'Connection_errors_max_connections', Value, 1) +connect (admin_1,127.0.0.1,u_admin,,,$MASTER_ADMINPORT,,TCP); +connect (admin_2,127.0.0.1,u_admin,,,$MASTER_ADMINPORT,,TCP); +connection default; +SHOW GLOBAL STATUS LIKE 'Admin_connections'; +--replace_result $MASTER_ADMINPORT ADMIN_PORT $MASTER_MYSOCK SOURCE_SOCKET +--error ER_CON_COUNT_ERROR +connect (admin_3,127.0.0.1,u_admin,,,$MASTER_ADMINPORT,,TCP); +SHOW GLOBAL STATUS LIKE 'Admin_connection_errors_max_connections'; +--echo # The rejection did not leak into Connection_errors_max_connections +--let $normal_errors_after = query_get_value(SHOW GLOBAL STATUS LIKE 'Connection_errors_max_connections', Value, 1) +if ($normal_errors_before != $normal_errors_after) +{ + --die Connection_errors_max_connections changed on an admin rejection +} +--echo # Connection_errors_max_connections unchanged: OK + +--echo # +--echo # 5. Ordinary connections are not affected by the admin cap +--echo # +connect (ordinary_1,localhost,u_admin,,,,,); +SELECT 1 AS ordinary_connection_works; +disconnect ordinary_1; +--source include/wait_until_disconnected.inc +connection default; + +--echo # +--echo # 6. Closing an administrative connection frees a slot +--echo # +connection admin_2; +disconnect admin_2; +--source include/wait_until_disconnected.inc +connection default; +--let $wait_condition= SELECT VARIABLE_VALUE = 1 FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Admin_connections' +--source include/wait_condition.inc +connect (admin_4,127.0.0.1,u_admin,,,$MASTER_ADMINPORT,,TCP); +SELECT 1 AS admin_slot_freed; +connection default; + +--echo # +--echo # 7. Lowering the cap below the number of open administrative +--echo # connections affects only new connections +--echo # +SET GLOBAL admin_max_connections = 1; +connection admin_1; +SELECT 1 AS existing_admin_conn_alive; +connection default; +--replace_result $MASTER_ADMINPORT ADMIN_PORT $MASTER_MYSOCK SOURCE_SOCKET +--error ER_CON_COUNT_ERROR +connect (admin_5,127.0.0.1,u_admin,,,$MASTER_ADMINPORT,,TCP); + +--echo # +--echo # 8. Setting the value back to 0 removes the limit again +--echo # +SET GLOBAL admin_max_connections = 0; +connect (admin_6,127.0.0.1,u_admin,,,$MASTER_ADMINPORT,,TCP); +connection default; + +--echo # +--echo # Cleanup +--echo # +connection admin_6; +disconnect admin_6; +--source include/wait_until_disconnected.inc +connection admin_4; +disconnect admin_4; +--source include/wait_until_disconnected.inc +connection admin_1; +disconnect admin_1; +--source include/wait_until_disconnected.inc +connection default; +DROP USER u_admin; +SET GLOBAL admin_max_connections = DEFAULT; +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/all_persisted_variables.test b/mysql-test/t/all_persisted_variables.test index 2e46e91af2b8..d04254fc766a 100644 --- a/mysql-test/t/all_persisted_variables.test +++ b/mysql-test/t/all_persisted_variables.test @@ -56,7 +56,7 @@ let $total_global_vars=`SELECT COUNT(*) AND variable_name NOT LIKE 'debug_%' AND variable_name NOT LIKE '%telemetry%'`; -let $total_persistent_vars=450; +let $total_persistent_vars=451; --echo *************************************************************** --echo * 0. Verify that variables present in performance_schema.global --echo * variables are actually global variables and can be set using diff --git a/sql/conn_handler/connection_handler_manager.cc b/sql/conn_handler/connection_handler_manager.cc index 69966bf0fa11..6e2f77b4b540 100644 --- a/sql/conn_handler/connection_handler_manager.cc +++ b/sql/conn_handler/connection_handler_manager.cc @@ -53,6 +53,8 @@ struct Connection_handler_functions; // Initialize static members uint Connection_handler_manager::connection_count = 0; +uint Connection_handler_manager::admin_connection_count = 0; +ulong Connection_handler_manager::admin_connection_errors_max_connection = 0; ulong Connection_handler_manager::max_used_connections = 0; ulong Connection_handler_manager::max_used_connections_time = 0; std::atomic_ulong Connection_handler_manager::incoming_connection_count = 0; @@ -107,6 +109,23 @@ bool Connection_handler_manager::check_and_incr_conn_count( bool is_admin_connection, bool internal_session) { bool connection_accepted = true; mysql_mutex_lock(&LOCK_connection_count); + /* + Bug#99917: Restrict the number of concurrent connections accepted on the + administrative interface (admin_address/admin_port). When + admin_max_connections is 0 (the default) no limit is imposed, which + matches the previous behaviour of the administrative interface. The + rejection is counted in Admin_connection_errors_max_connections only, so + Connection_errors_max_connections keeps counting ordinary connections + refused by max_connections and nothing else. Internal sessions + (internal_session == true) never use the administrative interface and + are always exempt from this limit. + */ + if (is_admin_connection && !internal_session && admin_max_connections != 0 && + admin_connection_count >= admin_max_connections) { + admin_connection_errors_max_connection++; + mysql_mutex_unlock(&LOCK_connection_count); + return false; + } /* Here we allow max_connections + 1 clients to connect (by checking before we increment by 1). @@ -120,6 +139,7 @@ bool Connection_handler_manager::check_and_incr_conn_count( m_connection_errors_max_connection++; } else { ++connection_count; + if (is_admin_connection && !internal_session) ++admin_connection_count; if (!internal_session) ++incoming_connection_count; if (connection_count > max_used_connections) { max_used_connections = connection_count; diff --git a/sql/conn_handler/connection_handler_manager.h b/sql/conn_handler/connection_handler_manager.h index 60148c39ad5a..4c6dc1f3446d 100644 --- a/sql/conn_handler/connection_handler_manager.h +++ b/sql/conn_handler/connection_handler_manager.h @@ -116,6 +116,12 @@ class Connection_handler_manager { // Status variables. Must be static as they are used by the signal handler. static uint connection_count; // Protected by LOCK_connection_count + // Bug#99917: administrative interface counters, exposed as the + // Admin_connections / Admin_connection_errors_max_connections status + // variables. They count administrative connections only. + static uint admin_connection_count; // Protected by LOCK_connection_count + static ulong + admin_connection_errors_max_connection; // LOCK_connection_count static ulong max_used_connections; // Protected by LOCK_connection_count static ulong max_used_connections_time; // Protected by LOCK_connection_count @@ -193,9 +199,18 @@ class Connection_handler_manager { /** Decrease the number of current connections. */ - static void dec_connection_count() { + static void dec_connection_count(bool is_admin_connection = false) { mysql_mutex_lock(&LOCK_connection_count); connection_count--; + /* + Bug#99917: keep the count of connections established on the + administrative interface in sync, so that admin_max_connections can be + enforced in check_and_incr_conn_count(). is_admin_connection defaults + to false for callers (internal sessions, thread pool) that never serve + the administrative interface. + */ + if (is_admin_connection && admin_connection_count > 0) + admin_connection_count--; /* Notify shutdown thread when last connection is done with its job */ diff --git a/sql/conn_handler/connection_handler_one_thread.cc b/sql/conn_handler/connection_handler_one_thread.cc index aa87e2b1e8ca..21bc3e0b361d 100644 --- a/sql/conn_handler/connection_handler_one_thread.cc +++ b/sql/conn_handler/connection_handler_one_thread.cc @@ -44,7 +44,8 @@ bool One_thread_connection_handler::add_connection(Channel_info *channel_info) { if (my_thread_init()) { connection_errors_internal++; channel_info->send_error_and_close_channel(ER_OUT_OF_RESOURCES, 0, false); - Connection_handler_manager::dec_connection_count(); + Connection_handler_manager::dec_connection_count( + channel_info->is_admin_connection()); return true; } @@ -52,7 +53,8 @@ bool One_thread_connection_handler::add_connection(Channel_info *channel_info) { if (thd == nullptr) { connection_errors_internal++; channel_info->send_error_and_close_channel(ER_OUT_OF_RESOURCES, 0, false); - Connection_handler_manager::dec_connection_count(); + Connection_handler_manager::dec_connection_count( + channel_info->is_admin_connection()); return true; } @@ -89,7 +91,7 @@ bool One_thread_connection_handler::add_connection(Channel_info *channel_info) { close_connection(thd, 0, false, false); thd->release_resources(); thd_manager->remove_thd(thd); - Connection_handler_manager::dec_connection_count(); + Connection_handler_manager::dec_connection_count(thd->is_admin_connection()); delete thd; return error; } diff --git a/sql/conn_handler/connection_handler_per_thread.cc b/sql/conn_handler/connection_handler_per_thread.cc index d3c158b95fd5..b957a1776eee 100644 --- a/sql/conn_handler/connection_handler_per_thread.cc +++ b/sql/conn_handler/connection_handler_per_thread.cc @@ -254,18 +254,20 @@ static void *handle_connection(void *arg) { connection_errors_internal++; channel_info->send_error_and_close_channel(ER_OUT_OF_RESOURCES, 0, false); handler_manager->inc_aborted_connects(); - Connection_handler_manager::dec_connection_count(); + Connection_handler_manager::dec_connection_count( + channel_info->is_admin_connection()); delete channel_info; my_thread_exit(nullptr); return nullptr; } for (;;) { + const bool is_admin_conn = channel_info->is_admin_connection(); THD *thd = init_new_thd(channel_info); if (thd == nullptr) { connection_errors_internal++; handler_manager->inc_aborted_connects(); - Connection_handler_manager::dec_connection_count(); + Connection_handler_manager::dec_connection_count(is_admin_conn); break; // We are out of resources, no sense in continuing. } @@ -315,7 +317,7 @@ static void *handle_connection(void *arg) { ERR_remove_thread_state(nullptr); #endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ thd_manager->remove_thd(thd); - Connection_handler_manager::dec_connection_count(); + Connection_handler_manager::dec_connection_count(thd->is_admin_connection()); #ifdef HAVE_PSI_THREAD_INTERFACE /* Stop telemetry, while THD is still available. */ @@ -344,9 +346,10 @@ static void *handle_connection(void *arg) { if (connection_events_loop_aborted()) { // Close the channel and exit as server is undergoing shutdown. channel_info->send_error_and_close_channel(ER_SERVER_SHUTDOWN, 0, false); + Connection_handler_manager::dec_connection_count( + channel_info->is_admin_connection()); delete channel_info; channel_info = nullptr; - Connection_handler_manager::dec_connection_count(); break; } } @@ -430,7 +433,8 @@ bool Per_thread_connection_handler::add_connection(Channel_info *channel_info) { LogErr(ERROR_LEVEL, ER_CONN_PER_THREAD_NO_THREAD, error); channel_info->send_error_and_close_channel(ER_CANT_CREATE_THREAD, error, true); - Connection_handler_manager::dec_connection_count(); + Connection_handler_manager::dec_connection_count( + channel_info->is_admin_connection()); return true; } diff --git a/sql/mysqld.cc b/sql/mysqld.cc index e8a2b1cbdf79..e8783a80fb43 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -1408,6 +1408,12 @@ ulong specialflag = 0; ulong binlog_cache_use = 0, binlog_cache_disk_use = 0; ulong binlog_stmt_cache_use = 0, binlog_stmt_cache_disk_use = 0; ulong max_connections, max_connect_errors; +/* + Bug#99917: limit on the number of concurrent connections to the + administrative interface (admin_address/admin_port). 0 means no limit, + which preserves the previous behaviour of the administrative interface. +*/ +ulong admin_max_connections = 0; ulong rpl_stop_replica_timeout = LONG_TIMEOUT; bool thread_cache_size_specified = false; bool host_cache_size_specified = false; @@ -11690,6 +11696,17 @@ SHOW_VAR status_vars[] = { SHOW_SCOPE_GLOBAL}, {"Acl_cache_items_count", (char *)&show_acl_cache_items_count, SHOW_FUNC, SHOW_SCOPE_GLOBAL}, + /* + Bug#99917: administrative interface counters. These count administrative + connections only; ordinary connections are accounted by + Threads_connected / Connection_errors_max_connections as before. + */ + {"Admin_connection_errors_max_connections", + (char *)&Connection_handler_manager::admin_connection_errors_max_connection, + SHOW_LONG, SHOW_SCOPE_GLOBAL}, + {"Admin_connections", + (char *)&Connection_handler_manager::admin_connection_count, SHOW_INT, + SHOW_SCOPE_GLOBAL}, #ifndef NDEBUG {"Ongoing_anonymous_gtid_violating_transaction_count", (char *)&show_ongoing_anonymous_gtid_violating_transaction_count, diff --git a/sql/mysqld.h b/sql/mysqld.h index 1a0d22d3aea6..a6447f25332c 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -305,6 +305,8 @@ extern ulong stored_program_def_size; extern ulong table_def_size; extern ulong tablespace_def_size; extern MYSQL_PLUGIN_IMPORT ulong max_connections; +/* Bug#99917: administrative interface connection limit. */ +extern ulong admin_max_connections; extern ulong max_digest_length; extern ulong max_connect_errors, connect_timeout; extern bool opt_replica_allow_batching; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 8ef0199c1802..37016bc89807 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -2859,6 +2859,21 @@ static Sys_var_ulong Sys_max_connections( /* max_connections is used as a sizing hint by the performance schema. */ sys_var::PARSE_EARLY); +/* + Bug#99917: Provide an option to cap the number of concurrent connections + accepted on the administrative interface (admin_address / admin_port). + Historically there was no limit on administrative connections; a value of + 0 (the default) keeps that behaviour. +*/ +static Sys_var_ulong Sys_admin_max_connections( + "admin_max_connections", + "The maximum number of concurrent client connections permitted on the " + "administrative interface (admin_address/admin_port). 0 (the default) " + "means no limit.", + GLOBAL_VAR(admin_max_connections), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, 100000), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD, + NOT_IN_BINLOG, ON_CHECK(nullptr), ON_UPDATE(nullptr)); + static Sys_var_ulong Sys_max_connect_errors( "max_connect_errors", "If there is more than this number of interrupted connections from "