From 429b0e6c0ff6a66dfae341d4ec9fafc014397b57 Mon Sep 17 00:00:00 2001 From: "malff/marcsql@weblab.(none)" <> Date: Mon, 13 Nov 2006 15:40:22 -0700 Subject: [PATCH 1/6] Bug#23703 (DROP TRIGGER needs an IF EXISTS) This change set implements the DROP TRIGGER IF EXISTS functionality. This fix is considered a bug and not a feature, because without it, there is no known method to write a database creation script that can create a trigger without failing, when executed on a database that may or may not contain already a trigger of the same name. Implementing this functionality closes an orthogonality gap between triggers and stored procedures / stored functions (which do support the DROP IF EXISTS syntax). In sql_trigger.cc, in mysql_create_or_drop_trigger, the code has been reordered to: - perform the tests that do not depend on the file system (access()), - get the locks (wait_if_global_read_lock, LOCK_open) - call access() - perform the operation - write to the binlog - unlock (LOCK_open, start_waiting_global_read_lock) This is to ensure that all the code that depends on the presence of the trigger file is executed in the same critical section, and prevents race conditions similar to the case fixed by Bug 14262 : - thread 1 executes DROP TRIGGER IF EXISTS, access() returns a failure - thread 2 executes CREATE TRIGGER - thread 2 logs CREATE TRIGGER - thread 1 logs DROP TRIGGER IF EXISTS The patch itself is based on code contributed by the MySQL community, under the terms of the Contributor License Agreement (See Bug 18161). --- mysql-test/r/rpl_trigger.result | 27 +++++++++ mysql-test/r/trigger.result | 22 +++++++ mysql-test/t/rpl_trigger.test | 37 ++++++++++++ mysql-test/t/trigger.test | 28 +++++++++ sql/sql_trigger.cc | 104 +++++++++++++++++++++++--------- sql/sql_yacc.yy | 5 +- 6 files changed, 194 insertions(+), 29 deletions(-) diff --git a/mysql-test/r/rpl_trigger.result b/mysql-test/r/rpl_trigger.result index 3c740bf8e64..f8573eec75f 100644 --- a/mysql-test/r/rpl_trigger.result +++ b/mysql-test/r/rpl_trigger.result @@ -941,3 +941,30 @@ c ---> Cleaning up... DROP TABLE t1; DROP TABLE t2; +drop table if exists t1; +create table t1(a int, b varchar(50)); +drop trigger not_a_trigger; +ERROR HY000: Trigger does not exist +drop trigger if exists not_a_trigger; +Warnings: +Note 1360 Trigger does not exist +create trigger t1_bi before insert on t1 +for each row set NEW.b := "In trigger t1_bi"; +insert into t1 values (1, "a"); +drop trigger if exists t1_bi; +insert into t1 values (2, "b"); +drop trigger if exists t1_bi; +Warnings: +Note 1360 Trigger does not exist +insert into t1 values (3, "c"); +select * from t1; +a b +1 In trigger t1_bi +2 b +3 c +select * from t1; +a b +1 In trigger t1_bi +2 b +3 c +drop table t1; diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result index 9d3ab9b1d7d..9f34f60eb1a 100644 --- a/mysql-test/r/trigger.result +++ b/mysql-test/r/trigger.result @@ -1256,4 +1256,26 @@ select @a; 20 drop table t1; drop function f1; +drop table if exists t1; +create table t1(a int, b varchar(50)); +drop trigger not_a_trigger; +ERROR HY000: Trigger does not exist +drop trigger if exists not_a_trigger; +Warnings: +Note 1360 Trigger does not exist +create trigger t1_bi before insert on t1 +for each row set NEW.b := "In trigger t1_bi"; +insert into t1 values (1, "a"); +drop trigger if exists t1_bi; +insert into t1 values (2, "b"); +drop trigger if exists t1_bi; +Warnings: +Note 1360 Trigger does not exist +insert into t1 values (3, "c"); +select * from t1; +a b +1 In trigger t1_bi +2 b +3 c +drop table t1; End of 5.0 tests diff --git a/mysql-test/t/rpl_trigger.test b/mysql-test/t/rpl_trigger.test index d6e9410b1d3..faba89e7a73 100644 --- a/mysql-test/t/rpl_trigger.test +++ b/mysql-test/t/rpl_trigger.test @@ -423,6 +423,43 @@ DROP TABLE t2; --sync_with_master --connection master +# +# BUG#23703: DROP TRIGGER needs an IF EXISTS +# + +connection master; + +--disable_warnings +drop table if exists t1; +--enable_warnings + +create table t1(a int, b varchar(50)); + +-- error ER_TRG_DOES_NOT_EXIST +drop trigger not_a_trigger; + +drop trigger if exists not_a_trigger; + +create trigger t1_bi before insert on t1 +for each row set NEW.b := "In trigger t1_bi"; + +insert into t1 values (1, "a"); +drop trigger if exists t1_bi; +insert into t1 values (2, "b"); +drop trigger if exists t1_bi; +insert into t1 values (3, "c"); + +select * from t1; + +save_master_pos; +connection slave; +sync_with_master; + +select * from t1; + +connection master; + +drop table t1; # # End of tests diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test index 81334f9b8ef..a9395c12a63 100644 --- a/mysql-test/t/trigger.test +++ b/mysql-test/t/trigger.test @@ -1519,4 +1519,32 @@ connection default; drop table t1; drop function f1; +# +# Bug#23703: DROP TRIGGER needs an IF EXISTS +# + +--disable_warnings +drop table if exists t1; +--enable_warnings + +create table t1(a int, b varchar(50)); + +-- error ER_TRG_DOES_NOT_EXIST +drop trigger not_a_trigger; + +drop trigger if exists not_a_trigger; + +create trigger t1_bi before insert on t1 +for each row set NEW.b := "In trigger t1_bi"; + +insert into t1 values (1, "a"); +drop trigger if exists t1_bi; +insert into t1 values (2, "b"); +drop trigger if exists t1_bi; +insert into t1 values (3, "c"); + +select * from t1; + +drop table t1; + --echo End of 5.0 tests diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 95734d31411..3552fc596f3 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -107,7 +107,9 @@ const LEX_STRING trg_event_type_names[]= }; -static TABLE_LIST *add_table_for_trigger(THD *thd, sp_name *trig); +static int +add_table_for_trigger(THD *thd, sp_name *trig, bool if_exists, + TABLE_LIST ** table); class Handle_old_incorrect_sql_modes_hook: public Unknown_key_hook { @@ -156,6 +158,13 @@ private: */ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) { + /* + FIXME: The code below takes too many different paths depending on the + 'create' flag, so that the justification for a single function + 'mysql_create_or_drop_trigger', compared to two separate functions + 'mysql_create_trigger' and 'mysql_drop_trigger' is not apparent. + This is a good candidate for a minor refactoring. + */ TABLE *table; bool result= TRUE; String stmt_query; @@ -181,10 +190,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) DBUG_RETURN(TRUE); } - if (!create && - !(tables= add_table_for_trigger(thd, thd->lex->spname))) - DBUG_RETURN(TRUE); - /* We don't allow creating triggers on tables in the 'mysql' schema */ @@ -194,9 +199,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) DBUG_RETURN(TRUE); } - /* We should have only one table in table list. */ - DBUG_ASSERT(tables->next_global == 0); - /* TODO: We should check if user has TRIGGER privilege for table here. Now we just require SUPER privilege for creating/dropping because @@ -211,7 +213,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) DROP for example) so we do the check for privileges. For now there is already a stronger test right above; but when this stronger test will be removed, the test below will hold. Because triggers have the same - nature as functions regarding binlogging: their body is implicitely + nature as functions regarding binlogging: their body is implicitly binlogged, so they share the same danger, so trust_function_creators applies to them too. */ @@ -222,24 +224,52 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) DBUG_RETURN(TRUE); } - /* We do not allow creation of triggers on temporary tables. */ - if (create && find_temporary_table(thd, tables->db, tables->table_name)) - { - my_error(ER_TRG_ON_VIEW_OR_TEMP_TABLE, MYF(0), tables->alias); - DBUG_RETURN(TRUE); - } - /* We don't want perform our operations while global read lock is held - so we have to wait until its end and then prevent it from occuring + so we have to wait until its end and then prevent it from occurring again until we are done. (Acquiring LOCK_open is not enough because - global read lock is held without helding LOCK_open). + global read lock is held without holding LOCK_open). */ if (wait_if_global_read_lock(thd, 0, 1)) DBUG_RETURN(TRUE); VOID(pthread_mutex_lock(&LOCK_open)); + if (!create) + { + bool if_exists= thd->lex->drop_if_exists; + + if (add_table_for_trigger(thd, thd->lex->spname, if_exists, & tables)) + goto end; + + if (!tables) + { + DBUG_ASSERT(if_exists); + /* + Since the trigger does not exist, there is no associated table, + and therefore : + - no TRIGGER privileges to check, + - no trigger to drop, + - no table to lock/modify, + so the drop statement is successful. + */ + result= FALSE; + /* Still, we need to log the query ... */ + stmt_query.append(thd->query, thd->query_length); + goto end; + } + } + + /* We should have only one table in table list. */ + DBUG_ASSERT(tables->next_global == 0); + + /* We do not allow creation of triggers on temporary tables. */ + if (create && find_temporary_table(thd, tables->db, tables->table_name)) + { + my_error(ER_TRG_ON_VIEW_OR_TEMP_TABLE, MYF(0), tables->alias); + goto end; + } + if (lock_table_names(thd, tables)) goto end; @@ -1145,13 +1175,17 @@ bool Table_triggers_list::get_trigger_info(THD *thd, trg_event_type event, mysql_table_for_trigger() thd - current thread context trig - identifier for trigger + if_exists - treat a not existing trigger as a warning if TRUE + table - pointer to TABLE_LIST object for the table trigger (output) RETURN VALUE - 0 - error - # - pointer to TABLE_LIST object for the table + 0 Success + 1 Error */ -static TABLE_LIST *add_table_for_trigger(THD *thd, sp_name *trig) +static int +add_table_for_trigger(THD *thd, sp_name *trig, bool if_exists, + TABLE_LIST **table) { LEX *lex= thd->lex; char path_buff[FN_REFLEN]; @@ -1162,6 +1196,7 @@ static TABLE_LIST *add_table_for_trigger(THD *thd, sp_name *trig) path_buff, &trigname.trigger_table); DBUG_ENTER("add_table_for_trigger"); + DBUG_ASSERT(table != NULL); strxnmov(path_buff, FN_REFLEN, mysql_data_home, "/", trig->m_db.str, "/", trig->m_name.str, trigname_file_ext, NullS); @@ -1170,30 +1205,45 @@ static TABLE_LIST *add_table_for_trigger(THD *thd, sp_name *trig) if (access(path_buff, F_OK)) { + if (if_exists) + { + push_warning_printf(thd, + MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_TRG_DOES_NOT_EXIST, + ER(ER_TRG_DOES_NOT_EXIST)); + *table= NULL; + DBUG_RETURN(0); + } + my_error(ER_TRG_DOES_NOT_EXIST, MYF(0)); - DBUG_RETURN(0); + DBUG_RETURN(1); } if (!(parser= sql_parse_prepare(&path, thd->mem_root, 1))) - DBUG_RETURN(0); + DBUG_RETURN(1); if (!is_equal(&trigname_file_type, parser->type())) { my_error(ER_WRONG_OBJECT, MYF(0), trig->m_name.str, trigname_file_ext+1, "TRIGGERNAME"); - DBUG_RETURN(0); + DBUG_RETURN(1); } if (parser->parse((gptr)&trigname, thd->mem_root, trigname_file_parameters, 1, &trigger_table_hook)) - DBUG_RETURN(0); + DBUG_RETURN(1); /* We need to reset statement table list to be PS/SP friendly. */ lex->query_tables= 0; lex->query_tables_last= &lex->query_tables; - DBUG_RETURN(sp_add_to_query_tables(thd, lex, trig->m_db.str, - trigname.trigger_table.str, TL_IGNORE)); + *table= sp_add_to_query_tables(thd, lex, trig->m_db.str, + trigname.trigger_table.str, TL_IGNORE); + + if (! *table) + DBUG_RETURN(1); + + DBUG_RETURN(0); } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 6f24a42c07c..21325123baa 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -6098,11 +6098,12 @@ drop: lex->sql_command= SQLCOM_DROP_VIEW; lex->drop_if_exists= $3; } - | DROP TRIGGER_SYM sp_name + | DROP TRIGGER_SYM if_exists sp_name { LEX *lex= Lex; lex->sql_command= SQLCOM_DROP_TRIGGER; - lex->spname= $3; + lex->drop_if_exists= $3; + lex->spname= $4; } ; From d56ab5afb6b82d1aeddca0a575f891dc301643b9 Mon Sep 17 00:00:00 2001 From: "bell@desktop.sanja.is.com.ua" <> Date: Fri, 17 Nov 2006 08:18:10 +0200 Subject: [PATCH 2/6] Fix of debbuging mode of query cache (proposed by Monty reviewed by me). --- sql/sql_cache.cc | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index ff033b69f98..1d8c79d0a1a 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -702,6 +702,7 @@ void query_cache_abort(NET *net) void query_cache_end_of_result(THD *thd) { + Query_cache_block *query_block; DBUG_ENTER("query_cache_end_of_result"); /* See the comment on double-check locking usage above. */ @@ -717,13 +718,9 @@ void query_cache_end_of_result(THD *thd) if (unlikely(query_cache.query_cache_size == 0 || query_cache.flush_in_progress)) - { - STRUCT_UNLOCK(&query_cache.structure_guard_mutex); - DBUG_VOID_RETURN; - } + goto end; - Query_cache_block *query_block= ((Query_cache_block*) - thd->net.query_cache_query); + query_block= ((Query_cache_block*) thd->net.query_cache_query); if (query_block) { DUMP(&query_cache); @@ -742,27 +739,25 @@ void query_cache_end_of_result(THD *thd) header->query())); query_cache.wreck(__LINE__, ""); - STRUCT_UNLOCK(&query_cache.structure_guard_mutex); - - DBUG_VOID_RETURN; + /* + We do not need call of BLOCK_UNLOCK_WR(query_block); here because + query_cache.wreck() switched query cache off but left content + untouched for investigation (it is debugging method). + */ + goto end; } #endif header->found_rows(current_thd->limit_found_rows); header->result()->type= Query_cache_block::RESULT; header->writer(0); thd->net.query_cache_query= 0; + BLOCK_UNLOCK_WR(query_block); DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1);); - STRUCT_UNLOCK(&query_cache.structure_guard_mutex); - - BLOCK_UNLOCK_WR(query_block); - } - else - { - // Cache was flushed or resized and query was deleted => do nothing - STRUCT_UNLOCK(&query_cache.structure_guard_mutex); } +end: + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); DBUG_VOID_RETURN; } @@ -3529,7 +3524,7 @@ uint Query_cache::filename_2_table_key (char *key, const char *path, #if defined(DBUG_OFF) && !defined(USE_QUERY_CACHE_INTEGRITY_CHECK) -void wreck(uint line, const char *message) {} +void wreck(uint line, const char *message) { query_cache_size = 0; } void bins_dump() {} void cache_dump() {} void queries_dump() {} @@ -3541,6 +3536,17 @@ my_bool in_blocks(Query_cache_block * point) { return 0; } #else + +/* + Debug method which switch query cache off but left content for + investigation. + + SYNOPSIS + Query_cache::wreck() + line line of the wreck() call + message message for logging +*/ + void Query_cache::wreck(uint line, const char *message) { THD *thd=current_thd; From f6e4e2317e5b9030502b200b7ea8bcb41b468669 Mon Sep 17 00:00:00 2001 From: "kroki/tomash@moonlight.intranet" <> Date: Fri, 17 Nov 2006 13:56:11 +0300 Subject: [PATCH 3/6] Fix after manual merge: remove dead declaration. --- libmysqld/lib_sql.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/libmysqld/lib_sql.cc b/libmysqld/lib_sql.cc index a33e7fa6100..93b1ed5c81d 100644 --- a/libmysqld/lib_sql.cc +++ b/libmysqld/lib_sql.cc @@ -277,7 +277,6 @@ static int emb_stmt_execute(MYSQL_STMT *stmt) { DBUG_ENTER("emb_stmt_execute"); char header[5]; - MYSQL_DATA *res; THD *thd; my_bool res; From 21daae583d2a6caa6ac611d78ec7dca9f49d3895 Mon Sep 17 00:00:00 2001 From: "kroki/tomash@moonlight.intranet" <> Date: Wed, 22 Nov 2006 16:47:12 +0300 Subject: [PATCH 4/6] Cleanup: remove const. --- tests/mysql_client_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 4bc1fd5f9d4..98d7182d46f 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -12045,7 +12045,7 @@ static void test_bug21635() "COUNT(i)", "COUNT(i)", "COUNT(i) AS A3", "A3", }; - const char *query_end; + char *query_end; MYSQL_RES *result; MYSQL_FIELD *field; unsigned int field_count, i; From 3b9a8532f5d1b35d3e46d3bd7eb62d1da1681eac Mon Sep 17 00:00:00 2001 From: "kroki/tomash@moonlight.intranet" <> Date: Wed, 22 Nov 2006 16:49:32 +0300 Subject: [PATCH 5/6] After merge fixes: remove const and add query buffer. --- tests/mysql_client_test.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 473a3f1f6c2..c88252e6190 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -15403,7 +15403,8 @@ static void test_bug21635() "COUNT(i)", "COUNT(i)", "COUNT(i) AS A3", "A3", }; - const char *query_end; + char query[MAX_TEST_QUERY_LENGTH]; + char *query_end; MYSQL_RES *result; MYSQL_FIELD *field; unsigned int field_count, i; From ea47e05f3cc5929eb1f56cfdc094cedfce318499 Mon Sep 17 00:00:00 2001 From: "anozdrin/alik@booka." <> Date: Thu, 23 Nov 2006 22:55:36 +0300 Subject: [PATCH 6/6] Fix for BUG##24415: Instance manager test im_daemon_life_cycle fails randomly. The problem was that the test case used command line tool (mysql) without specifying connect_timeout argument. In some cases, this lead to hanging of the test case. The fix is to specify --connect_timeout=1 when starting mysql. Also, the patch contains polishing and various cleanups to simplify analyzing of the problems further. The patch affects only test suite, no server codebase has been touched. --- mysql-test/lib/mtr_im.pl | 4 +- mysql-test/lib/mtr_io.pl | 1 + mysql-test/lib/mtr_process.pl | 6 ++ mysql-test/r/im_daemon_life_cycle.result | 4 +- mysql-test/r/im_life_cycle.result | 4 +- mysql-test/t/im_daemon_life_cycle.imtest | 73 +++++++++------ mysql-test/t/im_life_cycle.imtest | 10 +-- mysql-test/t/im_utils.imtest | 4 +- mysql-test/t/kill_n_check.sh | 82 +++++++++++------ mysql-test/t/log.sh | 24 +++++ mysql-test/t/utils.sh | 55 ++++++++++++ mysql-test/t/wait_for_process.sh | 108 ++++++++++++++++------- mysql-test/t/wait_for_socket.sh | 60 ++++++++++--- 13 files changed, 327 insertions(+), 108 deletions(-) create mode 100755 mysql-test/t/log.sh create mode 100644 mysql-test/t/utils.sh diff --git a/mysql-test/lib/mtr_im.pl b/mysql-test/lib/mtr_im.pl index ca17516278e..967e92dfcdd 100644 --- a/mysql-test/lib/mtr_im.pl +++ b/mysql-test/lib/mtr_im.pl @@ -582,7 +582,7 @@ sub mtr_im_start($$) { mtr_add_arg($args, $opt); } - $im->{'pid'} = + $im->{'spawner_pid'} = mtr_spawn( $::exe_im, # path to the executable $args, # cmd-line args @@ -593,7 +593,7 @@ sub mtr_im_start($$) { { append_log_file => 1 } # append log files ); - unless ( $im->{'pid'} ) + unless ( $im->{'spawner_pid'} ) { mtr_error('Could not start Instance Manager.') } diff --git a/mysql-test/lib/mtr_io.pl b/mysql-test/lib/mtr_io.pl index 984d834486c..5be1d2ffddb 100644 --- a/mysql-test/lib/mtr_io.pl +++ b/mysql-test/lib/mtr_io.pl @@ -39,6 +39,7 @@ sub mtr_get_pid_from_file ($) { # Read pid number from file my $pid= ; + chomp $pid; close FILE; return $pid if $pid=~ /^(\d+)/; diff --git a/mysql-test/lib/mtr_process.pl b/mysql-test/lib/mtr_process.pl index 9d0c1f601ba..79285de88a7 100644 --- a/mysql-test/lib/mtr_process.pl +++ b/mysql-test/lib/mtr_process.pl @@ -937,6 +937,12 @@ sub check_expected_crash_and_restart($) } } } + + if ($::instance_manager->{'spawner_pid'} eq $ret_pid) + { + return; + } + mtr_warning("check_expected_crash_and_restart couldn't find an entry for pid: $ret_pid"); } diff --git a/mysql-test/r/im_daemon_life_cycle.result b/mysql-test/r/im_daemon_life_cycle.result index b842a5f3bf9..d80a34f8427 100644 --- a/mysql-test/r/im_daemon_life_cycle.result +++ b/mysql-test/r/im_daemon_life_cycle.result @@ -6,7 +6,7 @@ instance_name status mysqld1 online mysqld2 offline Killing the process... -Sleeping... +Waiting... Success: the process was restarted. Success: server is ready to accept connection on socket. @@ -16,7 +16,7 @@ Success: server is ready to accept connection on socket. START INSTANCE mysqld2; Success: the process has been started. Killing the process... -Sleeping... +Waiting... Success: the process was restarted. Success: server is ready to accept connection on socket. SHOW INSTANCE STATUS mysqld1; diff --git a/mysql-test/r/im_life_cycle.result b/mysql-test/r/im_life_cycle.result index 69f6bb5a490..a9ad85d5947 100644 --- a/mysql-test/r/im_life_cycle.result +++ b/mysql-test/r/im_life_cycle.result @@ -39,7 +39,7 @@ ERROR HY000: Bad instance name. Check that the instance with such a name exists -- 1.1.6. -------------------------------------------------------------------- Killing the process... -Sleeping... +Waiting... Success: the process was restarted. SHOW INSTANCES; instance_name status @@ -52,7 +52,7 @@ mysqld2 offline START INSTANCE mysqld2; Success: the process has been started. Killing the process... -Sleeping... +Waiting... Success: the process was killed. -------------------------------------------------------------------- diff --git a/mysql-test/t/im_daemon_life_cycle.imtest b/mysql-test/t/im_daemon_life_cycle.imtest index 65db9dee93f..acd615809f3 100644 --- a/mysql-test/t/im_daemon_life_cycle.imtest +++ b/mysql-test/t/im_daemon_life_cycle.imtest @@ -6,59 +6,73 @@ # ########################################################################### +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle im_daemon_life_cycle.imtest started. + +########################################################################### + --source include/im_check_env.inc -# Turn on reconnect, not on by default anymore +# Turn on reconnect, not on by default anymore. --enable_reconnect ########################################################################### - -# Kill the IM main process and check that the IM Angel will restart the main -# process. - ---exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_PATH_PID restarted 30 - +# +# The main daemon-life-cycle test case -- check that IM-angel will restart +# IM-main if it got killed: +# - kill IM-main and check that IM-angel will restart it; +# - wait for IM-main to start accepting connections before continue test +# case; +# ########################################################################### -# Wait for IM to start accepting connections. +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Main-test: starting... ---exec $MYSQL_TEST_DIR/t/wait_for_socket.sh $EXE_MYSQL $IM_PATH_SOCK $IM_USERNAME $IM_PASSWORD '' 30 +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Killing IM-main... +--exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_PATH_PID restarted 30 im_daemon_life_cycle + +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Waiting for IM-main to start accepting connections... +--exec $MYSQL_TEST_DIR/t/wait_for_socket.sh $EXE_MYSQL $IM_PATH_SOCK $IM_USERNAME $IM_PASSWORD '' 30 im_daemon_life_cycle + +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Main-test: done. ########################################################################### - # # BUG#12751: Instance Manager: client hangs +# - start nonguarded instance (mysqld2); +# - kill IM-main and get it restarted by IM-angel; +# - check that guarded instance (mysqld1) is accepting connections. +# - check that non-guarded instance (mysqld2) were not stopped. # +########################################################################### --echo --echo -------------------------------------------------------------------- --echo -- Test for BUG#12751 --echo -------------------------------------------------------------------- -# Give some time to begin accepting connections after restart. -# FIXME: race condition here. - ---sleep 3 +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle BUG12751: starting... # 1. Start mysqld; +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle mysqld2: starting... START INSTANCE mysqld2; -# FIXME: START INSTANCE should be synchronous. ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 started -# 2. Restart IM-main: kill it and IM-angel will restart it; wait for IM to -# start accepting connections again. +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle mysqld2: waiting to start... +--exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 started im_daemon_life_cycle ---exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_PATH_PID restarted 30 +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle mysqld2: started. ---exec $MYSQL_TEST_DIR/t/wait_for_socket.sh $EXE_MYSQL $IM_PATH_SOCK $IM_USERNAME $IM_PASSWORD '' 30 +# 2. Restart IM-main; + +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Killing IM-main... +--exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_PATH_PID restarted 30 im_daemon_life_cycle + +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Waiting for IM-main to start accepting connections... +--exec $MYSQL_TEST_DIR/t/wait_for_socket.sh $EXE_MYSQL $IM_PATH_SOCK $IM_USERNAME $IM_PASSWORD '' 30 im_daemon_life_cycle # 3. Issue some statement -- connection should be re-established. -# Give some time to begin accepting connections after restart. -# FIXME: race condition here. - ---sleep 3 +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle Checking that IM-main processing commands... --replace_column 3 VERSION SHOW INSTANCE STATUS mysqld1; @@ -67,6 +81,13 @@ SHOW INSTANCE STATUS mysqld1; # So, if it we do not stop it, it will be stopped by mysql-test-run.pl with # warning. +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle mysqld2: stopping... STOP INSTANCE mysqld2; -# FIXME: STOP INSTANCE should be synchronous. ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 stopped + +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle mysqld2: waiting to stop... +--exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 stopped im_daemon_life_cycle +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle mysqld2: stopped. + +########################################################################### + +--exec $MYSQL_TEST_DIR/t/log.sh im_daemon_life_cycle BUG12751: done. diff --git a/mysql-test/t/im_life_cycle.imtest b/mysql-test/t/im_life_cycle.imtest index ddfb62d312e..3721b92e2b7 100644 --- a/mysql-test/t/im_life_cycle.imtest +++ b/mysql-test/t/im_life_cycle.imtest @@ -25,7 +25,7 @@ START INSTANCE mysqld2; # FIXME: START INSTANCE should be synchronous. ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 started +--exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 started im_life_cycle # FIXME: Result of SHOW INSTANCES here is not deterministic unless START # INSTANCE is synchronous. Even waiting for mysqld to start by looking at @@ -58,7 +58,7 @@ SHOW VARIABLES LIKE 'port'; STOP INSTANCE mysqld2; # FIXME: STOP INSTANCE should be synchronous. ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 stopped +--exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 stopped im_life_cycle # FIXME: Result of SHOW INSTANCES here is not deterministic unless START # INSTANCE is synchronous. Even waiting for mysqld to start by looking at @@ -121,7 +121,7 @@ STOP INSTANCE mysqld3; --echo -- 1.1.6. --echo -------------------------------------------------------------------- ---exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_MYSQLD1_PATH_PID restarted 30 +--exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_MYSQLD1_PATH_PID restarted 30 im_life_cycle # Give some time to IM to detect that mysqld was restarted. It should be # longer than monitoring interval. @@ -143,7 +143,7 @@ SHOW INSTANCES; START INSTANCE mysqld2; # FIXME: START INSTANCE should be synchronous. ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 started +--exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 started im_life_cycle # FIXME: Result of SHOW INSTANCES here is not deterministic unless START # INSTANCE is synchronous. Even waiting for mysqld to start by looking at @@ -151,7 +151,7 @@ START INSTANCE mysqld2; # mysqld has started. # SHOW INSTANCES; ---exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_MYSQLD2_PATH_PID killed 10 +--exec $MYSQL_TEST_DIR/t/kill_n_check.sh $IM_MYSQLD2_PATH_PID killed 10 im_life_cycle # FIXME: Result of SHOW INSTANCES here is not deterministic unless START # INSTANCE is synchronous. Even waiting for mysqld to start by looking at diff --git a/mysql-test/t/im_utils.imtest b/mysql-test/t/im_utils.imtest index 52878f6c2b5..0866b87204a 100644 --- a/mysql-test/t/im_utils.imtest +++ b/mysql-test/t/im_utils.imtest @@ -31,10 +31,10 @@ SHOW INSTANCE OPTIONS mysqld2; # START INSTANCE mysqld2; ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 started +--exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 started im_utils STOP INSTANCE mysqld2; ---exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 stopped +--exec $MYSQL_TEST_DIR/t/wait_for_process.sh $IM_MYSQLD2_PATH_PID 30 stopped im_utils # # Check 'SHOW LOG FILES' command: diff --git a/mysql-test/t/kill_n_check.sh b/mysql-test/t/kill_n_check.sh index a54fb6ef8bb..96c402a638c 100755 --- a/mysql-test/t/kill_n_check.sh +++ b/mysql-test/t/kill_n_check.sh @@ -2,74 +2,101 @@ ########################################################################### -# NOTE: this script returns 0 (success) even in case of failure. This is -# because this script is executed under mysql-test-run[.pl] and it's better to -# examine particular problem in log file, than just having said that the test -# case has failed. +# NOTE: this script returns 0 (success) even in case of failure (except for +# usage-error). This is because this script is executed under +# mysql-test-run[.pl] and it's better to examine particular problem in log +# file, than just having said that the test case has failed. + +########################################################################### + +basename=`basename "$0"` +dirname=`dirname "$0"` + +########################################################################### + +. "$dirname/utils.sh" ########################################################################### check_restart() { if [ ! -r "$pid_path" ]; then + log_debug "No '$pid_path' found." user_msg='the process was killed' return 1 fi new_pid=`cat "$pid_path" 2>/dev/null` + err_code=$? - if [ $? -eq 0 -a "$original_pid" = "$new_pid" ]; then + log_debug "err_code: $err_code; original_pid: $original_pid; new_pid: $new_pid." + + if [ $err_code -eq 0 -a "$original_pid" = "$new_pid" ]; then + log_debug "The process was not restarted." user_msg='the process was not restarted' return 1 fi + log_debug "The process was restarted." user_msg='the process was restarted' return 0 } ########################################################################### -if [ $# -ne 3 ]; then - echo "Usage: kill_n_check.sh killed|restarted " - exit 0 +if [ $# -ne 4 ]; then + echo "Usage: $basename killed|restarted " + exit 1 fi pid_path="$1" expected_result="$2" total_timeout="$3" +test_id="$4" +log_file="$MYSQLTEST_VARDIR/log/$test_id.log" + +log_debug "-- $basename: starting --" +log_debug "pid_path: '$pid_path'" +log_debug "expected_result: '$expected_result'" +log_debug "total_timeout: '$total_timeout'" +log_debug "test_id: '$test_id'" +log_debug "log_file: '$log_file'" + +########################################################################### if [ "$expected_result" != 'killed' -a \ "$expected_result" != 'restarted' ]; then - echo "Error: invalid second argument ('killed' or 'restarted' expected)." - exit 0 + log_error "Invalid second argument ($expected_result): 'killed' or 'restarted' expected." + quit 0 fi if [ -z "$pid_path" ]; then - echo "Error: invalid PID path ($pid_path)." - exit 0 + log_error "Invalid PID path ($pid_path)." + quit 0 fi if [ ! -r "$pid_path" ]; then - echo "Error: PID file ($pid_path) does not exist." - exit 0 + log_error "PID file ($pid_path) does not exist." + quit 0 fi if [ -z "$total_timeout" ]; then - echo "Error: timeout is not specified." - exit 0 + log_error "Timeout is not specified." + quit 0 fi ########################################################################### original_pid=`cat "$pid_path"` +log_debug "original_pid: $original_pid." -echo "Killing the process..." +log_info "Killing the process..." kill -9 $original_pid ########################################################################### -echo "Sleeping..." +log_info "Waiting..." if [ "$expected_result" = "restarted" ]; then @@ -79,37 +106,42 @@ if [ "$expected_result" = "restarted" ]; then while true; do + log_debug "cur_attempt: $cur_attempt." + if check_restart; then - echo "Success: $user_msg." - exit 0 + log_info "Success: $user_msg." + quit 0 fi [ $cur_attempt -ge $total_timeout ] && break + log_debug "Sleeping for 1 second..." sleep 1 cur_attempt=`expr $cur_attempt + 1` done - echo "Error: $user_msg." - exit 0 + log_error "$user_msg." + quit 0 else # $expected_result == killed # Here we have to sleep for some long time to ensure that the process will # not be restarted. + log_debug "Sleeping for $total_timeout seconds..." sleep $total_timeout new_pid=`cat "$pid_path" 2>/dev/null` + log_debug "new_pid: $new_pid." if [ "$new_pid" -a "$new_pid" -ne "$original_pid" ]; then - echo "Error: the process was restarted." + log_error "The process was restarted." else - echo "Success: the process was killed." + log_info "Success: the process was killed." fi - exit 0 + quit 0 fi diff --git a/mysql-test/t/log.sh b/mysql-test/t/log.sh new file mode 100755 index 00000000000..20b265087cc --- /dev/null +++ b/mysql-test/t/log.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +########################################################################### + +basename=`basename "$0"` +dirname=`dirname "$0"` + +########################################################################### + +. "$dirname/utils.sh" + +########################################################################### + +if [ $# -lt 2 ]; then + echo "Usage: $basename log message ..." + exit 1 +fi + +test_id="$1" +log_file="$MYSQLTEST_VARDIR/log/$test_id.log" + +shift + +log_debug "$*" diff --git a/mysql-test/t/utils.sh b/mysql-test/t/utils.sh new file mode 100644 index 00000000000..b3f4744947d --- /dev/null +++ b/mysql-test/t/utils.sh @@ -0,0 +1,55 @@ +########################################################################### +# +# This file provides utility functions and is included by other scripts. +# +# The following global variables must be set before calling functions from this +# file: +# - basename -- base name of the calling script (main application); +# - log_file -- where to store log records; +# +########################################################################### + +log() +{ + [ -z "$log_file" ] && return; + + log_level="$1" + log_msg="$2" + ts=`date` + + echo "[$ts] [$basename] [$log_level] $log_msg" >> "$log_file"; +} + +########################################################################### + +log_debug() +{ + log 'DEBUG' "$1" +} + +########################################################################### + +log_info() +{ + log 'INFO' "$1" + echo "$1" +} + +########################################################################### + +log_error() +{ + log 'ERROR' "$1" + echo "Error: $1" +} + +########################################################################### + +quit() +{ + exit_status="$1" + + log_debug "-- $basename: finished (exit_status: $exit_status) --" + + exit $exit_status +} diff --git a/mysql-test/t/wait_for_process.sh b/mysql-test/t/wait_for_process.sh index df0f4a17e3a..4c2d89cfea6 100755 --- a/mysql-test/t/wait_for_process.sh +++ b/mysql-test/t/wait_for_process.sh @@ -2,11 +2,79 @@ ########################################################################### +# NOTE: this script returns 0 (success) even in case of failure (except for +# usage-error). This is because this script is executed under +# mysql-test-run[.pl] and it's better to examine particular problem in log +# file, than just having said that the test case has failed. + +########################################################################### + +basename=`basename "$0"` +dirname=`dirname "$0"` + +########################################################################### + +. "$dirname/utils.sh" + +########################################################################### + +check_started() +{ + if [ ! -r "$pid_path" ]; then + log_debug "No PID-file ($pid_path) found -- not started." + return 1 + fi + + new_pid=`cat "$pid_path" 2>/dev/null` + err_code=$? + + log_debug "err_code: $err_code; new_pid: $new_pid." + + if [ $? -ne 0 -o -z "$new_pid" ]; then + log_debug "The process was not started." + return 1 + fi + + log_debug "The process was started." + return 0 +} + +########################################################################### + +check_stopped() +{ + if [ -r "$pid_path" ]; then + log_debug "PID-file '$pid_path' exists -- not stopped." + return 1 + fi + + log_debug "No PID-file ($pid_path) found -- stopped." + return 0 +} + +########################################################################### + +if [ $# -ne 4 ]; then + echo "Usage: $basename started|stopped " + exit 1 +fi + pid_path="$1" total_attempts="$2" event="$3" +test_id="$4" +log_file="$MYSQLTEST_VARDIR/log/$test_id.log" -case "$3" in +log_debug "-- $basename: starting --" +log_debug "pid_path: '$pid_path'" +log_debug "total_attempts: '$total_attempts'" +log_debug "event: '$event'" +log_debug "test_id: '$test_id'" +log_debug "log_file: '$log_file'" + +########################################################################### + +case "$event" in started) check_fn='check_started'; ;; @@ -16,51 +84,31 @@ case "$3" in ;; *) - echo "Error: invalid third argument ('started' or 'stopped' expected)." - exit 0 + log_error "Invalid third argument ('started' or 'stopped' expected)." + quit 0 esac ########################################################################### -check_started() -{ - [ ! -r "$pid_path" ] && return 1 - - new_pid=`cat "$pid_path" 2>/dev/null` - - [ $? -eq 0 -a "$original_pid" = "$new_pid" ] && return 1 - - return 0 -} - -########################################################################### - -check_stopped() -{ - [ -r "$pid_path" ] && return 1 - - return 0 -} - -########################################################################### - cur_attempt=1 while true; do + log_debug "cur_attempt: $cur_attempt." + if ( eval $check_fn ); then - echo "Success: the process has been $event." - exit 0 + log_info "Success: the process has been $event." + quit 0 fi [ $cur_attempt -ge $total_attempts ] && break + log_debug "Sleeping for 1 second..." sleep 1 cur_attempt=`expr $cur_attempt + 1` done -echo "Error: the process has not been $event in $total_attempts secs." -exit 0 - +log_error "The process has not been $event in $total_attempts secs." +quit 0 diff --git a/mysql-test/t/wait_for_socket.sh b/mysql-test/t/wait_for_socket.sh index 3b900fa2208..1a73dff5244 100755 --- a/mysql-test/t/wait_for_socket.sh +++ b/mysql-test/t/wait_for_socket.sh @@ -2,9 +2,25 @@ ########################################################################### -if [ $# -ne 6 ]; then - echo "Usage: wait_for_socket.sh " - exit 0 +# NOTE: this script returns 0 (success) even in case of failure (except for +# usage-error). This is because this script is executed under +# mysql-test-run[.pl] and it's better to examine particular problem in log +# file, than just having said that the test case has failed. + +########################################################################### + +basename=`basename "$0"` +dirname=`dirname "$0"` + +########################################################################### + +. "$dirname/utils.sh" + +########################################################################### + +if [ $# -ne 7 ]; then + echo "Usage: wait_for_socket.sh " + exit 1 fi client_exe="$1" @@ -13,41 +29,57 @@ username="$3" password="$4" db="$5" total_timeout="$6" +test_id="$7" +log_file="$MYSQLTEST_VARDIR/log/$test_id.log" + +log_debug "-- $basename: starting --" +log_debug "client_exe: '$client_exe'" +log_debug "socket_path: '$socket_path'" +log_debug "username: '$username'" +log_debug "password: '$password'" +log_debug "db: '$db'" +log_debug "total_timeout: '$total_timeout'" +log_debug "test_id: '$test_id'" +log_debug "log_file: '$log_file'" ########################################################################### if [ -z "$client_exe" ]; then - echo "Error: invalid path to client executable ($client_exe)." - exit 0; + log_error "Invalid path to client executable ($client_exe)." + quit 0; fi if [ ! -x "$client_exe" ]; then - echo "Error: client by path '$client_exe' is not available." - exit 0; + log_error "Client by path '$client_exe' is not available." + quit 0; fi if [ -z "$socket_path" ]; then - echo "Error: invalid socket patch." - exit 0 + log_error "Invalid socket patch ($socket_path)." + quit 0 fi ########################################################################### -client_args="--silent --socket=$socket_path " +client_args="--silent --connect_timeout=1 --socket=$socket_path " [ -n "$username" ] && client_args="$client_args --user=$username " [ -n "$password" ] && client_args="$client_args --password=$password " [ -n "$db" ] && client_args="$client_args $db" +log_debug "client_args: '$client_args'" + ########################################################################### cur_attempt=1 while true; do + log_debug "cur_attempt: $cur_attempt." + if ( echo 'quit' | "$client_exe" $client_args >/dev/null 2>&1 ); then - echo "Success: server is ready to accept connection on socket." - exit 0 + log_info "Success: server is ready to accept connection on socket." + quit 0 fi [ $cur_attempt -ge $total_timeout ] && break @@ -58,5 +90,5 @@ while true; do done -echo "Error: server does not accept connections after $total_timeout seconds." -exit 0 +log_error "Server does not accept connections after $total_timeout seconds." +quit 0