diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dd6e826faa..f2e4c8cb131 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -205,6 +205,15 @@ ENDIF() OPTION(NOT_FOR_DISTRIBUTION "Allow linking with GPLv2-incompatible system libraries. Only set it you never plan to distribute the resulting binaries" OFF) +# +# Enable protection of statement's memory root after first SP/PS execution. +# Can be switched on only for debug build. +# +OPTION(WITH_PROTECT_STATEMENT_MEMROOT "Enable protection of statement's memory root after first SP/PS execution. Turned into account only for debug build" OFF) +IF (CMAKE_BUILD_TYPE MATCHES "Debug" AND WITH_PROTECT_STATEMENT_MEMROOT) + ADD_DEFINITIONS(-DPROTECT_STATEMENT_MEMROOT) +ENDIF() + INCLUDE(check_compiler_flag) INCLUDE(check_linker_flag) diff --git a/include/my_alloc.h b/include/my_alloc.h index 181f637c093..3da3a56a8fb 100644 --- a/include/my_alloc.h +++ b/include/my_alloc.h @@ -52,6 +52,10 @@ typedef struct st_mem_root */ unsigned int first_block_usage; +#ifdef PROTECT_STATEMENT_MEMROOT + int read_only; +#endif + void (*error_handler)(void); PSI_memory_key m_psi_key; diff --git a/mysys/my_alloc.c b/mysys/my_alloc.c index aa0182c755e..e0efd53040e 100644 --- a/mysys/my_alloc.c +++ b/mysys/my_alloc.c @@ -77,6 +77,9 @@ void init_alloc_root(PSI_memory_key key, MEM_ROOT *mem_root, size_t block_size, mem_root->block_num= 4; /* We shift this with >>2 */ mem_root->first_block_usage= 0; mem_root->m_psi_key= key; +#ifdef PROTECT_STATEMENT_MEMROOT + mem_root->read_only= 0; +#endif #if !(defined(HAVE_valgrind) && defined(EXTRA_DEBUG)) if (pre_alloc_size) @@ -216,6 +219,10 @@ void *alloc_root(MEM_ROOT *mem_root, size_t length) DBUG_PRINT("enter",("root: %p", mem_root)); DBUG_ASSERT(alloc_root_inited(mem_root)); +#ifdef PROTECT_STATEMENT_MEMROOT + DBUG_ASSERT(mem_root->read_only == 0); +#endif + DBUG_EXECUTE_IF("simulate_out_of_memory", { /* Avoid reusing an already allocated block */ diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 182e50fa7c6..ac609295e8a 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -534,6 +534,9 @@ sp_head::sp_head(MEM_ROOT *mem_root_arg, sp_package *parent, :Query_arena(NULL, STMT_INITIALIZED_FOR_SP), Database_qualified_name(&null_clex_str, &null_clex_str), main_mem_root(*mem_root_arg), +#ifdef PROTECT_STATEMENT_MEMROOT + executed_counter(0), +#endif m_parent(parent), m_handler(sph), m_flags(0), @@ -791,6 +794,10 @@ sp_head::init(LEX *lex) */ lex->trg_table_fields.empty(); +#ifdef PROTECT_STATEMENT_MEMROOT + executed_counter= 0; +#endif + DBUG_VOID_RETURN; } @@ -1434,6 +1441,11 @@ sp_head::execute(THD *thd, bool merge_da_on_success) err_status= i->execute(thd, &ip); +#ifdef PROTECT_STATEMENT_MEMROOT + if (!err_status) + i->mark_as_run(); +#endif + #ifdef HAVE_PSI_STATEMENT_INTERFACE MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da()); thd->m_statement_psi= parent_locker; @@ -1535,6 +1547,16 @@ sp_head::execute(THD *thd, bool merge_da_on_success) /* Reset sp_rcontext::end_partial_result_set flag. */ ctx->end_partial_result_set= FALSE; +#ifdef PROTECT_STATEMENT_MEMROOT + if (thd->is_error()) + { + // Don't count a call ended with an error as normal run + executed_counter= 0; + main_mem_root.read_only= 0; + reset_instrs_executed_counter(); + } +#endif + } while (!err_status && likely(!thd->killed) && likely(!thd->is_fatal_error) && !thd->spcont->pause_state); @@ -1647,6 +1669,20 @@ sp_head::execute(THD *thd, bool merge_da_on_success) err_status|= mysql_change_db(thd, (LEX_CSTRING*)&saved_cur_db_name, TRUE) != 0; } + +#ifdef PROTECT_STATEMENT_MEMROOT + if (!err_status) + { + if (!main_mem_root.read_only && + has_all_instrs_executed()) + { + main_mem_root.read_only= 1; + } + ++executed_counter; + DBUG_PRINT("info", ("execute counter: %lu", executed_counter)); + } +#endif + m_flags&= ~IS_INVOKED; if (m_parent) m_parent->m_invoked_subroutine_count--; @@ -3287,6 +3323,37 @@ void sp_head::add_mark_lead(uint ip, List *leads) leads->push_front(i); } +#ifdef PROTECT_STATEMENT_MEMROOT + +int sp_head::has_all_instrs_executed() +{ + sp_instr *ip; + uint count= 0; + + for (uint i= 0; i < m_instr.elements; ++i) + { + get_dynamic(&m_instr, (uchar*)&ip, i); + if (ip->has_been_run()) + ++count; + } + + return count == m_instr.elements; +} + + +void sp_head::reset_instrs_executed_counter() +{ + sp_instr *ip; + + for (uint i= 0; i < m_instr.elements; ++i) + { + get_dynamic(&m_instr, (uchar*)&ip, i); + ip->mark_as_not_run(); + } +} + +#endif + void sp_head::opt_mark() { diff --git a/sql/sp_head.h b/sql/sp_head.h index 98ef8d2be41..a4887e5eff9 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -140,6 +140,16 @@ class sp_head :private Query_arena, protected: MEM_ROOT main_mem_root; +#ifdef PROTECT_STATEMENT_MEMROOT + /* + The following data member is wholly for debugging purpose. + It can be used for possible crash analysis to determine how many times + the stored routine was executed before the mem_root marked read_only + was requested for a memory chunk. Additionally, a value of this data + member is output to the log with DBUG_PRINT. + */ + ulong executed_counter; +#endif public: /** Possible values of m_flags */ enum { @@ -823,6 +833,11 @@ public: return ip; } +#ifdef PROTECT_STATEMENT_MEMROOT + int has_all_instrs_executed(); + void reset_instrs_executed_counter(); +#endif + /* Add tables used by routine to the table list. */ bool add_used_tables_to_table_list(THD *thd, TABLE_LIST ***query_tables_last_ptr, @@ -1109,6 +1124,9 @@ public: /// Should give each a name or type code for debugging purposes? sp_instr(uint ip, sp_pcontext *ctx) :Query_arena(0, STMT_INITIALIZED_FOR_SP), marked(0), m_ip(ip), m_ctx(ctx) +#ifdef PROTECT_STATEMENT_MEMROOT + , m_has_been_run(false) +#endif {} virtual ~sp_instr() @@ -1199,6 +1217,25 @@ public: } virtual PSI_statement_info* get_psi_info() = 0; +#ifdef PROTECT_STATEMENT_MEMROOT + bool has_been_run() const + { + return m_has_been_run; + } + + void mark_as_run() + { + m_has_been_run= true; + } + + void mark_as_not_run() + { + m_has_been_run= false; + } + +private: + bool m_has_been_run; +#endif }; // class sp_instr : public Sql_alloc diff --git a/sql/sql_base.cc b/sql/sql_base.cc index fb3d08d388f..a67b69781e7 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1918,10 +1918,6 @@ retry_share: goto err_lock; } - /* Open view */ - if (mysql_make_view(thd, share, table_list, false)) - goto err_lock; - /* This table is a view. Validate its metadata version: in particular, that it was a view when the statement was prepared. @@ -1929,6 +1925,10 @@ retry_share: if (check_and_update_table_version(thd, table_list, share)) goto err_lock; + /* Open view */ + if (mysql_make_view(thd, share, table_list, false)) + goto err_lock; + /* TODO: Don't free this */ tdc_release_share(share); @@ -3863,6 +3863,12 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags, if (tables->open_strategy && !tables->table) goto end; + /* Check and update metadata version of a base table. */ + error= check_and_update_table_version(thd, tables, tables->table->s); + + if (unlikely(error)) + goto end; + error= extend_table_list(thd, tables, prelocking_strategy, has_prelocking_list); if (unlikely(error)) goto end; @@ -3870,11 +3876,6 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags, /* Copy grant information from TABLE_LIST instance to TABLE one. */ tables->table->grant= tables->grant; - /* Check and update metadata version of a base table. */ - error= check_and_update_table_version(thd, tables, tables->table->s); - - if (unlikely(error)) - goto end; /* After opening a MERGE table add the children to the query list of tables, so that they are opened too. diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index f4712d37065..77d193adbde 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -172,6 +172,16 @@ public: Server_side_cursor *cursor; uchar *packet; uchar *packet_end; +#ifdef PROTECT_STATEMENT_MEMROOT + /* + The following data member is wholly for debugging purpose. + It can be used for possible crash analysis to determine how many times + the stored routine was executed before the mem_root marked read_only + was requested for a memory chunk. Additionally, a value of this data + member is output to the log with DBUG_PRINT. + */ + ulong executed_counter; +#endif uint param_count; uint last_errno; uint flags; @@ -4004,6 +4014,9 @@ Prepared_statement::Prepared_statement(THD *thd_arg) cursor(0), packet(0), packet_end(0), +#ifdef PROTECT_STATEMENT_MEMROOT + executed_counter(0), +#endif param_count(0), last_errno(0), flags((uint) IS_IN_USE), @@ -4078,8 +4091,13 @@ void Prepared_statement::setup_set_params() Prepared_statement::~Prepared_statement() { DBUG_ENTER("Prepared_statement::~Prepared_statement"); +#ifdef PROTECT_STATEMENT_MEMROOT + DBUG_PRINT("enter",("stmt: %p cursor: %p executed_counter: %lu", + this, cursor, executed_counter)); +#else DBUG_PRINT("enter",("stmt: %p cursor: %p", this, cursor)); +#endif MYSQL_DESTROY_PS(m_prepared_stmt); @@ -4261,6 +4279,10 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) } lex->set_trg_event_type_for_tables(); +#ifdef PROTECT_STATEMENT_MEMROOT + executed_counter= 0; +#endif + /* While doing context analysis of the query (in check_prepared_statement) we allocate a lot of additional memory: for open tables, JOINs, derived @@ -4532,9 +4554,31 @@ reexecute: error= reprepare(); if (likely(!error)) /* Success */ + { +#ifdef PROTECT_STATEMENT_MEMROOT + // There was reprepare so the counter of runs should be reset + executed_counter= 0; + mem_root->read_only= 0; +#endif goto reexecute; + } } reset_stmt_params(this); +#ifdef PROTECT_STATEMENT_MEMROOT + if (!error) + { + mem_root->read_only= 1; + ++executed_counter; + + DBUG_PRINT("info", ("execute counter: %lu", executed_counter)); + } + else + { + // Error on call shouldn't be counted as a normal run + executed_counter= 0; + mem_root->read_only= 0; + } +#endif return error; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index d7797fef775..750757fae59 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -735,8 +735,7 @@ setup_without_group(THD *thd, Ref_ptr_array ref_pointer_array, ORDER *group, List &win_specs, List &win_funcs, - bool *hidden_group_fields, - uint *reserved) + bool *hidden_group_fields) { int res; enum_parsing_place save_place; @@ -751,13 +750,6 @@ setup_without_group(THD *thd, Ref_ptr_array ref_pointer_array, thd->lex->allow_sum_func.clear_bit(select->nest_level); res= setup_conds(thd, tables, leaves, conds); - if (thd->lex->current_select->first_cond_optimization) - { - if (!res && *conds && ! thd->lex->current_select->merged_into) - (*reserved)= (*conds)->exists2in_reserved_items(); - else - (*reserved)= 0; - } /* it's not wrong to have non-aggregated columns in a WHERE */ select->set_non_agg_field_used(saved_non_agg_field_used); @@ -1324,6 +1316,15 @@ JOIN::prepare(TABLE_LIST *tables_init, COND *conds_init, uint og_num, DBUG_ASSERT(select_lex->hidden_bit_fields == 0); if (setup_wild(thd, tables_list, fields_list, &all_fields, select_lex, false)) DBUG_RETURN(-1); + + if (thd->lex->current_select->first_cond_optimization) + { + if ( conds && ! thd->lex->current_select->merged_into) + select_lex->select_n_reserved= conds->exists2in_reserved_items(); + else + select_lex->select_n_reserved= 0; + } + if (select_lex->setup_ref_array(thd, real_og_num)) DBUG_RETURN(-1); @@ -1342,8 +1343,7 @@ JOIN::prepare(TABLE_LIST *tables_init, COND *conds_init, uint og_num, all_fields, &conds, order, group_list, select_lex->window_specs, select_lex->window_funcs, - &hidden_group_fields, - &select_lex->select_n_reserved)) + &hidden_group_fields)) DBUG_RETURN(-1); /* diff --git a/sql/sql_show.cc b/sql/sql_show.cc index a194068d2b0..cee35ac23a9 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -4902,7 +4902,8 @@ try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table, open_tables function for this table */ -static int fill_schema_table_from_frm(THD *thd, TABLE *table, +static int fill_schema_table_from_frm(THD *thd, MEM_ROOT *mem_root, + TABLE *table, ST_SCHEMA_TABLE *schema_table, LEX_CSTRING *db_name, LEX_CSTRING *table_name, @@ -4914,6 +4915,9 @@ static int fill_schema_table_from_frm(THD *thd, TABLE *table, TABLE_LIST table_list; uint res= 0; char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; + Query_arena i_s_arena(mem_root, Query_arena::STMT_CONVENTIONAL_EXECUTION); + Query_arena backup_arena, *old_arena; + bool i_s_arena_active= false; bzero((char*) &table_list, sizeof(TABLE_LIST)); bzero((char*) &tbl, sizeof(TABLE)); @@ -4988,6 +4992,11 @@ static int fill_schema_table_from_frm(THD *thd, TABLE *table, goto end; } + old_arena= thd->stmt_arena; + thd->stmt_arena= &i_s_arena; + thd->set_n_backup_active_arena(&i_s_arena, &backup_arena); + i_s_arena_active= true; + share= tdc_acquire_share(thd, &table_list, GTS_TABLE | GTS_VIEW); if (!share) { @@ -5069,7 +5078,16 @@ end: savepoint is safe. */ DBUG_ASSERT(thd->open_tables == NULL); + thd->mdl_context.rollback_to_savepoint(open_tables_state_backup->mdl_system_tables_svp); + + if (i_s_arena_active) + { + thd->stmt_arena= old_arena; + thd->restore_active_arena(&i_s_arena, &backup_arena); + i_s_arena.free_items(); + } + if (!thd->is_fatal_error) thd->clear_error(); return res; @@ -5298,7 +5316,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) if (!(table_open_method & ~OPEN_FRM_ONLY) && db_name != &INFORMATION_SCHEMA_NAME) { - if (!fill_schema_table_from_frm(thd, table, schema_table, + if (!fill_schema_table_from_frm(thd, &tmp_mem_root, + table, schema_table, db_name, table_name, &open_tables_state_backup, can_deadlock))