diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index 67bac28ce3f93..cb0d2b7e05521 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -1534,42 +1534,6 @@ inline void buf_pool_t::page_hash_table::write_unlock_all() noexcept } -namespace -{ - -struct find_interesting_trx -{ - void operator()(const trx_t &trx) - { - if (!trx.is_started()) - return; - if (trx.mysql_thd == nullptr) - return; - if (withdraw_started <= trx.start_time_micro) - return; - - if (!found) - { - sql_print_warning("InnoDB: The following trx might hold " - "the blocks in buffer pool to " - "be withdrawn. Buffer pool " - "resizing can complete only " - "after all the transactions " - "below release the blocks."); - found= true; - } - - lock_trx_print_wait_and_mvcc_state(stderr, &trx, current_time); - } - - bool &found; - /** microsecond_interval_timer() */ - const ulonglong withdraw_started; - const my_hrtime_t current_time; -}; - -} // namespace - /** Resize from srv_buf_pool_old_size to srv_buf_pool_size. */ inline void buf_pool_t::resize() { @@ -1656,15 +1620,37 @@ inline void buf_pool_t::resize() message_interval *= 2; } - bool found= false; - find_interesting_trx f - {found, withdraw_started, my_hrtime_coarse()}; withdraw_started = current_time; + const my_hrtime_t current_hrtime{my_hrtime_coarse()}; + bool found{false}; /* This is going to exceed the maximum size of a memory transaction. */ LockMutexGuard g{SRW_LOCK_CALL}; - trx_sys.trx_list.for_each(f); + for (const trx_t& trx: trx_sys.trx_list) { + if (!trx.is_started() || !trx.mysql_thd + || withdraw_started <= trx.start_time_micro) { + continue; + } + + if (!found) { + sql_print_warning("InnoDB: The following " + "trx might hold " + "the blocks in " + "buffer pool to " + "be withdrawn. " + "Buffer pool " + "resizing can " + "complete only " + "after all " + "the transactions " + "below release the blocks."); + found = true; + } + + lock_trx_print_wait_and_mvcc_state(stderr, &trx, + current_hrtime); + } } if (should_retry_withdraw) { diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index d1bcde83b8077..02649112b8cd2 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -2953,7 +2953,7 @@ static int innobase_close_connection(handlerton *, THD *thd) noexcept case TRX_STATE_PREPARED: if (trx->has_logged_persistent()) { - trx_disconnect_prepared(trx); + trx->disconnect_prepared(); return 0; } /* fall through */ diff --git a/storage/innobase/include/trx0sys.h b/storage/innobase/include/trx0sys.h index c2dbcc604d1dc..9fe353a06ecb3 100644 --- a/storage/innobase/include/trx0sys.h +++ b/storage/innobase/include/trx0sys.h @@ -781,58 +781,6 @@ class rw_trx_hash_t } }; -class thread_safe_trx_ilist_t -{ -public: - void create() { mysql_mutex_init(trx_sys_mutex_key, &mutex, nullptr); } - void close() { mysql_mutex_destroy(&mutex); } - - bool empty() const - { - mysql_mutex_lock(&mutex); - auto result= trx_list.empty(); - mysql_mutex_unlock(&mutex); - return result; - } - - void push_front(trx_t &trx) - { - mysql_mutex_lock(&mutex); - trx_list.push_front(trx); - mysql_mutex_unlock(&mutex); - } - - void remove(trx_t &trx) - { - mysql_mutex_lock(&mutex); - trx_list.remove(trx); - mysql_mutex_unlock(&mutex); - } - - template void for_each(Callable &&callback) const - { - mysql_mutex_lock(&mutex); - for (const auto &trx : trx_list) - callback(trx); - mysql_mutex_unlock(&mutex); - } - - template void for_each(Callable &&callback) - { - mysql_mutex_lock(&mutex); - for (auto &trx : trx_list) - callback(trx); - mysql_mutex_unlock(&mutex); - } - - void freeze() const { mysql_mutex_lock(&mutex); } - void unfreeze() const { mysql_mutex_unlock(&mutex); } - -private: - alignas(CPU_LEVEL1_DCACHE_LINESIZE) mutable mysql_mutex_t mutex; - alignas(CPU_LEVEL1_DCACHE_LINESIZE) ilist trx_list; -}; - /** The transaction system central memory data structure. */ class trx_sys_t { @@ -858,9 +806,10 @@ class trx_sys_t bool m_initialised; public: - /** List of all transactions. */ - thread_safe_trx_ilist_t trx_list; + /** List of all transactions; protected by lock_sys.latch */ + ilist trx_list; + alignas(CPU_LEVEL1_DCACHE_LINESIZE) /** Temporary rollback segments */ trx_rseg_t temp_rsegs[TRX_SYS_N_RSEGS]; @@ -1102,7 +1051,7 @@ class trx_sys_t void close(); /** @return total number of active (non-prepared) transactions */ - size_t any_active_transactions(size_t *prepared= nullptr); + size_t any_active_transactions(size_t *prepared= nullptr) noexcept; /** @@ -1179,10 +1128,7 @@ class trx_sys_t @param trx transaction */ - void register_trx(trx_t *trx) - { - trx_list.push_front(*trx); - } + inline void register_trx(trx_t *trx) noexcept; /** @@ -1190,10 +1136,7 @@ class trx_sys_t @param trx transaction */ - void deregister_trx(trx_t *trx) - { - trx_list.remove(*trx); - } + inline void deregister_trx(trx_t *trx) noexcept; /** @@ -1207,17 +1150,7 @@ class trx_sys_t /** @return the number of active views */ - size_t view_count() const - { - size_t count= 0; - - trx_list.for_each([&count](const trx_t &trx) { - if (trx.read_view.is_open()) - ++count; - }); - - return count; - } + size_t view_count() const noexcept; /** Disable further allocation of transactions in a rollback segment that are subject to innodb_undo_log_truncate=ON diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index 61ecae6efc8f4..784ae19274746 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -73,10 +73,6 @@ trx_t *trx_create(); /** At shutdown, frees a transaction object. */ void trx_free_at_shutdown(trx_t *trx); -/** Disconnect a prepared transaction from MySQL. -@param[in,out] trx transaction */ -void trx_disconnect_prepared(trx_t *trx); - /** Initialize (resurrect) transactions at startup. */ dberr_t trx_lists_init_at_db_start(); @@ -730,7 +726,7 @@ struct trx_t : ilist_node<> This field is accessed by the thread that owns the transaction, without holding any mutex. There is only one foreign-thread access in trx_print_low() - and a possible race condition with trx_disconnect_prepared(). */ + and a possible race condition with disconnect_prepared(). */ bool is_recovered; const char* op_info; /*!< English text describing the current operation, or an empty @@ -968,6 +964,8 @@ struct trx_t : ilist_node<> public: /** Commit the transaction. */ void commit() noexcept; + /** Disconnect a prepared transaction */ + void disconnect_prepared() noexcept; /** Try to drop a persistent table. @param table persistent table diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index d4dddafd8c1c2..f33ef62356fb5 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -5239,43 +5239,30 @@ lock_trx_print_locks( } } -/** Functor to display all transactions */ -struct lock_print_info +/*********************************************************************//** +Prints info of locks for each transaction. This function will release +lock_sys.latch, which the caller must be holding in exclusive mode. +@param file output stream */ +void lock_print_info_all_transactions(FILE *file) { - lock_print_info(FILE* file, my_hrtime_t now) : - file(file), now(now), - purge_trx(purge_sys.query ? purge_sys.query->trx : nullptr) - {} + fprintf(file, "LIST OF TRANSACTIONS FOR EACH SESSION:\n"); - void operator()(const trx_t &trx) const + const trx_t *const purge_trx= purge_sys.query + ? purge_sys.query->trx : nullptr; + const my_hrtime_t now{my_hrtime_coarse()}; + + for (const trx_t &trx : trx_sys.trx_list) { if (UNIV_UNLIKELY(&trx == purge_trx)) - return; + continue; lock_trx_print_wait_and_mvcc_state(file, &trx, now); if (trx.will_lock && srv_print_innodb_lock_monitor) lock_trx_print_locks(file, &trx); } - FILE* const file; - const my_hrtime_t now; - const trx_t* const purge_trx; -}; - -/*********************************************************************//** -Prints info of locks for each transaction. This function will release -lock_sys.latch, which the caller must be holding in exclusive mode. */ -void -lock_print_info_all_transactions( -/*=============================*/ - FILE* file) /*!< in/out: file where to print */ -{ - fprintf(file, "LIST OF TRANSACTIONS FOR EACH SESSION:\n"); - - trx_sys.trx_list.for_each(lock_print_info(file, my_hrtime_coarse())); - lock_sys.wr_unlock(); - - ut_d(lock_validate()); + lock_sys.wr_unlock(); + ut_d(lock_validate()); } #ifdef UNIV_DEBUG diff --git a/storage/innobase/read/read0read.cc b/storage/innobase/read/read0read.cc index 46d58326edfcf..24f19bbebbb4f 100644 --- a/storage/innobase/read/read0read.cc +++ b/storage/innobase/read/read0read.cc @@ -29,6 +29,7 @@ Created 2/16/1997 Heikki Tuuri #include "srv0srv.h" #include "trx0sys.h" #include "trx0purge.h" +#include "lock0lock.h" /* ------------------------------------------------------------------------------- @@ -259,7 +260,8 @@ void trx_sys_t::clone_oldest_view(ReadViewBase *view) const { view->snapshot(nullptr); /* Find oldest view. */ - trx_list.for_each([view](const trx_t &trx) { - trx.read_view.append_to(view); - }); + lock_sys.rd_lock(SRW_LOCK_CALL); + for (const trx_t &trx : trx_list) + trx.read_view.append_to(view); + lock_sys.rd_unlock(); } diff --git a/storage/innobase/srv/srv0srv.cc b/storage/innobase/srv/srv0srv.cc index def31335d3468..f1ec57e4a6880 100644 --- a/storage/innobase/srv/srv0srv.cc +++ b/storage/innobase/srv/srv0srv.cc @@ -695,6 +695,20 @@ static void srv_refresh_innodb_monitor_stats(time_t current_time) mysql_mutex_unlock(&srv_innodb_monitor_mutex); } +size_t trx_sys_t::view_count() const noexcept +{ + ut_ad(lock_sys.is_holder()); + + size_t count{0}; + + for (const trx_t &trx : trx_list) + if (trx.read_view.is_open()) + ++count; + + return count; +} + + /******************************************************************//** Outputs to a file the output of the InnoDB Monitor. @return FALSE if not all information printed @@ -760,6 +774,7 @@ srv_printf_innodb_monitor( ut_copy_file(file, dict_foreign_err_file); } + size_t view_count{0}; mysql_mutex_unlock(&dict_foreign_err_mutex); /* Only if lock_print_info_summary proceeds correctly, @@ -778,6 +793,8 @@ srv_printf_innodb_monitor( } } + view_count = trx_sys.view_count(); + /* NOTE: The following function will release the lock_sys.latch that lock_print_info_summary() acquired. */ @@ -848,11 +865,11 @@ srv_printf_innodb_monitor( buf_print_io(file); - fputs("--------------\n" - "ROW OPERATIONS\n" - "--------------\n", file); - fprintf(file, ULINTPF " read views open inside InnoDB\n", - trx_sys.view_count()); + fprintf(file, + "--------------\n" + "ROW OPERATIONS\n" + "--------------\n" + "%zu read views open inside InnoDB\n", view_count); if (ulint n_reserved = fil_system.sys_space->n_reserved_extents) { fprintf(file, diff --git a/storage/innobase/trx/trx0i_s.cc b/storage/innobase/trx/trx0i_s.cc index c1784b94bb172..78e3f26c6a613 100644 --- a/storage/innobase/trx/trx0i_s.cc +++ b/storage/innobase/trx/trx0i_s.cc @@ -999,7 +999,7 @@ trx_i_s_cache_clear( innodb_locks' and innodb_lock_waits' caches. */ -static void fetch_data_into_cache_low(trx_i_s_cache_t *cache, const trx_t *trx) +static bool fetch_data_into_cache_low(trx_i_s_cache_t *cache, const trx_t *trx) { i_s_locks_row_t *requested_lock_row; @@ -1028,13 +1028,14 @@ static void fetch_data_into_cache_low(trx_i_s_cache_t *cache, const trx_t *trx) table_cache_create_empty_row(&cache->innodb_trx, cache))) { if (fill_trx_row(trx_row, trx, requested_lock_row, cache)) - return; + return false; --cache->innodb_trx.rows_used; } } /* memory could not be allocated */ cache->is_truncated= true; + return true; } @@ -1045,21 +1046,25 @@ static void fetch_data_into_cache_low(trx_i_s_cache_t *cache, const trx_t *trx) static void fetch_data_into_cache(trx_i_s_cache_t *cache) { - LockMutexGuard g{SRW_LOCK_CALL}; + /* these are protected by cache->rw_lock.wr_lock() */ + cache->is_truncated= false; trx_i_s_cache_clear(cache); + LockMutexGuard g{SRW_LOCK_CALL}; + const trx_t *const purge_trx= purge_sys.query + ? purge_sys.query->trx : nullptr; + /* Capture the state of transactions */ - trx_sys.trx_list.for_each([cache](trx_t &trx) { - if (!cache->is_truncated && trx.state != TRX_STATE_NOT_STARTED && - &trx != (purge_sys.query ? purge_sys.query->trx : nullptr)) + for (trx_t &trx : trx_sys.trx_list) + if (trx.state != TRX_STATE_NOT_STARTED && &trx != purge_trx) { trx.mutex_lock(); - if (trx.is_started()) + const bool truncated= trx.is_started() && fetch_data_into_cache_low(cache, &trx); trx.mutex_unlock(); + if (truncated) + break; } - }); - cache->is_truncated= false; } diff --git a/storage/innobase/trx/trx0sys.cc b/storage/innobase/trx/trx0sys.cc index 0bd9a06bd4486..ea034c14b38d8 100644 --- a/storage/innobase/trx/trx0sys.cc +++ b/storage/innobase/trx/trx0sys.cc @@ -40,6 +40,8 @@ Created 3/26/1996 Heikki Tuuri #include "log0log.h" #include "log0recv.h" #include "os0file.h" +#include "lock0lock.h" +#include "log.h" /** The transaction system */ trx_sys_t trx_sys; @@ -172,7 +174,6 @@ void trx_sys_t::create() ut_ad(this == &trx_sys); ut_ad(!is_initialised()); m_initialised= true; - trx_list.create(); rw_trx_hash.init(); for (auto &rseg : temp_rsegs) rseg.init(nullptr, FIL_NULL); @@ -362,11 +363,12 @@ trx_sys_t::close() return; } + lock_sys.rd_lock(SRW_LOCK_CALL); if (size_t size = view_count()) { - ib::error() << "All read views were not closed before" - " shutdown: " << size << " read views open"; + sql_print_error("InnoDB: All read views were not closed before" + " shutdown: %zu read views open", size); } - + lock_sys.rd_unlock(); rw_trx_hash.destroy(); /* There can't be any active transactions. */ @@ -374,16 +376,15 @@ trx_sys_t::close() for (auto& rseg : rseg_array) rseg.destroy(); ut_a(trx_list.empty()); - trx_list.close(); m_initialised = false; } /** @return total number of active (non-prepared) transactions */ -size_t trx_sys_t::any_active_transactions(size_t *prepared) +size_t trx_sys_t::any_active_transactions(size_t *prepared) noexcept { size_t total_trx= 0, prepared_trx= 0; - trx_sys.trx_list.for_each([&](const trx_t &trx) { + for (const trx_t &trx : trx_list) switch (trx.state) { case TRX_STATE_NOT_STARTED: case TRX_STATE_ABORTED: @@ -399,7 +400,6 @@ size_t trx_sys_t::any_active_transactions(size_t *prepared) case TRX_STATE_PREPARED_RECOVERED: prepared_trx++; } - }); if (prepared) *prepared= prepared_trx; diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index 2d2492600b5f9..7d66391347daa 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -318,6 +318,21 @@ trx_pool_close() trx_pools = 0; } +inline void trx_sys_t::register_trx(trx_t *trx) noexcept +{ + lock_sys.wr_lock(SRW_LOCK_CALL); + trx_list.push_front(*trx); + lock_sys.wr_unlock(); +} + +inline void trx_sys_t::deregister_trx(trx_t *trx) noexcept +{ + lock_sys.wr_lock(SRW_LOCK_CALL); + trx_list.remove(*trx); + lock_sys.wr_unlock(); +} + + /** @return an allocated transaction */ trx_t *trx_create() { @@ -532,23 +547,19 @@ TRANSACTIONAL_TARGET void trx_free_at_shutdown(trx_t *trx) } -/** - Disconnect a prepared transaction from MySQL - @param[in,out] trx transaction -*/ -void trx_disconnect_prepared(trx_t *trx) +void trx_t::disconnect_prepared() noexcept { - ut_ad(trx_state_eq(trx, TRX_STATE_PREPARED)); - ut_ad(trx->mysql_thd); - ut_ad(!trx->mysql_log_file_name); - trx->read_view.close(); - trx_sys.trx_list.freeze(); - trx->is_recovered= true; - trx->mysql_thd= NULL; - trx_sys.trx_list.unfreeze(); + ut_ad(trx_state_eq(this, TRX_STATE_PREPARED)); + ut_ad(mysql_thd); + ut_ad(!mysql_log_file_name); + read_view.close(); + mutex.wr_lock(); + is_recovered= true; + mysql_thd= nullptr; /* todo/fixme: suggest to do it at innodb prepare */ - trx->will_lock= false; - trx_sys.rw_trx_hash.put_pins(trx); + will_lock= false; + mutex.wr_unlock(); + trx_sys.rw_trx_hash.put_pins(this); } MY_ATTRIBUTE((nonnull, warn_unused_result))