Commit a447980f authored by Sergey Vojtovich's avatar Sergey Vojtovich

MDEV-14756 - Remove trx_sys_t::rw_trx_list

Let lock_print_info_all_transactions() iterate rw_trx_hash instead of
rw_trx_list.

When printing info of locks for transactions, InnoDB monitor doesn't
attempt to read relevant page from disk anymore. The code was prone
to race conditions.

Note that TrxListIterator didn't work as advertised: it iterated
rw_trx_list only.
parent 886af392
......@@ -368,22 +368,6 @@ trx_print_latched(
ulint max_query_len); /*!< in: max query length to print,
or 0 to use the default max length */
#ifdef WITH_WSREP
/**********************************************************************//**
Prints info about a transaction.
Transaction information may be retrieved without having trx_sys->mutex acquired
so it may not be completely accurate. The caller must own lock_sys->mutex
and the trx must have some locks to make sure that it does not escape
without locking lock_sys->mutex. */
UNIV_INTERN
void
wsrep_trx_print_locking(
FILE* f, /*!< in: output stream */
const trx_t* trx, /*!< in: transaction */
ulint max_query_len) /*!< in: max query length to print,
or 0 to use the default max length */
MY_ATTRIBUTE((nonnull));
#endif /* WITH_WSREP */
/**********************************************************************//**
Prints info about a transaction.
Acquires and releases lock_sys->mutex and trx_sys->mutex. */
......@@ -852,7 +836,7 @@ transactions. The trx_sys->mutex prevents a race condition between it
and lock_trx_release_locks() [invoked by trx_commit()].
* trx_print_low() may access transactions not associated with the current
thread. The caller must be holding trx_sys->mutex and lock_sys->mutex.
thread. The caller must be holding lock_sys->mutex.
* When a transaction handle is in the trx_sys->mysql_trx_list or
trx_sys->trx_list, some of its fields must not be modified without
......
......@@ -1402,14 +1402,14 @@ wsrep_kill_victim(
ib::info() << "*** Victim TRANSACTION:";
}
wsrep_trx_print_locking(stderr, trx, 3000);
trx_print_latched(stderr, trx, 3000);
if (bf_other) {
ib::info() << "*** Priority TRANSACTION:";
} else {
ib::info() << "*** Victim TRANSACTION:";
}
wsrep_trx_print_locking(stderr, lock->trx, 3000);
trx_print_latched(stderr, lock->trx, 3000);
ib::info() << "*** WAITING FOR THIS LOCK TO BE GRANTED:";
......@@ -5714,116 +5714,6 @@ struct PrintNotStarted {
FILE* m_file;
};
/** Iterate over a transaction's locks. Keeping track of the
iterator using an ordinal value. */
class TrxLockIterator {
public:
TrxLockIterator() { rewind(); }
/** Get the m_index(th) lock of a transaction.
@return current lock or 0 */
const lock_t* current(const trx_t* trx) const
{
lock_t* lock;
ulint i = 0;
for (lock = UT_LIST_GET_FIRST(trx->lock.trx_locks);
lock != NULL && i < m_index;
lock = UT_LIST_GET_NEXT(trx_locks, lock), ++i) {
/* No op */
}
return(lock);
}
/** Set the ordinal value to 0 */
void rewind()
{
m_index = 0;
}
/** Increment the ordinal value.
@retun the current index value */
ulint next()
{
return(++m_index);
}
private:
/** Current iterator position */
ulint m_index;
};
/** This iterates over both the RW and RO trx_sys lists. We need to keep
track where the iterator was up to and we do that using an ordinal value. */
class TrxListIterator {
public:
TrxListIterator() : m_index()
{
/* We iterate over the RW trx list first. */
m_trx_list = &trx_sys->rw_trx_list;
}
/** Get the current transaction whose ordinality is m_index.
@return current transaction or 0 */
const trx_t* current()
{
return(reposition());
}
/** Advance the transaction current ordinal value and reset the
transaction lock ordinal value */
void next()
{
++m_index;
m_lock_iter.rewind();
}
TrxLockIterator& lock_iter()
{
return(m_lock_iter);
}
private:
/** Reposition the "cursor" on the current transaction. If it
is the first time then the "cursor" will be positioned on the
first transaction.
@return transaction instance or 0 */
const trx_t* reposition() const
{
ulint i;
trx_t* trx;
/* Make the transaction at the ordinal value of m_index
the current transaction. ie. reposition/restore */
for (i = 0, trx = UT_LIST_GET_FIRST(*m_trx_list);
trx != NULL && (i < m_index);
trx = UT_LIST_GET_NEXT(trx_list, trx), ++i) {
check_trx_state(trx);
}
return(trx);
}
/** Ordinal value of the transaction in the current transaction list */
ulint m_index;
/** Current transaction list */
trx_ut_list_t* m_trx_list;
/** For iterating over a transaction's locks */
TrxLockIterator m_lock_iter;
};
/** Prints transaction lock wait and MVCC state.
@param[in,out] file file where to print
@param[in] trx transaction */
......@@ -5860,119 +5750,29 @@ lock_trx_print_wait_and_mvcc_state(
}
/*********************************************************************//**
Prints info of locks for a transaction. This function will release the
lock mutex and the trx_sys_t::mutex if the page was read from disk.
@return true if page was read from the tablespace */
Prints info of locks for a transaction. */
static
bool
lock_rec_fetch_page(
/*================*/
const lock_t* lock) /*!< in: record lock */
{
ut_ad(lock_get_type_low(lock) == LOCK_REC);
ulint space_id = lock->un_member.rec_lock.space;
fil_space_t* space;
bool found;
const page_size_t& page_size = fil_space_get_page_size(space_id,
&found);
ulint page_no = lock->un_member.rec_lock.page_no;
/* Check if the .ibd file exists. */
if (found) {
mtr_t mtr;
lock_mutex_exit();
mutex_exit(&trx_sys->mutex);
DEBUG_SYNC_C("innodb_monitor_before_lock_page_read");
/* Check if the space is exists or not. only
when the space is valid, try to get the page. */
space = fil_space_acquire(space_id);
if (space) {
dberr_t err = DB_SUCCESS;
mtr_start(&mtr);
buf_page_get_gen(
page_id_t(space_id, page_no), page_size,
RW_NO_LATCH, NULL,
BUF_GET_POSSIBLY_FREED,
__FILE__, __LINE__, &mtr, &err);
mtr_commit(&mtr);
fil_space_release(space);
}
lock_mutex_enter();
mutex_enter(&trx_sys->mutex);
return(true);
}
return(false);
}
/*********************************************************************//**
Prints info of locks for a transaction.
@return true if all printed, false if latches were released. */
static
bool
void
lock_trx_print_locks(
/*=================*/
FILE* file, /*!< in/out: File to write */
const trx_t* trx, /*!< in: current transaction */
TrxLockIterator&iter, /*!< in: transaction lock iterator */
bool load_block) /*!< in: if true then read block
from disk */
const trx_t* trx) /*!< in: current transaction */
{
const lock_t* lock;
uint32_t i= 0;
/* Iterate over the transaction's locks. */
while ((lock = iter.current(trx)) != 0) {
for (lock_t *lock = UT_LIST_GET_FIRST(trx->lock.trx_locks);
lock != NULL;
lock = UT_LIST_GET_NEXT(trx_locks, lock)) {
if (lock_get_type_low(lock) == LOCK_REC) {
if (load_block) {
/* Note: lock_rec_fetch_page() will
release both the lock mutex and the
trx_sys_t::mutex if it does a read
from disk. */
if (lock_rec_fetch_page(lock)) {
/* We need to resync the
current transaction. */
return(false);
}
/* It is a single table tablespace
and the .ibd file is missing
(TRUNCATE TABLE probably stole the
locks): just print the lock without
attempting to load the page in the
buffer pool. */
fprintf(file,
"RECORD LOCKS on non-existing"
" space %u\n",
lock->un_member.rec_lock.space);
}
/* Print all the record locks on the page from
the record lock bitmap */
lock_rec_print(file, lock);
load_block = true;
} else {
ut_ad(lock_get_type_low(lock) & LOCK_TABLE);
lock_table_print(file, lock);
}
if (iter.next() >= 10) {
if (++i == 10) {
fprintf(file,
"10 LOCKS PRINTED FOR THIS TRX:"
......@@ -5981,10 +5781,32 @@ lock_trx_print_locks(
break;
}
}
}
return(true);
static my_bool lock_print_info_all_transactions_callback(
rw_trx_hash_element_t *element, FILE *file)
{
mutex_enter(&element->mutex);
if (trx_t *trx= element->trx)
{
check_trx_state(trx);
lock_trx_print_wait_and_mvcc_state(file, trx);
if (srv_print_innodb_lock_monitor)
{
trx->reference();
mutex_exit(&element->mutex);
lock_trx_print_locks(file, trx);
trx->release_reference();
return 0;
}
}
mutex_exit(&element->mutex);
return 0;
}
/*********************************************************************//**
Prints info of locks for each transaction. This function assumes that the
caller holds the lock mutex and more importantly it will release the lock
......@@ -5998,8 +5820,6 @@ lock_print_info_all_transactions(
fprintf(file, "LIST OF TRANSACTIONS FOR EACH SESSION:\n");
mutex_enter(&trx_sys->mutex);
/* First print info on non-active transactions */
/* NOTE: information of auto-commit non-locking read-only
......@@ -6007,62 +5827,14 @@ lock_print_info_all_transactions(
available from INFORMATION_SCHEMA.INNODB_TRX. */
PrintNotStarted print_not_started(file);
mutex_enter(&trx_sys->mutex);
ut_list_map(trx_sys->mysql_trx_list, print_not_started);
mutex_exit(&trx_sys->mutex);
const trx_t* trx;
TrxListIterator trx_iter;
const trx_t* prev_trx = 0;
/* Control whether a block should be fetched from the buffer pool. */
bool load_block = true;
bool monitor = srv_print_innodb_lock_monitor;
while ((trx = trx_iter.current()) != 0) {
check_trx_state(trx);
if (trx != prev_trx) {
lock_trx_print_wait_and_mvcc_state(file, trx);
prev_trx = trx;
/* The transaction that read in the page is no
longer the one that read the page in. We need to
force a page read. */
load_block = true;
}
/* If we need to print the locked record contents then we
need to fetch the containing block from the buffer pool. */
if (monitor) {
/* Print the locks owned by the current transaction. */
TrxLockIterator& lock_iter = trx_iter.lock_iter();
if (!lock_trx_print_locks(
file, trx, lock_iter, load_block)) {
/* Resync trx_iter, the trx_sys->mutex and
the lock mutex were released. A page was
successfully read in. We need to print its
contents on the next call to
lock_trx_print_locks(). On the next call to
lock_trx_print_locks() we should simply print
the contents of the page just read in.*/
load_block = false;
continue;
}
}
load_block = true;
/* All record lock details were printed without fetching
a page from disk, or we didn't need to print the detail. */
trx_iter.next();
}
trx_sys->rw_trx_hash.iterate_no_dups(
reinterpret_cast<my_hash_walk_action>
(lock_print_info_all_transactions_callback), file);
lock_mutex_exit();
mutex_exit(&trx_sys->mutex);
ut_ad(lock_validate());
}
......@@ -7992,8 +7764,6 @@ DeadlockChecker::print(const trx_t* trx, ulint max_query_len)
ulint n_trx_locks = UT_LIST_GET_LEN(trx->lock.trx_locks);
ulint heap_size = mem_heap_get_size(trx->lock.lock_heap);
mutex_enter(&trx_sys->mutex);
trx_print_low(lock_latest_err_file, trx, max_query_len,
n_rec_locks, n_trx_locks, heap_size);
......@@ -8001,8 +7771,6 @@ DeadlockChecker::print(const trx_t* trx, ulint max_query_len)
trx_print_low(stderr, trx, max_query_len,
n_rec_locks, n_trx_locks, heap_size);
}
mutex_exit(&trx_sys->mutex);
}
/** Print lock data to the deadlock file and possibly to stderr.
......
......@@ -203,7 +203,7 @@ wsrep_is_BF_lock_timeout(
ut_ad(lock_mutex_own());
wsrep_trx_print_locking(stderr, trx, 3000);
trx_print_latched(stderr, trx, 3000);
if (!locked) {
lock_mutex_exit();
......
......@@ -779,8 +779,6 @@ row_ins_foreign_trx_print(
heap_size = mem_heap_get_size(trx->lock.lock_heap);
lock_mutex_exit();
trx_sys_mutex_enter();
mutex_enter(&dict_foreign_err_mutex);
rewind(dict_foreign_err_file);
ut_print_timestamp(dict_foreign_err_file);
......@@ -789,8 +787,6 @@ row_ins_foreign_trx_print(
trx_print_low(dict_foreign_err_file, trx, 600,
n_rec_locks, n_trx_locks, heap_size);
trx_sys_mutex_exit();
ut_ad(mutex_own(&dict_foreign_err_mutex));
}
......
......@@ -2182,8 +2182,7 @@ trx_mark_sql_stat_end(
}
/**********************************************************************//**
Prints info about a transaction.
Caller must hold trx_sys->mutex. */
Prints info about a transaction. */
void
trx_print_low(
/*==========*/
......@@ -2204,8 +2203,6 @@ trx_print_low(
ibool newline;
const char* op_info;
ut_ad(trx_sys_mutex_own());
fprintf(f, "TRANSACTION " TRX_ID_FMT, trx_get_id_for_print(trx));
/* trx->state cannot change from or to NOT_STARTED while we
......@@ -2305,7 +2302,7 @@ trx_print_low(
/**********************************************************************//**
Prints info about a transaction.
The caller must hold lock_sys->mutex and trx_sys->mutex.
The caller must hold lock_sys->mutex.
When possible, use trx_print() instead. */
void
trx_print_latched(
......@@ -2316,7 +2313,6 @@ trx_print_latched(
or 0 to use the default max length */
{
ut_ad(lock_mutex_own());
ut_ad(trx_sys_mutex_own());
trx_print_low(f, trx, max_query_len,
lock_number_of_rows_locked(&trx->lock),
......@@ -2324,119 +2320,9 @@ trx_print_latched(
mem_heap_get_size(trx->lock.lock_heap));
}
#ifdef WITH_WSREP
/**********************************************************************//**
Prints info about a transaction.
Transaction information may be retrieved without having trx_sys->mutex acquired
so it may not be completely accurate. The caller must own lock_sys->mutex
and the trx must have some locks to make sure that it does not escape
without locking lock_sys->mutex. */
UNIV_INTERN
void
wsrep_trx_print_locking(
FILE* f,
/*!< in: output stream */
const trx_t* trx,
/*!< in: transaction */
ulint max_query_len)
/*!< in: max query length to print,
or 0 to use the default max length */
{
ibool newline;
const char* op_info;
ut_ad(lock_mutex_own());
ut_ad(trx->lock.trx_locks.count > 0);
fprintf(f, "TRANSACTION " TRX_ID_FMT, trx->id);
/* trx->state may change since trx_sys->mutex is not required */
switch (trx->state) {
case TRX_STATE_NOT_STARTED:
fputs(", not started", f);
goto state_ok;
case TRX_STATE_ACTIVE:
fprintf(f, ", ACTIVE %lu sec",
(ulong) difftime(time(NULL), trx->start_time));
goto state_ok;
case TRX_STATE_FORCED_ROLLBACK:
fprintf(f, ", FORCED ROLLBACK, %lu sec",
(ulong) difftime(time(NULL), trx->start_time));
goto state_ok;
case TRX_STATE_PREPARED:
fprintf(f, ", ACTIVE (PREPARED) %lu sec",
(ulong) difftime(time(NULL), trx->start_time));
goto state_ok;
case TRX_STATE_COMMITTED_IN_MEMORY:
fputs(", COMMITTED IN MEMORY", f);
goto state_ok;
}
fprintf(f, ", state %lu", (ulong) trx->state);
ut_ad(0);
state_ok:
/* prevent a race condition */
op_info = trx->op_info;
if (*op_info) {
putc(' ', f);
fputs(op_info, f);
}
if (trx->is_recovered) {
fputs(" recovered trx", f);
}
if (trx->declared_to_be_inside_innodb) {
fprintf(f, ", thread declared inside InnoDB %lu",
(ulong) trx->n_tickets_to_enter_innodb);
}
putc('\n', f);
if (trx->n_mysql_tables_in_use > 0 || trx->mysql_n_tables_locked > 0) {
fprintf(f, "mysql tables in use %lu, locked %lu\n",
(ulong) trx->n_mysql_tables_in_use,
(ulong) trx->mysql_n_tables_locked);
}
newline = TRUE;
/* trx->lock.que_state of an ACTIVE transaction may change
while we are not holding trx->mutex. We perform a dirty read
for performance reasons. */
switch (trx->lock.que_state) {
case TRX_QUE_RUNNING:
newline = FALSE; break;
case TRX_QUE_LOCK_WAIT:
fputs("LOCK WAIT ", f); break;
case TRX_QUE_ROLLING_BACK:
fputs("ROLLING BACK ", f); break;
case TRX_QUE_COMMITTING:
fputs("COMMITTING ", f); break;
default:
fprintf(f, "que state %lu ", (ulong) trx->lock.que_state);
}
if (trx->undo_no != 0) {
newline = TRUE;
fprintf(f, ", undo log entries " TRX_ID_FMT, trx->undo_no);
}
if (newline) {
putc('\n', f);
}
if (trx->mysql_thd != NULL) {
innobase_mysql_print_thd(
f, trx->mysql_thd, static_cast<uint>(max_query_len));
}
}
#endif /* WITH_WSREP */
/**********************************************************************//**
Prints info about a transaction.
Acquires and releases lock_sys->mutex and trx_sys->mutex. */
Acquires and releases lock_sys->mutex. */
void
trx_print(
/*======*/
......@@ -2455,12 +2341,8 @@ trx_print(
heap_size = mem_heap_get_size(trx->lock.lock_heap);
lock_mutex_exit();
mutex_enter(&trx_sys->mutex);
trx_print_low(f, trx, max_query_len,
n_rec_locks, n_trx_locks, heap_size);
mutex_exit(&trx_sys->mutex);
}
#ifdef UNIV_DEBUG
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment