diff --git a/include/mysql/service_thd_wait.h b/include/mysql/service_thd_wait.h index 2e7318a610f7..ebe4581b6e23 100644 --- a/include/mysql/service_thd_wait.h +++ b/include/mysql/service_thd_wait.h @@ -56,7 +56,7 @@ class THD; #define MYSQL_THD THD * -/* +/** One should only report wait events that could potentially block for a long time. A mutex wait is too short of an event to report. The reason is that an event which is reported leads to a new thread starts @@ -69,9 +69,13 @@ class THD; holds true for global read locks, table locks and other meta data locks. Another event of interest is going to sleep for an extended time. - Note that user-level locks no longer use THD_WAIT_USER_LOCK wait type. + @note User-level locks no longer use THD_WAIT_USER_LOCK wait type. Since their implementation relies on metadata locks manager it uses THD_WAIT_META_DATA_LOCK instead. + + @note THD_WAIT_ADMIT is a fake wait type communicating that query has been + parsed and is ready for execution. The query attributes and sql command + are available at this point. */ typedef enum _thd_wait_type_e { THD_WAIT_SLEEP = 1, @@ -80,14 +84,15 @@ typedef enum _thd_wait_type_e { THD_WAIT_GLOBAL_LOCK = 4, THD_WAIT_META_DATA_LOCK = 5, THD_WAIT_TABLE_LOCK = 6, - THD_WAIT_USER_LOCK = 7, + THD_WAIT_INNODB_CONC = 7, THD_WAIT_BINLOG = 8, THD_WAIT_GROUP_COMMIT = 9, THD_WAIT_SYNC = 10, THD_WAIT_FOR_HLC = 11, THD_WAIT_NET_IO = 12, THD_WAIT_YIELD = 13, - THD_WAIT_LAST = 14 + THD_WAIT_ADMIT = 14, + THD_WAIT_LAST = 15 } thd_wait_type; extern "C" struct thd_wait_service_st { diff --git a/mysql-test/suite/sys_vars/r/admission_control_wait_events_basic.result b/mysql-test/suite/sys_vars/r/admission_control_wait_events_basic.result index 461c3baa157b..b3735a7e0652 100644 --- a/mysql-test/suite/sys_vars/r/admission_control_wait_events_basic.result +++ b/mysql-test/suite/sys_vars/r/admission_control_wait_events_basic.result @@ -18,13 +18,17 @@ SET GLOBAL admission_control_wait_events='YIELD'; SELECT @@global.admission_control_wait_events; @@global.admission_control_wait_events YIELD -SET GLOBAL admission_control_wait_events='SLEEP,NET_IO,META_DATA_LOCK,ROW_LOCK,YIELD'; +SET GLOBAL admission_control_wait_events='SLEEP,ROW_LOCK,META_DATA_LOCK,INNODB_CONC,NET_IO,YIELD'; SELECT @@global.admission_control_wait_events; @@global.admission_control_wait_events -SLEEP,ROW_LOCK,META_DATA_LOCK,NET_IO,YIELD +SLEEP,ROW_LOCK,META_DATA_LOCK,INNODB_CONC,NET_IO,YIELD +SET GLOBAL admission_control_wait_events='SLEEP,SLEEP'; +SELECT @@global.admission_control_wait_events; +@@global.admission_control_wait_events +SLEEP SET GLOBAL admission_control_wait_events='YIELD,NONEXISTING_BIT'; ERROR 42000: Variable 'admission_control_wait_events' can't be set to the value of 'NONEXISTING_BIT' SELECT @@global.admission_control_wait_events; @@global.admission_control_wait_events -SLEEP,ROW_LOCK,META_DATA_LOCK,NET_IO,YIELD +SLEEP set global admission_control_wait_events = @saved_admission_control_wait_events; diff --git a/mysql-test/suite/sys_vars/t/admission_control_wait_events_basic.test b/mysql-test/suite/sys_vars/t/admission_control_wait_events_basic.test index 38310ace7d32..e6c5078f5f7b 100644 --- a/mysql-test/suite/sys_vars/t/admission_control_wait_events_basic.test +++ b/mysql-test/suite/sys_vars/t/admission_control_wait_events_basic.test @@ -12,7 +12,11 @@ SELECT @@global.admission_control_wait_events; SET GLOBAL admission_control_wait_events='YIELD'; SELECT @@global.admission_control_wait_events; -SET GLOBAL admission_control_wait_events='SLEEP,NET_IO,META_DATA_LOCK,ROW_LOCK,YIELD'; +SET GLOBAL admission_control_wait_events='SLEEP,ROW_LOCK,META_DATA_LOCK,INNODB_CONC,NET_IO,YIELD'; +SELECT @@global.admission_control_wait_events; + +# check that repeating the same value is OK +SET GLOBAL admission_control_wait_events='SLEEP,SLEEP'; SELECT @@global.admission_control_wait_events; # checking that setting variable to a non existing value raises error diff --git a/mysql-test/suite/thread_pool/r/admission_control.result b/mysql-test/suite/thread_pool/r/admission_control.result new file mode 100644 index 000000000000..03ba25c77088 --- /dev/null +++ b/mysql-test/suite/thread_pool/r/admission_control.result @@ -0,0 +1,160 @@ +create database test_db; +create database test_db2; +create user 'test_user'@'localhost'; +grant all on test_db.* to 'test_user'@'localhost'; +grant all on test_db2.* to 'test_user'@'localhost'; +grant all on test.* to 'test_user'@'localhost'; +use test_db2; +use test_db; +set @start_max_running_queries= @@global.thread_pool_max_running_queries; +set @start_max_waiting_queries= @@global.thread_pool_max_waiting_queries; +set @@global.thread_pool_max_running_queries=10; +set @@global.thread_pool_max_waiting_queries=5; +create table t1(a int) engine=InnoDB; +lock table t1 write; +Threads waiting for admission will have appropriate state set in processlist. +Super user is exempted from admission control checks. +select * from t1; +a +set @@global.thread_pool_admission_control_filter = 'USE'; +select @@global.thread_pool_admission_control_filter; +@@global.thread_pool_admission_control_filter +USE +Maximum waiting queries reached. So this would hit an error. +use test_db; +select * from t1|||| +ERROR HY000: Maximum waiting queries 5 reached for database `test_db` +Maximum waiting queries reached. So this would hit an error. +use test_db2; +create table t1_test(aaa int); +insert into t1_test values (1); +select aaa from t1_test; +drop table t1_test; +use test_db; +select * from t1|||| +aaa +1 +ERROR HY000: Maximum waiting queries 5 reached for database `test_db` +use test_db; +select * from t1; +ERROR HY000: Maximum waiting queries 5 reached for database `test_db` +set @@global.thread_pool_admission_control_filter = ''; +select @@global.thread_pool_admission_control_filter; +@@global.thread_pool_admission_control_filter + +Check status variables +aborted_queries = 3 +running_queries = 10 +waiting_queries = 5 +select * from information_schema.tp_admission_control_entities where schema_name like 'test_db%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test_db 5 10 3 0 15 0 +test_db2 0 0 0 0 0 0 +Filled up queues on one db doesn't affect queries on other db. +use test_db2; +select * from information_schema.tp_admission_control_entities where schema_name like 'test_db%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test_db 5 10 3 0 15 0 +test_db2 0 1 0 0 1 0 +set @@global.thread_pool_max_waiting_queries=6; +Kill a thread that is waiting for admission. +select count(*) from t1; +kill ID; +use test_db; +unlock tables; +Verify the waiting queries received wakeup signal. +select count(*) from t1; +count(*) +15 +set @save_admission_control_by_trx = @@global.thread_pool_admission_control_by_trx; +select @save_admission_control_by_trx; +@save_admission_control_by_trx +0 +set @@global.thread_pool_max_running_queries=5; +set @@global.thread_pool_max_waiting_queries=10; +# By default, open transaction has no effect on running queries +select count(*) from t1; +count(*) +15 +# Test: open transactions will take slots in running queries, +# and will not be blocked +set @@global.thread_pool_admission_control_filter = 'BEGIN,COMMIT,ROLLBACK'; +select @@global.thread_pool_admission_control_filter; +@@global.thread_pool_admission_control_filter +BEGIN,COMMIT,ROLLBACK +set @@global.thread_pool_admission_control_by_trx = true; +SELECT @@global.thread_pool_admission_control_by_trx; +@@global.thread_pool_admission_control_by_trx +1 +Open transaction is able to continue running queries +connection con_max_wait; +New queries will be rejected (waiting queue is full) +select * from t1; +ERROR HY000: Maximum waiting queries 10 reached for database `test_db` +New transactions will be rejected (waiting queue is full) +begin; +select * from t1; +ERROR HY000: Maximum waiting queries 10 reached for database `test_db` +aborted_queries will increase by 2 +Committing a transaction will free up the running query slots +The waiting queries will be unblocked +Check status variables +include/assert.inc [DB Admission control waiting queries should be zero] +include/assert.inc [DB Admission control running queries should be zero] +include/assert.inc [DB Admission control aborted queries should be five] +select * from information_schema.tp_admission_control_entities where schema_name like 'test_db%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test_db 0 0 5 0 13 0 +test_db2 0 0 0 0 0 0 +set @@global.thread_pool_admission_control_by_trx = @save_admission_control_by_trx; +select @@global.thread_pool_admission_control_by_trx; +@@global.thread_pool_admission_control_by_trx +0 +set @@global.thread_pool_admission_control_filter = ''; +select @@global.thread_pool_admission_control_filter; +@@global.thread_pool_admission_control_filter + +# End of open transaction test +# Test admission_control_queue_timeout +use test_db; +set @@global.thread_pool_max_running_queries=1; +set @@global.thread_pool_max_waiting_queries=5; +set @save_admission_control_filter = @@global.thread_pool_admission_control_filter; +set @save_admission_control_queue_timeout = @@global.thread_pool_admission_control_queue_timeout; +set @@global.thread_pool_admission_control_queue_timeout = 100; +set @@global.thread_pool_admission_control_filter = 'BEGIN,COMMIT,ROLLBACK'; +create table t2(a int primary key) engine=InnoDB; +begin; +insert into t2 values (1); +begin; +insert into t2 values (1); +insert into t2 values (2); +ERROR HY000: Got timeout while waiting on admission control queue for database `test_db` +rollback; +rollback; +drop table t2; +set @@global.thread_pool_admission_control_filter = @save_admission_control_filter; +set @@global.thread_pool_admission_control_queue_timeout = @save_admission_control_queue_timeout; +timeout_queries should be 1 +timeout_queries = 1 +waiting_queries should be 0 +waiting_queries = 0 +select * from information_schema.tp_admission_control_entities where schema_name like 'test_db%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test_db 0 0 5 1 15 0 +test_db2 0 0 0 0 0 0 +reset global.thread_pool_max_running_queries and global.thread_pool_max_waiting_queries +set @@global.thread_pool_max_running_queries=10; +set @@global.thread_pool_max_waiting_queries=5; +Run parallel load and drop the database. +set @@global.thread_pool_max_waiting_queries=0; +Cleanup. +Verify there are no waiting threads. +select count(*) from information_schema.processlist where state='waiting for admission'; +count(*) +0 +select * from information_schema.tp_admission_control_entities where schema_name like 'test_db%' order by schema_name; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +set @@global.thread_pool_max_running_queries=@start_max_running_queries; +set @@global.thread_pool_max_waiting_queries=@start_max_waiting_queries; +drop user test_user@localhost; diff --git a/mysql-test/suite/thread_pool/r/admission_control_hang.result b/mysql-test/suite/thread_pool/r/admission_control_hang.result new file mode 100644 index 000000000000..b41f2dc3e57a --- /dev/null +++ b/mysql-test/suite/thread_pool/r/admission_control_hang.result @@ -0,0 +1,93 @@ +create database test_db; +create user test_user@localhost; +grant all on test_db.* to test_user@localhost; +set @start_max_running_queries = @@global.max_running_queries; +set @@global.max_running_queries = 4; +set @start_innodb_lock_wait_timeout = @@global.innodb_lock_wait_timeout; +set @@global.innodb_lock_wait_timeout = 10000; +set @start_admission_control_filter = @@global.admission_control_filter; +set @@global.admission_control_filter = 'COMMIT'; +create table t1 (a int) engine=innodb; +insert into t1 values(1); +begin; +update t1 set a=2 where a=1; +update t1 set a=2 where a=1; +update t1 set a=2 where a=1; +update t1 set a=2 where a=1; +update t1 set a=2 where a=1; +set @@global.admission_control_filter = 'USE'; +select @@global.admission_control_filter; +@@global.admission_control_filter +USE +use test; +use test_db; +set @@global.admission_control_filter = 'ALTER,BEGIN,COMMIT,CREATE,DELETE,DROP,INSERT,LOAD,SELECT,SET,REPLACE,TRUNCATE,UPDATE,SHOW,ROLLBACK'; +select @@global.admission_control_filter; +@@global.admission_control_filter +ALTER,BEGIN,COMMIT,CREATE,DELETE,DROP,INSERT,LOAD,SELECT,SET,REPLACE,ROLLBACK,TRUNCATE,UPDATE,SHOW +create table t2(a int) engine=innodb; +begin; +insert into t2 values(1); +update t2 set a=2 where a=1; +commit; +SHOW TABLES LIKE 't2'; +Tables_in_test_db (t2) +t2 +begin; +alter table t2 rename t3; +select * from t3; +a +2 +delete from t3; +set @val = 1; +truncate table t3; +rollback; +drop table t3; +set @save_admission_control_by_trx = @@global.admission_control_by_trx; +select @save_admission_control_by_trx; +@save_admission_control_by_trx +0 +# Turn on admission_control_by_trx +set @@global.admission_control_by_trx = true; +SELECT @@global.admission_control_by_trx; +@@global.admission_control_by_trx +1 +create table t2(a int) engine=innodb; +begin; +insert into t2 values(1); +update t2 set a=2 where a=1; +commit; +SHOW TABLES LIKE 't2'; +Tables_in_test_db (t2) +t2 +begin; +alter table t2 rename t3; +select * from t3; +a +2 +delete from t3; +set @val = 1; +truncate table t3; +rollback; +drop table t3; +set @@global.admission_control_filter = default; +select @@global.admission_control_filter; +@@global.admission_control_filter + +select count(*) from t1; +count(*) +1 +set @@global.admission_control_by_trx = @save_admission_control_by_trx; +select @@global.admission_control_by_trx; +@@global.admission_control_by_trx +0 +set @@global.admission_control_filter = 'COMMIT'; +select @@global.admission_control_filter; +@@global.admission_control_filter +COMMIT +commit; +set @@global.max_running_queries = @start_max_running_queries; +set @@global.innodb_lock_wait_timeout = @start_innodb_lock_wait_timeout; +set @@global.admission_control_filter = @start_admission_control_filter; +drop database test_db; +drop user test_user@localhost; diff --git a/mysql-test/suite/thread_pool/r/admission_control_multi_query.result b/mysql-test/suite/thread_pool/r/admission_control_multi_query.result new file mode 100644 index 000000000000..b9f3955224d3 --- /dev/null +++ b/mysql-test/suite/thread_pool/r/admission_control_multi_query.result @@ -0,0 +1,8 @@ +create database test_db; +create user test_user@localhost identified with 'mysql_native_password' BY ''; +grant all on test_db.* to test_user@localhost; +grant all on test.* to test_user@localhost; +use test_db; +create table t1 (a int primary key, b int) engine=InnoDB; +drop database test_db; +drop user test_user@localhost; diff --git a/mysql-test/suite/thread_pool/r/admission_control_multi_query_wait_events.result b/mysql-test/suite/thread_pool/r/admission_control_multi_query_wait_events.result new file mode 100644 index 000000000000..b9f3955224d3 --- /dev/null +++ b/mysql-test/suite/thread_pool/r/admission_control_multi_query_wait_events.result @@ -0,0 +1,8 @@ +create database test_db; +create user test_user@localhost identified with 'mysql_native_password' BY ''; +grant all on test_db.* to test_user@localhost; +grant all on test.* to test_user@localhost; +use test_db; +create table t1 (a int primary key, b int) engine=InnoDB; +drop database test_db; +drop user test_user@localhost; diff --git a/mysql-test/suite/thread_pool/r/admission_control_multi_query_weighted.result b/mysql-test/suite/thread_pool/r/admission_control_multi_query_weighted.result new file mode 100644 index 000000000000..b9f3955224d3 --- /dev/null +++ b/mysql-test/suite/thread_pool/r/admission_control_multi_query_weighted.result @@ -0,0 +1,8 @@ +create database test_db; +create user test_user@localhost identified with 'mysql_native_password' BY ''; +grant all on test_db.* to test_user@localhost; +grant all on test.* to test_user@localhost; +use test_db; +create table t1 (a int primary key, b int) engine=InnoDB; +drop database test_db; +drop user test_user@localhost; diff --git a/mysql-test/suite/thread_pool/r/admission_control_queue.result b/mysql-test/suite/thread_pool/r/admission_control_queue.result new file mode 100644 index 000000000000..0577c3a03112 --- /dev/null +++ b/mysql-test/suite/thread_pool/r/admission_control_queue.result @@ -0,0 +1,138 @@ +create database test_db; +create user test_user@localhost; +grant all on test_db.* to test_user@localhost; +grant all on test.* to test_user@localhost; +use test_db; +set @save_max_running_queries = @@thread_pool_max_running_queries; +set @save_max_waiting_queries = @@thread_pool_max_waiting_queries; +set @save_admission_control_weights = @@thread_pool_admission_control_weights; +set global thread_pool_max_running_queries = 5; +set global thread_pool_max_waiting_queries = 2000; +# +# Test admission_control_queue variable works +# +select GET_LOCK('lock1', -1); +GET_LOCK('lock1', -1) +1 +select GET_LOCK('lock1', -1); +select * from information_schema.tp_admission_control_entities where waiting_queries + running_queries + aborted_queries + timeout_queries != 0 and schema_name like 'test%'; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test_db 0 1 0 0 1 0 +select * from information_schema.tp_admission_control_queue; +SCHEMA_NAME QUEUE_ID WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES +test_db 0 0 1 0 0 +select RELEASE_LOCK('lock1'); +RELEASE_LOCK('lock1') +1 +select * from information_schema.tp_admission_control_entities where waiting_queries + running_queries + aborted_queries + timeout_queries != 0 and schema_name like 'test%'; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +select * from information_schema.tp_admission_control_queue; +SCHEMA_NAME QUEUE_ID WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES +GET_LOCK('lock1', -1) +1 +# +# Test connection attribute overrides +# +select GET_LOCK('lock1', -1); +GET_LOCK('lock1', -1) +1 +use test_db; +select GET_LOCK('lock1', -1); +select * from information_schema.tp_admission_control_entities where waiting_queries + running_queries + aborted_queries + timeout_queries != 0 and schema_name like 'test%'; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test_db 0 1 0 0 1 0 +select * from information_schema.tp_admission_control_queue; +SCHEMA_NAME QUEUE_ID WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES +test_db 1 0 1 0 0 +select RELEASE_LOCK('lock1'); +RELEASE_LOCK('lock1') +1 +select * from information_schema.tp_admission_control_queue; +SCHEMA_NAME QUEUE_ID WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES +GET_LOCK('lock1', -1) +1 +# +# Test connection and query attribute overrides +# +select GET_LOCK('lock1', -1); +GET_LOCK('lock1', -1) +1 +use test_db; +select GET_LOCK('lock1', -1); +select * from information_schema.tp_admission_control_entities where waiting_queries + running_queries + aborted_queries + timeout_queries != 0 and schema_name like 'test%'; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test_db 0 1 0 0 1 0 +select * from information_schema.tp_admission_control_queue; +SCHEMA_NAME QUEUE_ID WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES +test_db 2 0 1 0 0 +select RELEASE_LOCK('lock1'); +RELEASE_LOCK('lock1') +1 +select * from information_schema.tp_admission_control_entities where waiting_queries + running_queries + aborted_queries + timeout_queries != 0 and schema_name like 'test%'; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +select * from information_schema.tp_admission_control_queue; +SCHEMA_NAME QUEUE_ID WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES +GET_LOCK('lock1', -1) +1 +# +# Test weights (2,3) on queue 1,2 +# +set global thread_pool_admission_control_weights = "1,2,3"; +select GET_LOCK('lock1', -1); +GET_LOCK('lock1', -1) +1 +select GET_LOCK('lock2', -1); +GET_LOCK('lock2', -1) +1 +# Set up 5 running queries +select GET_LOCK('lock1', -1); +select GET_LOCK('lock1', -1); +select GET_LOCK('lock1', -1); +select GET_LOCK('lock1', -1); +select GET_LOCK('lock1', -1); +select * from information_schema.tp_admission_control_entities where waiting_queries + running_queries + aborted_queries + timeout_queries != 0 and schema_name like 'test%'; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test_db 0 5 0 0 5 0 +select * from information_schema.tp_admission_control_queue; +SCHEMA_NAME QUEUE_ID WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES +test_db 0 0 5 0 0 +# Set up 5 waiting queries each for queue 1 and 2 +select GET_LOCK('lock2', -1); +select GET_LOCK('lock2', -1); +select GET_LOCK('lock2', -1); +select GET_LOCK('lock2', -1); +select GET_LOCK('lock2', -1); +select GET_LOCK('lock2', -1); +select GET_LOCK('lock2', -1); +select GET_LOCK('lock2', -1); +select GET_LOCK('lock2', -1); +select GET_LOCK('lock2', -1); +# Check that 10 queries are waiting +select * from information_schema.tp_admission_control_entities where waiting_queries + running_queries + aborted_queries + timeout_queries != 0 and schema_name like 'test%'; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test_db 10 5 0 0 15 0 +select * from information_schema.tp_admission_control_queue; +SCHEMA_NAME QUEUE_ID WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES +test_db 0 0 5 0 0 +test_db 1 5 0 0 0 +test_db 2 5 0 0 0 +select RELEASE_LOCK('lock1'); +RELEASE_LOCK('lock1') +1 +# Check that 5 of the 10 queries are now scheduled with 2-3 ratio. +select * from information_schema.tp_admission_control_entities where waiting_queries + running_queries + aborted_queries + timeout_queries != 0 and schema_name like 'test%'; +SCHEMA_NAME WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES CONNECTIONS REJECTED_CONNECTIONS +test_db 5 5 0 0 10 0 +select * from information_schema.tp_admission_control_queue; +SCHEMA_NAME QUEUE_ID WAITING_QUERIES RUNNING_QUERIES ABORTED_QUERIES TIMEOUT_QUERIES +test_db 1 3 2 0 0 +test_db 2 2 3 0 0 +# Cleanup +select RELEASE_LOCK('lock2'); +RELEASE_LOCK('lock2') +1 +set global thread_pool_max_running_queries = @save_max_running_queries; +set global thread_pool_max_waiting_queries = @save_max_waiting_queries; +set global thread_pool_admission_control_weights = @save_admission_control_weights; +drop database test_db; +drop user test_user@localhost; diff --git a/mysql-test/suite/thread_pool/r/admission_control_stress.result b/mysql-test/suite/thread_pool/r/admission_control_stress.result new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/mysql-test/suite/thread_pool/r/admission_control_yield.result b/mysql-test/suite/thread_pool/r/admission_control_yield.result new file mode 100644 index 000000000000..f4ae4495209c --- /dev/null +++ b/mysql-test/suite/thread_pool/r/admission_control_yield.result @@ -0,0 +1,101 @@ +create database test_db; +create user test_user@localhost; +grant all on test_db.* to test_user@localhost; +use test_db; +set @save_max_running_queries = @@thread_pool_max_running_queries; +set @save_max_waiting_queries = @@thread_pool_max_waiting_queries; +set @save_admission_control_wait_events = @@thread_pool_admission_control_wait_events; +set @save_admission_control_filter = @@thread_pool_admission_control_filter; +set @save_admission_control_yield_freq = @@thread_pool_admission_control_yield_freq; +set global thread_pool_max_running_queries = 1; +set global thread_pool_max_waiting_queries = 2000; +set global thread_pool_admission_control_filter = 'SET'; +set thread_pool_admission_control_queue_timeout = 5000; +# +# Test admission_control_wait_events = SLEEP +# +set global thread_pool_admission_control_wait_events = ''; +select SLEEP(1000000); +select 1; +ERROR HY000: Got timeout while waiting on admission control queue for database `test_db` +SLEEP(1000000) +1 +set global thread_pool_admission_control_wait_events = 'SLEEP'; +select SLEEP(1000000); +select 1; +1 +1 +SLEEP(1000000) +1 +# +# Test admission_control_wait_events = ROW_LOCK +# +set global thread_pool_admission_control_wait_events = ''; +create table t (i int primary key) engine=innodb; +insert into t values (1); +begin; +select * from t where i = 1 for update; +i +1 +select * from t where i = 1 for update;; +select 1; +ERROR HY000: Got timeout while waiting on admission control queue for database `test_db` +ERROR 70100: Query execution was interrupted +set global thread_pool_admission_control_wait_events = 'ROW_LOCK'; +select * from t where i = 1 for update;; +select 1; +1 +1 +rollback; +drop table t; +ERROR 70100: Query execution was interrupted +# +# Test admission_control_wait_events = META_DATA_LOCK +# +set global thread_pool_admission_control_wait_events = ''; +select GET_LOCK('lock', -1); +GET_LOCK('lock', -1) +1 +select GET_LOCK('lock', -1); +select 1; +ERROR HY000: Got timeout while waiting on admission control queue for database `test_db` +ERROR 70100: Query execution was interrupted +set global thread_pool_admission_control_wait_events = 'META_DATA_LOCK'; +select GET_LOCK('lock', -1); +select 1; +1 +1 +select RELEASE_LOCK('lock'); +RELEASE_LOCK('lock') +1 +ERROR 70100: Query execution was interrupted +# +# Test admission_control_wait_events = YIELD +# +set global thread_pool_admission_control_wait_events = ''; +create table t1 (i int primary key) engine=innodb; +insert into t1 values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10); +create table t2 (i int primary key) engine=innodb; +insert into t2 select a.i + 10 * b.i + 100 * c.i + 1000 * d.i from t1 a, t1 b, t1 c, t1 d; +select * from t2 a, t2 b, t2 c, t2 d, t2 e, t2 f, t2 g, t2 h limit 10000000000, 1;; +Timeout in wait_condition.inc for select count(*) = 1 from information_schema.processlist where state = 'Sending data' +select 1; +ERROR HY000: Got timeout while waiting on admission control queue for database `test_db` +ERROR 70100: Query execution was interrupted +set global thread_pool_admission_control_wait_events = 'YIELD'; +set global thread_pool_admission_control_yield_freq = 1; +select * from t2 a, t2 b, t2 c, t2 d, t2 e, t2 f, t2 g, t2 h limit 10000000000, 1;; +Timeout in wait_condition.inc for select count(*) = 1 from information_schema.processlist where state = 'Sending data' +select 1; +1 +1 +drop table t1; +drop table t2; +ERROR 70100: Query execution was interrupted +set global thread_pool_max_running_queries = @save_max_running_queries; +set global thread_pool_max_waiting_queries = @save_max_waiting_queries; +set global thread_pool_admission_control_wait_events = @save_admission_control_wait_events; +set global thread_pool_admission_control_filter = @save_admission_control_filter; +set global thread_pool_admission_control_yield_freq = @save_admission_control_yield_freq; +drop database test_db; +drop user test_user@localhost; diff --git a/mysql-test/suite/thread_pool/r/admission_control_yield_debug.result b/mysql-test/suite/thread_pool/r/admission_control_yield_debug.result new file mode 100644 index 000000000000..4030a92f9dd3 --- /dev/null +++ b/mysql-test/suite/thread_pool/r/admission_control_yield_debug.result @@ -0,0 +1,30 @@ +create database test_db; +create user test_user@localhost; +grant all on test_db.* to test_user@localhost; +grant SYSTEM_VARIABLES_ADMIN on *.* to test_user@localhost; +use test_db; +set @save_max_running_queries = @@thread_pool_max_running_queries; +set @save_max_waiting_queries = @@thread_pool_max_waiting_queries; +set @save_admission_control_wait_events = @@thread_pool_admission_control_wait_events; +set global thread_pool_max_running_queries = 1; +set global thread_pool_max_waiting_queries = 2000; +set thread_pool_admission_control_queue_timeout = 100; +# +# Test admission_control_wait_events = NET_IO +# We need debug sync to simulate slow clients +# +set global thread_pool_admission_control_wait_events = ''; +set session debug = "+d,simulate_net_write_delay"; +SELECT REPEAT('X', @@max_allowed_packet);; +select 1; +ERROR HY000: Got timeout while waiting on admission control queue for database `test_db` +set global thread_pool_admission_control_wait_events = 'NET_IO'; +SELECT REPEAT('X', @@max_allowed_packet);; +select 1; +1 +1 +set global thread_pool_max_running_queries = @save_max_running_queries; +set global thread_pool_max_waiting_queries = @save_max_waiting_queries; +set global thread_pool_admission_control_wait_events = @save_admission_control_wait_events; +drop database test_db; +drop user test_user@localhost; diff --git a/mysql-test/suite/thread_pool/t/admission_control.test b/mysql-test/suite/thread_pool/t/admission_control.test new file mode 100644 index 000000000000..545bdbb29436 --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control.test @@ -0,0 +1,446 @@ +--source include/have_thread_pool_plugin.inc + +--source include/count_sessions.inc +create database test_db; +create database test_db2; +create user 'test_user'@'localhost'; +grant all on test_db.* to 'test_user'@'localhost'; +grant all on test_db2.* to 'test_user'@'localhost'; +grant all on test.* to 'test_user'@'localhost'; + +let $start_aborted_queries = query_get_value(show global status like "Thread_pool_admission_control_aborted_queries", Value, 1); +let $start_running_queries = query_get_value(show global status like "Thread_pool_admission_control_running_queries", Value, 1); +let $start_waiting_queries = query_get_value(show global status like "Thread_pool_admission_control_waiting_queries", Value, 1); + +let $ac_entities_query=select * from information_schema.tp_admission_control_entities where schema_name like 'test_db%' order by schema_name; + +connection default; +use test_db2; +use test_db; +set @start_max_running_queries= @@global.thread_pool_max_running_queries; +set @start_max_waiting_queries= @@global.thread_pool_max_waiting_queries; +set @@global.thread_pool_max_running_queries=10; +set @@global.thread_pool_max_waiting_queries=5; +create table t1(a int) engine=InnoDB; + +# Write lock here ensures the insert queries will block on row lock and be +# in running queue until unlock tables is executed. +lock table t1 write; + +disable_query_log; +disable_result_log; +let $i= 15; +while ($i) +{ + connect (con$i, localhost, test_user,,test_db); + dec $i; +} +let $i= 10; +while($i) +{ + connection con$i; + # These queries will be in running queue + send insert into t1 values(1); + dec $i; +} +let $i= 15; +while ($i != 10) +{ + # These queries will be in waiting queue + connection con$i; + send insert into t1 values(1); + dec $i; +} +enable_query_log; +enable_result_log; + +connection default; +--echo Threads waiting for admission will have appropriate state set in processlist. +let $wait_condition= + select count(*)=5 from information_schema.processlist where state='waiting for admission'; +source include/wait_condition.inc; +--echo Super user is exempted from admission control checks. +select * from t1; + +# set filter to bypass USE statement +--let $saved_admission_control_filter = `SELECT @@global.thread_pool_admission_control_filter` +set @@global.thread_pool_admission_control_filter = 'USE'; +select @@global.thread_pool_admission_control_filter; + +# Test failure case +# Default db is intentionally not set when creating the connection +connect (con_max_wait, localhost, test_user,,); +connection con_max_wait; + +# Test multi-statement packet with use statement. +delimiter ||||; +--echo Maximum waiting queries reached. So this would hit an error. +--error ER_DB_ADMISSION_CONTROL +# switch the session's default db to test_db, which (the use statement) should +# be bypassed in admission control. The select statement will fail. +use test_db; +select * from t1|||| + +# Query will only fail on test_db, but not on test_db2 +--echo Maximum waiting queries reached. So this would hit an error. +--error ER_DB_ADMISSION_CONTROL +use test_db2; +create table t1_test(aaa int); +insert into t1_test values (1); +select aaa from t1_test; +drop table t1_test; +use test_db; +select * from t1|||| + +delimiter ;|||| + +# USE is ok +use test_db; + +# Single statement +--error ER_DB_ADMISSION_CONTROL +select * from t1; + +disconnect con_max_wait; + +connection default; +# reset admission_control_filter +--eval set @@global.thread_pool_admission_control_filter = '$saved_admission_control_filter' +select @@global.thread_pool_admission_control_filter; + +--echo Check status variables +let $aborted_queries = query_get_value(show global status like "Thread_pool_admission_control_aborted_queries", Value, 1); +let $running_queries = query_get_value(show global status like "Thread_pool_admission_control_running_queries", Value, 1); +let $waiting_queries = query_get_value(show global status like "Thread_pool_admission_control_waiting_queries", Value, 1); +let $aborted_queries = `select $aborted_queries - $start_aborted_queries`; +let $running_queries = `select $running_queries - $start_running_queries`; +let $waiting_queries = `select $waiting_queries - $start_waiting_queries`; +--echo aborted_queries = $aborted_queries +--echo running_queries = $running_queries +--echo waiting_queries = $waiting_queries +eval $ac_entities_query; + +--echo Filled up queues on one db doesn't affect queries on other db. +connect (con_test, localhost, test_user,,test_db2); +connection con_test; +use test_db2; +eval $ac_entities_query; +disconnect con_test; + +connection default; +set @@global.thread_pool_max_waiting_queries=6; + +--echo Kill a thread that is waiting for admission. +connect (killed_connection, localhost, test_user,,test_db); +connection killed_connection; +send select count(*) from t1; +connection default; +# Need to first wait for the state in processlist, otherwise we run the risk +# of a race condition and test will fail because $id would be empty. +let $wait_timeout= 300; +let $wait_condition= + select count(*) = 1 from information_schema.processlist where state='waiting for admission' and info='select count(*) from t1'; +source include/wait_condition.inc; +let $id= + `select id from information_schema.processlist where state='waiting for admission' and info='select count(*) from t1'`; +--replace_result $id ID +eval kill $id; +disconnect killed_connection; + +# Insert queries sent above will be unblocked by this. +use test_db; +unlock tables; + +disable_query_log; +disable_result_log; +let $i= 15; +while ($i) +{ + connection con$i; + reap; + dec $i; +} +enable_query_log; +enable_result_log; +connection default; +--echo Verify the waiting queries received wakeup signal. +select count(*) from t1; + +set @save_admission_control_by_trx = @@global.thread_pool_admission_control_by_trx; +select @save_admission_control_by_trx; +set @@global.thread_pool_max_running_queries=5; +set @@global.thread_pool_max_waiting_queries=10; +--echo # By default, open transaction has no effect on running queries +disable_query_log; +disable_result_log; +let $i= 5; # max_running_queries is 5 +while($i) +{ + connection con$i; + # 5 Open transactions + begin; + select count(*) from t1; + dec $i; +} +enable_query_log; +enable_result_log; +# query will succeed +connection con11; +select count(*) from t1; + +--echo # Test: open transactions will take slots in running queries, +--echo # and will not be blocked +connection default; +--let $saved_admission_control_filter = `SELECT @@global.thread_pool_admission_control_filter` +set @@global.thread_pool_admission_control_filter = 'BEGIN,COMMIT,ROLLBACK'; +select @@global.thread_pool_admission_control_filter; +set @@global.thread_pool_admission_control_by_trx = true; +SELECT @@global.thread_pool_admission_control_by_trx; +disable_query_log; +disable_result_log; +let $i= 5; # max_running_queries is 5 +while($i) +{ + connection con$i; + # Open transactions will take up slots in running queue + begin; + select count(*) from t1; + dec $i; +} +let $i= 15; # max_waiting_queries is 10 +while ($i != 5) +{ + # These queries will be in waiting queue + connection con$i; + send select count(*) from t1; + dec $i; +} +enable_query_log; +enable_result_log; + +connection default; +let $wait_timeout= 300; +let $wait_condition= + select count(*)=10 from information_schema.processlist where state='waiting for admission'; +source include/wait_condition.inc; + +--echo Open transaction is able to continue running queries +disable_query_log; +disable_result_log; +let $i= 5; +while($i) +{ + connection con$i; + select count(*) from t1; + dec $i; +} +enable_query_log; +enable_result_log; + +connect (con_max_wait, localhost, test_user,,test_db); +--echo connection con_max_wait; +connection con_max_wait; +--echo New queries will be rejected (waiting queue is full) +error ER_DB_ADMISSION_CONTROL; +select * from t1; +--echo New transactions will be rejected (waiting queue is full) +begin; +error ER_DB_ADMISSION_CONTROL; +select * from t1; +disconnect con_max_wait; +--echo aborted_queries will increase by 2 + +--echo Committing a transaction will free up the running query slots +disable_query_log; +let $i= 5; +# commit con4 and con5 +while($i > 3) +{ + connection con$i; + commit; + dec $i; +} +# rollback con2 explicitly +connection con2; +rollback; +# close con1 to rollback +connection con1; +disconnect con1; +connection con3; +# get connection id of con3 +let $con3_id = `select connection_id()`; +# kill con3 to rollback +connection default; +eval kill $con3_id; +enable_query_log; + +--echo The waiting queries will be unblocked +disable_query_log; +disable_result_log; +let $i= 15; +while ($i != 5) +{ + connection con$i; + reap; + dec $i; +} +enable_query_log; +enable_result_log; + +connection default; +let $wait_timeout= 300; + +# Total 15-2 = 13 test_user connections (con1 & con3 were closed) +# wait for con1 and con3 to be killed or disconnected +let $wait_condition= + select count(*)=13 from information_schema.processlist where user='test_user'; +source include/wait_condition.inc; + +# wait for all test_user connections becoming idle. +let $wait_condition= + select count(*)=13 from information_schema.processlist where command='Sleep' and state='' and user='test_user'; +source include/wait_condition.inc; + +--echo Check status variables +let $aborted_queries = query_get_value(show global status like "Thread_pool_admission_control_aborted_queries", Value, 1); +let $running_queries = query_get_value(show global status like "Thread_pool_admission_control_running_queries", Value, 1); +let $waiting_queries = query_get_value(show global status like "Thread_pool_admission_control_waiting_queries", Value, 1); +let $aborted_queries = `select $aborted_queries - $start_aborted_queries`; +let $running_queries = `select $running_queries - $start_running_queries`; +let $waiting_queries = `select $waiting_queries - $start_waiting_queries`; + +--let $assert_text= DB Admission control waiting queries should be zero +--let $assert_cond= $waiting_queries = 0 +--source include/assert.inc + +--let $assert_text= DB Admission control running queries should be zero +--let $assert_cond= $running_queries = 0 +--source include/assert.inc + +--let $assert_text= DB Admission control aborted queries should be five +--let $assert_cond= $aborted_queries = 5 +--source include/assert.inc + +eval $ac_entities_query; + +# restore admission_control_by_trx +connection default; +set @@global.thread_pool_admission_control_by_trx = @save_admission_control_by_trx; +select @@global.thread_pool_admission_control_by_trx; +# reset admission_control_filter +--eval set @@global.thread_pool_admission_control_filter = '$saved_admission_control_filter' +select @@global.thread_pool_admission_control_filter; +# recreate con1 and con3 +disconnect con3; +connect (con1, localhost, test_user,,test_db); +connect (con3, localhost, test_user,,test_db); +--echo # End of open transaction test + +--echo # Test admission_control_queue_timeout +connection default; +use test_db; +set @@global.thread_pool_max_running_queries=1; +set @@global.thread_pool_max_waiting_queries=5; + +set @save_admission_control_filter = @@global.thread_pool_admission_control_filter; +set @save_admission_control_queue_timeout = @@global.thread_pool_admission_control_queue_timeout; +set @@global.thread_pool_admission_control_queue_timeout = 100; +set @@global.thread_pool_admission_control_filter = 'BEGIN,COMMIT,ROLLBACK'; + +let $start_timeout_queries = query_get_value(show global status like "Thread_pool_admission_control_timeout_queries", Value, 1); + +create table t2(a int primary key) engine=InnoDB; + +connect (con_timeout1, localhost, test_user,,test_db); +begin; +insert into t2 values (1); + +connect (con_timeout2, localhost, test_user,,test_db); +begin; +send insert into t2 values (1); + +connection default; +let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "update" and info = "insert into t2 values (1)"; +--source include/wait_condition.inc + +connection con_timeout1; +--error ER_DB_ADMISSION_CONTROL_TIMEOUT +insert into t2 values (2); +rollback; + +connection con_timeout2; +reap; +rollback; + +# Cleanup +connection default; +disconnect con_timeout1; +disconnect con_timeout2; +drop table t2; + +set @@global.thread_pool_admission_control_filter = @save_admission_control_filter; +set @@global.thread_pool_admission_control_queue_timeout = @save_admission_control_queue_timeout; + +# Verify counters +let $timeout_queries = query_get_value(show global status like "Thread_pool_admission_control_timeout_queries", Value, 1); +let $timeout_queries = `select $timeout_queries - $start_timeout_queries`; +--echo timeout_queries should be 1 +--echo timeout_queries = $timeout_queries +let $waiting_queries = query_get_value(show global status like "Thread_pool_admission_control_waiting_queries", Value, 1); +--echo waiting_queries should be 0 +--echo waiting_queries = $waiting_queries +eval $ac_entities_query; + +connection default; +--echo reset global.thread_pool_max_running_queries and global.thread_pool_max_waiting_queries +set @@global.thread_pool_max_running_queries=10; +set @@global.thread_pool_max_waiting_queries=5; +--echo Run parallel load and drop the database. +# Verify the waiting queries will receive the signal from DROP DATABASE +# and exit with appropriate error ER_NO_SUCH_TABLE. max_waiting_queries=0 +# is used to not have a limit on number of waiting queries. +set @@global.thread_pool_max_waiting_queries=0; # No limit on waiting queue. +disable_query_log; +disable_result_log; +let $i= 15; +while($i) +{ + connection con$i; + # 10 queries (max_running_queries is 10) will be in running queue. + # Using sleep(10) in the insert ensures some queries end up in waiting queue. + send insert into t1(select sleep(10)); + dec $i; +} +connection default; +drop database test_db; +drop database test_db2; +let $i= 15; +while($i) +{ + connection con$i; + error 0,ER_BAD_DB_ERROR,ER_LOCK_DEADLOCK; + reap; + dec $i; +} +enable_query_log; +enable_result_log; + +--echo Cleanup. +connection default; +--echo Verify there are no waiting threads. +select count(*) from information_schema.processlist where state='waiting for admission'; +eval $ac_entities_query; +set @@global.thread_pool_max_running_queries=@start_max_running_queries; +set @@global.thread_pool_max_waiting_queries=@start_max_waiting_queries; +drop user test_user@localhost; +disable_query_log; +disable_result_log; +let $i= 15; +while ($i) +{ + disconnect con$i; + dec $i; +} +enable_query_log; +enable_result_log; +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/thread_pool/t/admission_control_hang.test b/mysql-test/suite/thread_pool/t/admission_control_hang.test new file mode 100644 index 000000000000..32122b787832 --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_hang.test @@ -0,0 +1,128 @@ +--source include/have_thread_pool_plugin.inc + +--source include/count_sessions.inc +create database test_db; +create user test_user@localhost; +grant all on test_db.* to test_user@localhost; + +set @start_max_running_queries = @@global.thread_pool_max_running_queries; +set @@global.thread_pool_max_running_queries = 4; +set @start_innodb_lock_wait_timeout = @@global.innodb_lock_wait_timeout; +set @@global.innodb_lock_wait_timeout = 10000; +set @start_admission_control_filter = @@global.thread_pool_admission_control_filter; +set @@global.thread_pool_admission_control_filter = 'COMMIT'; + +let $i= 6; +while ($i) +{ + connect (con$i, localhost, test_user,,test_db); + dec $i; +} + +connection con5; +create table t1 (a int) engine=innodb; +insert into t1 values(1); +begin; +update t1 set a=2 where a=1; + +connection default; +let $i= 4; +# Fill up the admission control running limit. +while($i) +{ + connection con$i; + # Blocked on innodb row lock + send update t1 set a=2 where a=1; + dec $i; +} + +# Test filter USE statement +connection default; +set @@global.thread_pool_admission_control_filter = 'USE'; +select @@global.thread_pool_admission_control_filter; + +connection con6; +use test; +use test_db; + +connection default; +set @@global.thread_pool_admission_control_filter = 'ALTER,BEGIN,COMMIT,CREATE,DELETE,DROP,INSERT,LOAD,SELECT,SET,REPLACE,TRUNCATE,UPDATE,SHOW,ROLLBACK'; +select @@global.thread_pool_admission_control_filter; + +# Verify the commands filtered above run fine. +connection con6; +create table t2(a int) engine=innodb; +begin; +insert into t2 values(1); +update t2 set a=2 where a=1; +commit; +SHOW TABLES LIKE 't2'; +begin; +alter table t2 rename t3; +select * from t3; +delete from t3; +set @val = 1; +truncate table t3; +rollback; +drop table t3; + +# Verify that admission_control_by_trx will honor the filters +connection default; +set @save_admission_control_by_trx = @@global.thread_pool_admission_control_by_trx; +select @save_admission_control_by_trx; +--echo # Turn on admission_control_by_trx +set @@global.thread_pool_admission_control_by_trx = true; +SELECT @@global.thread_pool_admission_control_by_trx; + +connection con6; +create table t2(a int) engine=innodb; +begin; +insert into t2 values(1); +update t2 set a=2 where a=1; +commit; +SHOW TABLES LIKE 't2'; +begin; +alter table t2 rename t3; +select * from t3; +delete from t3; +set @val = 1; +truncate table t3; +rollback; +drop table t3; + +connection default; +set @@global.thread_pool_admission_control_filter = default; +select @@global.thread_pool_admission_control_filter; + +# open transaction will be able to go through +connection con5; +select count(*) from t1; + +# restore admission_control_by_trx +connection default; +set @@global.thread_pool_admission_control_by_trx = @save_admission_control_by_trx; +select @@global.thread_pool_admission_control_by_trx; + +connection default; +set @@global.thread_pool_admission_control_filter = 'COMMIT'; +select @@global.thread_pool_admission_control_filter; + +connection con5; +# Without the COMMIT filter set above, this query gets blocked until conflicting +# queries hit timeout. +commit; + +let $i= 6; +while ($i) +{ + disconnect con$i; + dec $i; +} + +connection default; +set @@global.thread_pool_max_running_queries = @start_max_running_queries; +set @@global.innodb_lock_wait_timeout = @start_innodb_lock_wait_timeout; +set @@global.thread_pool_admission_control_filter = @start_admission_control_filter; +drop database test_db; +drop user test_user@localhost; +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/thread_pool/t/admission_control_multi_query-master.opt b/mysql-test/suite/thread_pool/t/admission_control_multi_query-master.opt new file mode 100644 index 000000000000..ca9adf04d91e --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_multi_query-master.opt @@ -0,0 +1 @@ +--innodb_lock_wait_timeout=4 --innodb_deadlock_detect=0 --loose-thread_pool_max_running_queries=5 --loose-thread_pool_max_waiting_queries=2000 diff --git a/mysql-test/suite/thread_pool/t/admission_control_multi_query.py b/mysql-test/suite/thread_pool/t/admission_control_multi_query.py new file mode 100644 index 000000000000..066b19f05bbd --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_multi_query.py @@ -0,0 +1,185 @@ +import time +import sys +import MySQLdb +from MySQLdb.constants import * +import argparse +import random +import threading +import traceback + +NUM_WORKERS = 50 +NUM_TRANSACTIONS = 2000 + +def parse_args(): + parser = argparse.ArgumentParser( + 'multi_query', + description='Generate Multi query load', + ) + parser.add_argument( + '--host', + type=str, + help='host name') + parser.add_argument( + '--port', + type=int, + help='port number') + parser.add_argument( + '--user', + type=str, + help='user name') + parser.add_argument( + '--password', + default='', + type=str, + help='password') + parser.add_argument( + '--database', + type=str, + help='database to use') + parser.add_argument( + '--weighted-queues', + action="store_true", + help='use weighted queues') + parser.add_argument( + '--wait-events', + action="store_true", + help='add wait event workload') + + return parser.parse_args() + +def is_deadlock_error(exc): + error_code = exc.args[0] + return (error_code == MySQLdb.constants.ER.LOCK_DEADLOCK or + error_code == MySQLdb.constants.ER.LOCK_WAIT_TIMEOUT) + +def generate_load(args, worker_id): + con = MySQLdb.connect(user=args.user, + passwd=args.password, + host=args.host, + port=args.port, + db=args.database) + + if args.weighted_queues: + queue_id = worker_id % 2 + print("WORKER %d: Using queue %d" % (worker_id, queue_id)) + cursor = con.cursor() + cursor.execute('set thread_pool_admission_control_queue = %d;' % \ + queue_id) + cursor.close() + + op = 1 + for i in range(NUM_TRANSACTIONS): + try: + if args.wait_events: + op = random.randint(1, 3) + if op == 1: + print("WORKER %d: Executing iteration %d" % (worker_id, i)) + cursor = con.cursor() + cursor.execute('begin;') + values = [] + for j in range(3): + val = random.randrange(1, 10000) + values.append(val) + values = sorted(values) + for val in values: + insert_sql = 'insert into t1 values(%d, 1) on duplicate ' \ + 'key update b=greatest(b+1, 0);' % (val) + cursor.execute(insert_sql) + cursor.execute("commit;") + cursor.close() + elif op == 2: + print("WORKER %d: Executing GET_LOCK %d" % (worker_id, i)) + cursor = con.cursor() + cursor.execute("select get_lock('testlock', -1)") + cursor.execute("select release_lock('testlock')") + cursor.close() + else: + print("WORKER %d: Executing SLEEP %d" % (worker_id, i)) + cursor = con.cursor() + cursor.execute("select sleep(0.1)") + cursor.close() + except (MySQLdb.OperationalError, MySQLdb.InternalError) as e: + if not is_deadlock_error(e): + raise e + + con.close() + +def run_reads(args): + con = MySQLdb.connect(user=args.user, + passwd=args.password, + host=args.host, + port=args.port, + db=args.database) + for i in range(int(NUM_TRANSACTIONS / 10)): + cursor = con.cursor() + cursor.execute("select * from t1") + cursor.execute("select repeat('X', @@max_allowed_packet)") + cursor.execute("commit") + cursor.close() + +def run_admin_checks(args): + con = MySQLdb.connect(user=args.user, + passwd=args.password, + host=args.host, + port=args.port, + db=args.database) + cursor=con.cursor() + cursor.execute("select @@global.thread_pool_max_running_queries") + rows = cursor.fetchone() + max_running_queries = int(rows[0]) + for i in range(NUM_TRANSACTIONS): + cursor=con.cursor() + cursor.execute("show status like " \ + "'Thread_pool_admission_control_running_queries'") + rows = cursor.fetchall() + if int(rows[0][1]) > max_running_queries: + raise Exception('Current running queries %s is more than ' \ + 'max_running_queries %d' % (rows[1][1], + max_running_queries)) + +class worker_thread(threading.Thread): + def __init__(self, args, worker_id, admin, readonly): + threading.Thread.__init__(self) + self.args = args + self.exception = None + self.admin = admin + self.readonly = readonly + self.worker_id = worker_id + self.start() + + def run(self): + try: + if self.admin: + run_admin_checks(self.args) + elif self.readonly: + run_reads(self.args) + else: + generate_load(self.args, self.worker_id) + except Exception as e: + self.exception = traceback.format_exc() + +def main(): + args = parse_args() + workers = [] + for i in range(NUM_WORKERS): + worker = worker_thread(args, i, False, False) + workers.append(worker) + + admin_worker = worker_thread(args, NUM_WORKERS, True, False) + workers.append(admin_worker) + + readonly_worker = worker_thread(args, NUM_WORKERS, False, True) + workers.append(readonly_worker) + + worker_failed = False + for w in workers: + w.join() + if w.exception: + print("worker hit an exception:\n%s\n'" % w.exception) + worker_failed = True + + if worker_failed: + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/mysql-test/suite/thread_pool/t/admission_control_multi_query.test b/mysql-test/suite/thread_pool/t/admission_control_multi_query.test new file mode 100644 index 000000000000..26bff907f8ee --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_multi_query.test @@ -0,0 +1,17 @@ +source include/have_thread_pool_plugin.inc; +source include/big_test.inc; +source include/not_valgrind.inc; +create database test_db; +create user test_user@localhost identified with 'mysql_native_password' BY ''; +grant all on test_db.* to test_user@localhost; +grant all on test.* to test_user@localhost; + +use test_db; +create table t1 (a int primary key, b int) engine=InnoDB; + +let $exec = /usr/bin/python3 $MYSQL_TEST_DIR/suite/thread_pool/t/admission_control_multi_query.py --user='test_user' --host=127.0.0.1 --port=$MASTER_MYPORT --database='test_db'; +exec $exec > $MYSQLTEST_VARDIR/tmp/admission_control_multi_query.output; + +drop database test_db; +drop user test_user@localhost; +--remove_file $MYSQLTEST_VARDIR/tmp/admission_control_multi_query.output diff --git a/mysql-test/suite/thread_pool/t/admission_control_multi_query_wait_events-master.opt b/mysql-test/suite/thread_pool/t/admission_control_multi_query_wait_events-master.opt new file mode 100644 index 000000000000..0ecbe2751f1c --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_multi_query_wait_events-master.opt @@ -0,0 +1 @@ +--innodb_lock_wait_timeout=4 --innodb_deadlock_detect=0 --loose-thread_pool_max_running_queries=5 --loose-thread_pool_max_waiting_queries=2000 --loose-thread_pool_admission_control_wait_events=SLEEP,ROW_LOCK,META_DATA_LOCK,NET_IO,YIELD --loose-thread_pool_admission_control_yield_freq=10 diff --git a/mysql-test/suite/thread_pool/t/admission_control_multi_query_wait_events.test b/mysql-test/suite/thread_pool/t/admission_control_multi_query_wait_events.test new file mode 100644 index 000000000000..961f8b62dfd0 --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_multi_query_wait_events.test @@ -0,0 +1,18 @@ +--source include/have_thread_pool_plugin.inc +--source include/big_test.inc +--source include/not_valgrind.inc + +create database test_db; +create user test_user@localhost identified with 'mysql_native_password' BY ''; +grant all on test_db.* to test_user@localhost; +grant all on test.* to test_user@localhost; + +use test_db; +create table t1 (a int primary key, b int) engine=InnoDB; + +let $exec = /usr/bin/python3 $MYSQL_TEST_DIR/t/admission_control_multi_query.py --user='test_user' --host=127.0.0.1 --port=$MASTER_MYPORT --database='test_db' --wait-events; +exec $exec > $MYSQLTEST_VARDIR/tmp/admission_control_multi_query.output; + +drop database test_db; +drop user test_user@localhost; +--remove_file $MYSQLTEST_VARDIR/tmp/admission_control_multi_query.output diff --git a/mysql-test/suite/thread_pool/t/admission_control_multi_query_weighted-master.opt b/mysql-test/suite/thread_pool/t/admission_control_multi_query_weighted-master.opt new file mode 100644 index 000000000000..d8bab958826f --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_multi_query_weighted-master.opt @@ -0,0 +1 @@ +--innodb_lock_wait_timeout=4 --innodb_deadlock_detect=0 --loose-thread_pool_max_running_queries=5 --loose-thread_pool_max_waiting_queries=2000 --loose-thread_pool_admission_control_weights=3,7 diff --git a/mysql-test/suite/thread_pool/t/admission_control_multi_query_weighted.test b/mysql-test/suite/thread_pool/t/admission_control_multi_query_weighted.test new file mode 100644 index 000000000000..4fcf0b89bbfe --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_multi_query_weighted.test @@ -0,0 +1,18 @@ +--source include/have_thread_pool_plugin.inc +--source include/big_test.inc +--source include/not_valgrind.inc + +create database test_db; +create user test_user@localhost identified with 'mysql_native_password' BY ''; +grant all on test_db.* to test_user@localhost; +grant all on test.* to test_user@localhost; + +use test_db; +create table t1 (a int primary key, b int) engine=InnoDB; + +let $exec = /usr/bin/python3 $MYSQL_TEST_DIR/t/admission_control_multi_query.py --user='test_user' --host=127.0.0.1 --port=$MASTER_MYPORT --database='test_db' --weighted-queues; +exec $exec > $MYSQLTEST_VARDIR/tmp/admission_control_multi_query.output; + +drop database test_db; +drop user test_user@localhost; +--remove_file $MYSQLTEST_VARDIR/tmp/admission_control_multi_query.output diff --git a/mysql-test/suite/thread_pool/t/admission_control_queue.test b/mysql-test/suite/thread_pool/t/admission_control_queue.test new file mode 100644 index 000000000000..ac128e5a5f96 --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_queue.test @@ -0,0 +1,221 @@ +--source include/have_thread_pool_plugin.inc + +create database test_db; +create user test_user@localhost; +grant all on test_db.* to test_user@localhost; +grant all on test.* to test_user@localhost; +use test_db; + +--let $ac_entities_query=select * from information_schema.tp_admission_control_entities where waiting_queries + running_queries + aborted_queries + timeout_queries != 0 and schema_name like 'test%' + +set @save_max_running_queries = @@thread_pool_max_running_queries; +set @save_max_waiting_queries = @@thread_pool_max_waiting_queries; +set @save_admission_control_weights = @@thread_pool_admission_control_weights; + +set global thread_pool_max_running_queries = 5; +set global thread_pool_max_waiting_queries = 2000; + +--source include/count_sessions.inc + +--echo # +--echo # Test admission_control_queue variable works +--echo # + +select GET_LOCK('lock1', -1); + +--connect (con1,localhost,test_user,,test_db) +--send select GET_LOCK('lock1', -1) + +--connection default +let $wait_condition = + select count(*) = 1 from information_schema.tp_admission_control_queue + where schema_name = "test_db" and queue_id = 0 and waiting_queries = 0 and running_queries = 1; +--source include/wait_condition.inc +--eval $ac_entities_query +select * from information_schema.tp_admission_control_queue; + +select RELEASE_LOCK('lock1'); + +let $wait_condition = + select count(*) = 0 from information_schema.tp_admission_control_queue; +--source include/wait_condition.inc +--eval $ac_entities_query +select * from information_schema.tp_admission_control_queue; + +--connection con1 +--reap + +--connection default +--disconnect con1 + +--echo # +--echo # Test connection attribute overrides +--echo # + +select GET_LOCK('lock1', -1); + +--connect (con1,localhost,test_user,,test_db) +conn_attrs_add @@admission_control_queue 1; +# change user command to re-send connection attributes +change_user test_user; +use test_db; + +--send select GET_LOCK('lock1', -1) + +--connection default +let $wait_condition = + select count(*) = 1 from information_schema.tp_admission_control_queue + where schema_name = "test_db" and queue_id = 1 and waiting_queries = 0 and running_queries = 1; +--source include/wait_condition.inc +--eval $ac_entities_query +select * from information_schema.tp_admission_control_queue; + +select RELEASE_LOCK('lock1'); + +let $wait_condition = + select count(*) = 0 from information_schema.tp_admission_control_queue; +--source include/wait_condition.inc +select * from information_schema.tp_admission_control_queue; + +--connection con1 +--reap + +--connection default +--disconnect con1 + +--echo # +--echo # Test connection and query attribute overrides +--echo # + +select GET_LOCK('lock1', -1); + +--connect (con1,localhost,test_user,,test_db) +conn_attrs_add @@admission_control_queue 1; +# change user command to re-send connection attributes +change_user test_user; +use test_db; + +query_attrs_add @@admission_control_queue 2; +--send select GET_LOCK('lock1', -1) + +--connection default +let $wait_condition = + select count(*) = 1 from information_schema.tp_admission_control_queue + where schema_name = "test_db" and queue_id = 2 and waiting_queries = 0 and running_queries = 1; +--source include/wait_condition.inc +--eval $ac_entities_query +select * from information_schema.tp_admission_control_queue; + +select RELEASE_LOCK('lock1'); + +let $wait_condition = + select count(*) = 0 from information_schema.tp_admission_control_queue; +--source include/wait_condition.inc +--eval $ac_entities_query +select * from information_schema.tp_admission_control_queue; + +--connection con1 +--reap + +--connection default +--disconnect con1 + +--echo # +--echo # Test weights (2,3) on queue 1,2 +--echo # + +--connection default +set global thread_pool_admission_control_weights = "1,2,3"; +select GET_LOCK('lock1', -1); + +# We need a different connnection for the second lock, because GET_LOCK will +# unlock any previously locked locks in 5.6. This has been fixed in 5.7. +connect (con_lock,localhost,root,,test_db); +select GET_LOCK('lock2', -1); + +--echo # Set up 5 running queries +let $i = 5; +while ($i) { + connect (conA$i,localhost,test_user,,test_db); + --send select GET_LOCK('lock1', -1) + dec $i; +} + +--connection default +let $wait_condition = + select count(*) = 1 from information_schema.tp_admission_control_queue + where schema_name = "test_db" and queue_id = 0 and waiting_queries = 0 and running_queries = 5; +--source include/wait_condition.inc +--eval $ac_entities_query +select * from information_schema.tp_admission_control_queue; + +--echo # Set up 5 waiting queries each for queue 1 and 2 +let $i = 5; +while ($i) { + connect (conB$i,localhost,test_user,,test_db); + query_attrs_add @@admission_control_queue 1; + --send select GET_LOCK('lock2', -1) + dec $i; +} + +let $i = 5; +while ($i) { + connect (conC$i,localhost,test_user,,test_db); + query_attrs_add @@admission_control_queue 2; + --send select GET_LOCK('lock2', -1) + dec $i; +} + +--echo # Check that 10 queries are waiting +--connection default +let $wait_condition = + select sum(waiting_queries) = 10 from information_schema.tp_admission_control_queue + where schema_name = "test_db" and queue_id in (1, 2); +--source include/wait_condition.inc +--eval $ac_entities_query +select * from information_schema.tp_admission_control_queue; + +--connection default +select RELEASE_LOCK('lock1'); +let $i = 5; +while ($i) { + disconnect conA$i; + dec $i; +} + +--echo # Check that 5 of the 10 queries are now scheduled with 2-3 ratio. +--connection default +let $wait_condition = + select sum(running_queries) = 5 from information_schema.tp_admission_control_queue + where schema_name = "test_db" and queue_id in (1, 2); +--source include/wait_condition.inc +--eval $ac_entities_query +select * from information_schema.tp_admission_control_queue; + +--echo # Cleanup +--connection con_lock +select RELEASE_LOCK('lock2'); + +let $i = 5; +while ($i) { + disconnect conB$i; + dec $i; +} + +let $i = 5; +while ($i) { + disconnect conC$i; + dec $i; +} + +disconnect con_lock; +--connection default + +--source include/wait_until_count_sessions.inc + +set global thread_pool_max_running_queries = @save_max_running_queries; +set global thread_pool_max_waiting_queries = @save_max_waiting_queries; +set global thread_pool_admission_control_weights = @save_admission_control_weights; + +drop database test_db; +drop user test_user@localhost; diff --git a/mysql-test/suite/thread_pool/t/admission_control_stress.test b/mysql-test/suite/thread_pool/t/admission_control_stress.test new file mode 100644 index 000000000000..631a9dd49b20 --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_stress.test @@ -0,0 +1,96 @@ +source include/have_thread_pool_plugin.inc; +source include/big_test.inc; +# valgrind is pretty slow with lot of threads. +source include/not_valgrind.inc; +source include/count_sessions.inc; + + +--echo Test setup. + +let $num_databases = 2; +let $num_connections = 100; +let $num_connection_per_db = 50; +let $num_iterations = 10000; + +--disable_query_log +--disable_result_log + +set @start_max_connections = @@global.max_connections; +eval set @@global.max_connections = $num_connections + 10; +set @start_max_running_queries = @@global.thread_pool_max_running_queries; +set global thread_pool_max_running_queries = 2; + +# Create databases and tables. +connection default; +let $i = $num_databases; +while ($i) { + eval create database test_db$i; + eval use test_db$i; + eval create table test_table (a int auto_increment primary key, b text) engine=InnoDB; + dec $i; +} + +# Create test user and connection pool. +create user test_user@localhost; +grant all on *.* to test_user@localhost; +let $i = $num_connections; +while ($i) { + let $db = `select ceiling($i/$num_connection_per_db)`; + connect (con$i, localhost, test_user,,test_db$db); + dec $i; +} + +--echo Generate load. Toggle max_running_queries and randomly kill a query. +let $j = $num_iterations; +while ($j) { + let $i = $num_connections; + while ($i) { + connection con$i; + send insert into test_table values (NULL, uuid()); + dec $i; + } + + connection default; + let $i = 20; + while ($i) { + let $val = `select 5*floor(10*rand())`; + eval set global thread_pool_max_running_queries = $val; + let $id = `select id from information_schema.processlist where user='test_user' order by rand() limit 1;`; + eval kill query $id; + dec $i; + } + + let $i = $num_connections; + while ($i) { + connection con$i; + error 0, ER_QUERY_INTERRUPTED; # Error may be caused by kill query. + reap; + dec $i; + } + + dec $j; +} + +--echo Cleanup +connection default; +let $i = $num_databases; +while ($i) { + eval select count(*) from test_db$i.test_table; + eval drop database if exists test_db$i; + dec $i; +} + +drop user test_user@localhost; + +let $i = $num_connections; +while ($i) { + disconnect con$i; + dec $i; +} + +--enable_result_log +--enable_query_log + +set global max_connections = @start_max_connections; +set global thread_pool_max_running_queries = @start_max_running_queries; +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/thread_pool/t/admission_control_yield.test b/mysql-test/suite/thread_pool/t/admission_control_yield.test new file mode 100644 index 000000000000..03245715da04 --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_yield.test @@ -0,0 +1,265 @@ +--source include/have_thread_pool_plugin.inc + +create database test_db; +create user test_user@localhost; +grant all on test_db.* to test_user@localhost; +use test_db; + +set @save_max_running_queries = @@thread_pool_max_running_queries; +set @save_max_waiting_queries = @@thread_pool_max_waiting_queries; +set @save_admission_control_wait_events = @@thread_pool_admission_control_wait_events; +set @save_admission_control_filter = @@thread_pool_admission_control_filter; +set @save_admission_control_yield_freq = @@thread_pool_admission_control_yield_freq; + +set global thread_pool_max_running_queries = 1; +set global thread_pool_max_waiting_queries = 2000; +set global thread_pool_admission_control_filter = 'SET'; + +--source include/count_sessions.inc +--connect (con1,localhost,test_user,,test_db) +--connect (con2,localhost,test_user,,test_db) +set thread_pool_admission_control_queue_timeout = 5000; + +--echo # +--echo # Test admission_control_wait_events = SLEEP +--echo # + +# Test that query blocks on admission control without variable. +--connection default +set global thread_pool_admission_control_wait_events = ''; +--connection con1 +--let $con1_id = `SELECT CONNECTION_ID()` +--send select SLEEP(1000000) + +--connection default +let $wait_condition = + select count(*) = 1 from information_schema.processlist where state = 'User sleep'; +--source include/wait_condition.inc + +--connection con2 +--error ER_DB_ADMISSION_CONTROL_TIMEOUT +select 1; + +--connection default +--disable_query_log +--eval KILL QUERY $con1_id +--enable_query_log +--connection con1 +--reap + +# Test that query passes admission control with variable. +--connection default +set global thread_pool_admission_control_wait_events = 'SLEEP'; +--connection con1 +--let $con1_id = `SELECT CONNECTION_ID()` +--send select SLEEP(1000000) + +--connection default +let $wait_condition = + select count(*) = 1 from information_schema.processlist where state = 'User sleep'; +--source include/wait_condition.inc + +--connection con2 +select 1; + +--connection default +--disable_query_log +--eval KILL QUERY $con1_id +--enable_query_log +--connection con1 +--reap + +--echo # +--echo # Test admission_control_wait_events = ROW_LOCK +--echo # + +# Test that query blocks on admission control without variable. +--connection default +set global thread_pool_admission_control_wait_events = ''; +create table t (i int primary key) engine=innodb; +insert into t values (1); +begin; +select * from t where i = 1 for update; + +--connection con1 +--let $con1_id = `SELECT CONNECTION_ID()` +--send select * from t where i = 1 for update; + +--connection default +let $wait_condition = + select count(*) = 1 from performance_schema.data_lock_waits; +--source include/wait_condition.inc + +--connection con2 +--error ER_DB_ADMISSION_CONTROL_TIMEOUT +select 1; + +# Cleanup +--connection default +--disable_query_log +--eval KILL QUERY $con1_id +--enable_query_log +--connection con1 +--error ER_QUERY_INTERRUPTED +--reap + +# Test that query passes admission control with variable. +--connection default +set global thread_pool_admission_control_wait_events = 'ROW_LOCK'; +--connection con1 +--let $con1_id = `SELECT CONNECTION_ID()` +--send select * from t where i = 1 for update; + +--connection default +let $wait_condition = + select count(*) = 1 from performance_schema.data_lock_waits; +--source include/wait_condition.inc + +--connection con2 +select 1; + +# Cleanup +--connection default +--disable_query_log +--eval KILL QUERY $con1_id +--enable_query_log +rollback; +drop table t; + +--connection con1 +--error ER_QUERY_INTERRUPTED +--reap + +--echo # +--echo # Test admission_control_wait_events = META_DATA_LOCK +--echo # + +# Test that query blocks on admission control without variable. +--connection default +set global thread_pool_admission_control_wait_events = ''; +select GET_LOCK('lock', -1); + +--connection con1 +--let $con1_id = `SELECT CONNECTION_ID()` +--send select GET_LOCK('lock', -1) + +--connection default +let $wait_condition = + select count(*) = 1 from information_schema.processlist where state = 'User lock'; +--source include/wait_condition.inc + +--connection con2 +--error ER_DB_ADMISSION_CONTROL_TIMEOUT +select 1; + +# Cleanup +--connection default +--disable_query_log +--eval KILL QUERY $con1_id +--enable_query_log +--connection con1 +--error ER_QUERY_INTERRUPTED +--reap + +# Test that query passes admission control with variable. +--connection default +set global thread_pool_admission_control_wait_events = 'META_DATA_LOCK'; +--connection con1 +--let $con1_id = `SELECT CONNECTION_ID()` +--send select GET_LOCK('lock', -1) + +--connection default +let $wait_condition = + select count(*) = 1 from information_schema.processlist where state = 'User lock'; +--source include/wait_condition.inc + +--connection con2 +select 1; + +# Cleanup +--connection default +--disable_query_log +--eval KILL QUERY $con1_id +--enable_query_log +select RELEASE_LOCK('lock'); + +--connection con1 +--error ER_QUERY_INTERRUPTED +--reap + +--echo # +--echo # Test admission_control_wait_events = YIELD +--echo # + +# Test that query blocks on admission control without variable. +--connection default +set global thread_pool_admission_control_wait_events = ''; +create table t1 (i int primary key) engine=innodb; +insert into t1 values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10); +create table t2 (i int primary key) engine=innodb; +insert into t2 select a.i + 10 * b.i + 100 * c.i + 1000 * d.i from t1 a, t1 b, t1 c, t1 d; + +--connection con1 +--let $con1_id = `SELECT CONNECTION_ID()` +--send select * from t2 a, t2 b, t2 c, t2 d, t2 e, t2 f, t2 g, t2 h limit 10000000000, 1; + +--connection default +let $wait_condition = + select count(*) = 1 from information_schema.processlist where state = 'Sending data'; +--source include/wait_condition.inc + +--connection con2 +--error ER_DB_ADMISSION_CONTROL_TIMEOUT +select 1; + +# Cleanup +--connection default +--disable_query_log +--eval KILL QUERY $con1_id +--enable_query_log +--connection con1 +--error ER_QUERY_INTERRUPTED +--reap + +# Test that query passes admission control with variable. +--connection default +set global thread_pool_admission_control_wait_events = 'YIELD'; +set global thread_pool_admission_control_yield_freq = 1; +--connection con1 +--let $con1_id = `SELECT CONNECTION_ID()` +--send select * from t2 a, t2 b, t2 c, t2 d, t2 e, t2 f, t2 g, t2 h limit 10000000000, 1; + +--connection default +let $wait_condition = + select count(*) = 1 from information_schema.processlist where state = 'Sending data'; +--source include/wait_condition.inc + +--connection con2 +select 1; + +# Cleanup +--connection default +--disable_query_log +--eval KILL QUERY $con1_id +--enable_query_log +drop table t1; +drop table t2; + +--connection con1 +--error ER_QUERY_INTERRUPTED +--reap + +--connection default +--disconnect con1 +--disconnect con2 + +--source include/wait_until_count_sessions.inc + +set global thread_pool_max_running_queries = @save_max_running_queries; +set global thread_pool_max_waiting_queries = @save_max_waiting_queries; +set global thread_pool_admission_control_wait_events = @save_admission_control_wait_events; +set global thread_pool_admission_control_filter = @save_admission_control_filter; +set global thread_pool_admission_control_yield_freq = @save_admission_control_yield_freq; + +drop database test_db; +drop user test_user@localhost; diff --git a/mysql-test/suite/thread_pool/t/admission_control_yield_debug.test b/mysql-test/suite/thread_pool/t/admission_control_yield_debug.test new file mode 100644 index 000000000000..5a37ce28d5bb --- /dev/null +++ b/mysql-test/suite/thread_pool/t/admission_control_yield_debug.test @@ -0,0 +1,101 @@ +--source include/have_thread_pool_plugin.inc +--source include/have_debug.inc + +create database test_db; +create user test_user@localhost; +grant all on test_db.* to test_user@localhost; +grant SYSTEM_VARIABLES_ADMIN on *.* to test_user@localhost; +use test_db; + +set @save_max_running_queries = @@thread_pool_max_running_queries; +set @save_max_waiting_queries = @@thread_pool_max_waiting_queries; +set @save_admission_control_wait_events = @@thread_pool_admission_control_wait_events; + +set global thread_pool_max_running_queries = 1; +set global thread_pool_max_waiting_queries = 2000; + + +--source include/count_sessions.inc +--connect (con1,localhost,test_user,,test_db) +--connect (con2,localhost,test_user,,test_db) +set thread_pool_admission_control_queue_timeout = 100; + +--echo # +--echo # Test admission_control_wait_events = NET_IO +--echo # We need debug sync to simulate slow clients +--echo # + +# Test that query blocks on admission control without variable. +--connection default +set global thread_pool_admission_control_wait_events = ''; + +--connection con1 +--let $con1_id = `SELECT CONNECTION_ID()` +set session debug = "+d,simulate_net_write_delay"; +--send SELECT REPEAT('X', @@max_allowed_packet); + +--connection default +let $wait_condition = + select count(*) > 0 from information_schema.processlist where state = 'Sending to client' and id = $con1_id; +--source include/wait_condition.inc + +--connection con2 +--error ER_DB_ADMISSION_CONTROL_TIMEOUT +select 1; + +# Cleanup +--connection default +--disable_query_log +--eval KILL $con1_id +--enable_query_log +--connection con1 +--disable_result_log +--error 1317,2013 +--reap +--enable_result_log + +--enable_reconnect +--source include/wait_until_connected_again.inc +--disable_reconnect + +# Test that query passes admission control with variable. +--connection default +set global thread_pool_admission_control_wait_events = 'NET_IO'; +--connection con1 +--let $con1_id = `SELECT CONNECTION_ID()` +--send SELECT REPEAT('X', @@max_allowed_packet); + +--connection default +let $wait_condition = + select count(*) > 0 from information_schema.processlist where state = 'Sending to client'; +--source include/wait_condition.inc + +--connection con2 +select 1; + +# Cleanup +--connection default +--disable_query_log +--eval KILL $con1_id +--enable_query_log +--connection con1 +--disable_result_log +--error 1317,2013 +--reap +--enable_result_log +--enable_reconnect +--source include/wait_until_connected_again.inc +--disable_reconnect + +--connection default +--disconnect con1 +--disconnect con2 + +--source include/wait_until_count_sessions.inc + +set global thread_pool_max_running_queries = @save_max_running_queries; +set global thread_pool_max_waiting_queries = @save_max_waiting_queries; +set global thread_pool_admission_control_wait_events = @save_admission_control_wait_events; + +drop database test_db; +drop user test_user@localhost; diff --git a/mysql-test/t/admission_control_multi_query.py b/mysql-test/t/admission_control_multi_query.py index b6955232d69f..4fb687ffb418 100644 --- a/mysql-test/t/admission_control_multi_query.py +++ b/mysql-test/t/admission_control_multi_query.py @@ -129,9 +129,9 @@ def run_admin_checks(args): max_running_queries = int(rows[0]) for i in range(NUM_TRANSACTIONS): cursor=con.cursor() - cursor.execute("show status like '%admission%'") + cursor.execute("show status like 'Database_admission_control_running_queries'") rows = cursor.fetchall() - if int(rows[1][1]) > max_running_queries: + if int(rows[0][1]) > max_running_queries: raise Exception('Current running queries %s is more than ' \ 'max_running_queries %d' % (rows[1][1], max_running_queries)) diff --git a/sql/handler.cc b/sql/handler.cc index 4ef07ae6ba31..73147012b156 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2867,7 +2867,7 @@ void handler::ha_statistic_increment( ulonglong System_status_var::*offset) const { if (table && table->in_use) { (table->in_use->status_var.*offset)++; - table->in_use->check_yield(std::bind(yield_condition, table)); + table->in_use->check_yield([t = table] { return yield_condition(t); }); } } @@ -6505,7 +6505,7 @@ ha_rows handler::multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq, while (!seq->next(seq_it, &range)) { if (unlikely(thd->killed != 0)) return HA_POS_ERROR; - thd->check_yield(std::bind(yield_condition, table)); + thd->check_yield([t = table] { return yield_condition(t); }); n_ranges++; key_range *min_endp, *max_endp; diff --git a/sql/iterators/hash_join_iterator.cc b/sql/iterators/hash_join_iterator.cc index f66ea86e523e..e969cd18588c 100644 --- a/sql/iterators/hash_join_iterator.cc +++ b/sql/iterators/hash_join_iterator.cc @@ -341,6 +341,8 @@ static bool WriteRowsToChunks( return true; } + thd->check_yield(); + if (res == -1) { return false; // EOF; success. } @@ -459,6 +461,8 @@ bool HashJoinIterator::BuildHashTable() { return true; } + thd()->check_yield(); + if (res == -1) { m_build_iterator_has_more_rows = false; // If the build input was empty, the result of inner joins and semijoins diff --git a/sql/sql_admission_control.h b/sql/sql_admission_control.h index 866e35f7ad7e..e1df716c90c6 100644 --- a/sql/sql_admission_control.h +++ b/sql/sql_admission_control.h @@ -99,8 +99,9 @@ enum enum_admission_control_wait_events { ADMISSION_CONTROL_THD_WAIT_SLEEP = (1U << 0), ADMISSION_CONTROL_THD_WAIT_ROW_LOCK = (1U << 1), ADMISSION_CONTROL_THD_WAIT_META_DATA_LOCK = (1U << 2), - ADMISSION_CONTROL_THD_WAIT_NET_IO = (1U << 3), - ADMISSION_CONTROL_THD_WAIT_YIELD = (1U << 4), + ADMISSION_CONTROL_THD_WAIT_INNODB_CONC = (1U << 3), + ADMISSION_CONTROL_THD_WAIT_NET_IO = (1U << 4), + ADMISSION_CONTROL_THD_WAIT_YIELD = (1U << 5), }; enum enum_admission_control_request_mode { diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 7110bc7f9772..df354f0c1bf5 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -108,6 +108,7 @@ #include "sql/sql_plugin.h" // plugin_thdvar_init #include "sql/sql_prepare.h" // Prepared_statement #include "sql/sql_profile.h" +#include "sql/sql_thd_internal_api.h" // thd_yield_cond #include "sql/sql_timer.h" // thd_timer_destroy #include "sql/table.h" #include "sql/table_cache.h" // table_cache_manager @@ -2660,17 +2661,117 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup) { } } +/** + Default yield condition. +*/ +bool THD::always_yield() { + return true; +} + void THD::check_yield(std::function cond) { - assert(last_yield_counter <= yield_counter); - yield_counter++; - // We pass cond as a callback because cond() could be expensive, so it should - // only be called after we've determined that we are eligible for yielding. - // Hence, we call cond() after checking yield counters here. - if (last_yield_counter + admission_control_yield_freq < yield_counter && - cond()) { - thd_wait_begin(this, THD_WAIT_YIELD); - thd_wait_end(this); - last_yield_counter = yield_counter; + yield_cond = std::move(cond); + thd_wait_begin(this, THD_WAIT_YIELD); + thd_wait_end(this); + yield_cond = nullptr; +} + +/** + Concurrency control for query. + + @return 0 if the query is admitted, 1 otherwise +*/ +int THD::admit_query() { + // Begin records that admission is required, end performs admission. + thd_wait_begin(this, THD_WAIT_ADMIT); + thd_wait_end(this); + + return is_error(); +} + +/** + Check if wait type should release AC slot. + + @return true if should release, false otherwise. +*/ +static bool filter_wait_type(int wait_type) { + switch (wait_type) { + case THD_WAIT_SLEEP: + return admission_control_wait_events & ADMISSION_CONTROL_THD_WAIT_SLEEP; + case THD_WAIT_ROW_LOCK: + return admission_control_wait_events & + ADMISSION_CONTROL_THD_WAIT_ROW_LOCK; + case THD_WAIT_META_DATA_LOCK: + return admission_control_wait_events & + ADMISSION_CONTROL_THD_WAIT_META_DATA_LOCK; + case THD_WAIT_INNODB_CONC: + return admission_control_wait_events & + ADMISSION_CONTROL_THD_WAIT_INNODB_CONC; + case THD_WAIT_NET_IO: + return admission_control_wait_events & ADMISSION_CONTROL_THD_WAIT_NET_IO; + case THD_WAIT_YIELD: + return admission_control_wait_events & ADMISSION_CONTROL_THD_WAIT_YIELD; + // THD_WAIT_ADMIT never releases AC slot so multi-query batch is not + // readmitted between queries. + case THD_WAIT_ADMIT: + default: + return false; + } +} + +/** + Callback for thd_wait_begin. + + @param wait_type Wait type. +*/ +void THD::wait_begin(int wait_type) { + // Confirm that thd_wait_end() has been called. + assert(readmission_mode == AC_REQUEST_NONE); + + if (is_in_ac) { + if (filter_wait_type(wait_type)) { + bool exit = true; + if (wait_type == THD_WAIT_YIELD) { + assert(last_yield_counter <= yield_counter); + yield_counter++; + + // yield_cond() could be expensive, so it should only be called after + // we've determined that we are eligible for yielding. + // Hence, we call yield_cond() after checking yield counters here. + exit = + last_yield_counter + admission_control_yield_freq < yield_counter && + thd_yield_cond(this); + } + + if (exit) { + multi_tenancy_exit_query(this); + + // For explicit yields, we want to send the query to the back of the + // queue to allow for other queries to run. For other yields, it's + // likely we want to finish the query as soon as possible. + readmission_mode = (wait_type == THD_WAIT_YIELD) + ? AC_REQUEST_QUERY_READMIT_LOPRI + : AC_REQUEST_QUERY_READMIT_HIPRI; + } + } + } else if (wait_type == THD_WAIT_ADMIT) { + // This is a signal to admit query. Multi-query batch only gets here for + // the first query, subsequently THD_WAIT_ADMIT is filtered out. + readmission_mode = AC_REQUEST_QUERY; + } +} + +/** + Callback for thd_wait_end. +*/ +void THD::wait_end() { + if (readmission_mode > AC_REQUEST_NONE) { + if (readmission_mode == AC_REQUEST_QUERY_READMIT_HIPRI && + ++readmission_count % 1000 == 0) { + readmission_mode = AC_REQUEST_QUERY_READMIT_LOPRI; + } + + multi_tenancy_admit_query(this, readmission_mode); + readmission_mode = AC_REQUEST_NONE; } } diff --git a/sql/sql_class.h b/sql/sql_class.h index 25110aa0ab2a..91fb7c622709 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -5218,11 +5218,36 @@ class THD : public MDL_context_owner, ulonglong last_yield_counter = 0; ulonglong yield_counter = 0; ulonglong readmission_count = 0; + std::function yield_cond; + + /** + Default yield condition. + */ + static bool always_yield(); /** Check if we should exit and reenter admission control. */ - void check_yield(std::function cond); + void check_yield(std::function cond = always_yield); + + /** + Callback for thd_wait_begin. + + @param wait_type Wait type. + */ + void wait_begin(int wait_type); + + /** + Callback for thd_wait_end. + */ + void wait_end(); + + /** + Concurrency control for query. + + @return 0 if the query is admitted, 1 otherwise + */ + int admit_query(); private: /** diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 5cbeccdb20c0..1f55312b2e6c 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -6148,7 +6148,7 @@ void dispatch_sql_command(THD *thd, Parser_state *parser_state, bool switched = mgr_ptr->switch_resource_group_if_needed( thd, &src_res_grp, &dest_res_grp, &ticket, &cur_ticket); - if (multi_tenancy_admit_query(thd)) { + if (thd->admit_query()) { error = 1; } else { error = mysql_execute_command(thd, true, last_timer); diff --git a/sql/sql_thd_api.cc b/sql/sql_thd_api.cc index 23c64e458175..d0e906f87260 100644 --- a/sql/sql_thd_api.cc +++ b/sql/sql_thd_api.cc @@ -629,25 +629,6 @@ void *thd_memdup(MYSQL_THD thd, const void *str, size_t size) { // ////////////////////////////////////////////////////////// -static bool filter_wait_type(int wait_type) { - switch (wait_type) { - case THD_WAIT_SLEEP: - return admission_control_wait_events & ADMISSION_CONTROL_THD_WAIT_SLEEP; - case THD_WAIT_ROW_LOCK: - return admission_control_wait_events & - ADMISSION_CONTROL_THD_WAIT_ROW_LOCK; - case THD_WAIT_META_DATA_LOCK: - return admission_control_wait_events & - ADMISSION_CONTROL_THD_WAIT_META_DATA_LOCK; - case THD_WAIT_NET_IO: - return admission_control_wait_events & ADMISSION_CONTROL_THD_WAIT_NET_IO; - case THD_WAIT_YIELD: - return admission_control_wait_events & ADMISSION_CONTROL_THD_WAIT_YIELD; - default: - return false; - } -} - /** Interface for MySQL Server, plugins and storage engines to report when they are going to sleep/stall. @@ -667,14 +648,8 @@ static bool filter_wait_type(int wait_type) { to preserve compatibility with exported service api. */ void thd_wait_begin(MYSQL_THD thd, int wait_type) { - if (thd && thd->is_in_ac && filter_wait_type(wait_type)) { - multi_tenancy_exit_query(thd); - // For explicit yields, we want to send the query to the back of the queue - // to allow for other queries to run. For other yields, it's likely we - // want to finish the query as soon as possible. - thd->readmission_mode = (wait_type == THD_WAIT_YIELD) - ? AC_REQUEST_QUERY_READMIT_LOPRI - : AC_REQUEST_QUERY_READMIT_HIPRI; + if (thd) { + thd->wait_begin(wait_type); } MYSQL_CALLBACK(Connection_handler_manager::event_functions, thd_wait_begin, @@ -697,13 +672,9 @@ void thd_wait_begin(MYSQL_THD thd, int wait_type) { void thd_wait_end(MYSQL_THD thd) { MYSQL_CALLBACK(Connection_handler_manager::event_functions, thd_wait_end, (thd)); - if (thd && thd->readmission_mode > AC_REQUEST_NONE) { - if (++thd->readmission_count % 1000 == 0) { - thd->readmission_mode = AC_REQUEST_QUERY_READMIT_LOPRI; - } - multi_tenancy_admit_query(thd, thd->readmission_mode); - thd->readmission_mode = AC_REQUEST_NONE; + if (thd) { + thd->wait_end(); } } diff --git a/sql/sql_thd_internal_api.cc b/sql/sql_thd_internal_api.cc index 52d9f31ce627..43ab47b43acf 100644 --- a/sql/sql_thd_internal_api.cc +++ b/sql/sql_thd_internal_api.cc @@ -414,3 +414,10 @@ bool thd_is_dd_update_stmt(const THD *thd) { } my_thread_id thd_thread_id(const THD *thd) { return (thd->thread_id()); } + +/** + Invoke yield_cond. + + @return true if should yield, false otherwise. +*/ +bool thd_yield_cond(THD *thd) { return !thd->yield_cond || thd->yield_cond(); } diff --git a/sql/sql_thd_internal_api.h b/sql/sql_thd_internal_api.h index 6613c583277a..93773b1f9c10 100644 --- a/sql/sql_thd_internal_api.h +++ b/sql/sql_thd_internal_api.h @@ -310,4 +310,12 @@ bool thd_is_bootstrap_thread(THD *thd); bool thd_is_dd_update_stmt(const THD *thd); my_thread_id thd_thread_id(const THD *thd); + +/** + Invoke yield_cond. + + @return true if should yield, false otherwise. +*/ +bool thd_yield_cond(THD *thd); + #endif // SQL_THD_INTERNAL_API_INCLUDED diff --git a/sql/sql_union.cc b/sql/sql_union.cc index f4c3b07cc175..7b85450e6d75 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -1780,6 +1780,8 @@ bool Query_expression::ExecuteIteratorQuery(THD *thd) { return true; } + thd->check_yield(); + ++*send_records_ptr; if (query_result->send_data(thd, *fields)) { diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index a6e816f39dd6..9a0b6d9ea37c 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -3277,7 +3277,7 @@ static Sys_var_charptr Sys_admission_control_weights( ON_CHECK(check_admission_control_weights)); const char *admission_control_wait_events_names[] = { - "SLEEP", "ROW_LOCK", "META_DATA_LOCK", "NET_IO", "YIELD", 0}; + "SLEEP", "ROW_LOCK", "META_DATA_LOCK", "INNODB_CONC", "NET_IO", "YIELD", 0}; static Sys_var_set Sys_admission_control_wait_events( "admission_control_wait_events", "Determines events for which queries will exit admission control. After " diff --git a/storage/innobase/srv/srv0conc.cc b/storage/innobase/srv/srv0conc.cc index a12acabb90fb..d1d458cb73aa 100644 --- a/storage/innobase/srv/srv0conc.cc +++ b/storage/innobase/srv/srv0conc.cc @@ -166,7 +166,7 @@ static dberr_t srv_conc_enter_innodb_with_atomics( if (!notified_mysql) { srv_conc.n_waiting.fetch_add(1, std::memory_order_relaxed); - thd_wait_begin(trx->mysql_thd, THD_WAIT_USER_LOCK); + thd_wait_begin(trx->mysql_thd, THD_WAIT_INNODB_CONC); notified_mysql = true; }