Skip to content

Commit 2c9e75c

Browse files
committed
MDEV-15326 after-merge fixes
trx_t::is_recovered: Revert most of the changes that were made by the merge of MDEV-15326 from 10.2. The trx_sys.rw_trx_hash and the recovery of transactions at startup is quite different in 10.3. trx_free_at_shutdown(): Avoid excessive mutex protection. Reading fields that can only be modified by the current thread (owning the transaction) can be done outside mutex. trx_t::commit_state(): Restore a tighter assertion. trx_rollback_recovered(): Clarify why there is no potential race condition with other transactions. lock_trx_release_locks(): Merge with trx_t::release_locks(), and avoid holding lock_sys.mutex unnecessarily long. rw_trx_hash_t::find(): Remove redundant code, and avoid starving the committer by checking trx_t::state before trx_t::reference().
1 parent 537f859 commit 2c9e75c

File tree

7 files changed

+77
-81
lines changed

7 files changed

+77
-81
lines changed

storage/innobase/include/lock0lock.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ lock_rec_unlock(
485485

486486
/** Release the explicit locks of a committing transaction,
487487
and release possible other transactions waiting because of these locks. */
488-
void lock_trx_release_locks(trx_t* trx);
488+
void lock_release(trx_t* trx);
489489

490490
/*********************************************************************//**
491491
Calculates the fold value of a page file address: used in inserting or

storage/innobase/include/trx0sys.h

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -628,12 +628,7 @@ class rw_trx_hash_t
628628
The caller should already have handled trx_id==0 specially.
629629
*/
630630
ut_ad(trx_id);
631-
if (caller_trx && caller_trx->id == trx_id)
632-
{
633-
if (do_ref_count)
634-
caller_trx->reference();
635-
return caller_trx;
636-
}
631+
ut_ad(!caller_trx || caller_trx->id != trx_id || !do_ref_count);
637632

638633
trx_t *trx= 0;
639634
LF_PINS *pins= caller_trx ? get_pins(caller_trx) : lf_hash_get_pins(&hash);
@@ -648,9 +643,25 @@ class rw_trx_hash_t
648643
lf_hash_search_unpin(pins);
649644
if ((trx= element->trx)) {
650645
DBUG_ASSERT(trx_id == trx->id);
651-
if (do_ref_count)
652-
trx->reference();
653646
ut_d(validate_element(trx));
647+
if (do_ref_count)
648+
{
649+
/*
650+
We have an early state check here to avoid committer
651+
starvation in a wait loop for transaction references,
652+
when there's a stream of trx_sys.find() calls from other
653+
threads. The trx->state may change to COMMITTED after
654+
trx->mutex is released, and it will have to be rechecked
655+
by the caller after reacquiring the mutex.
656+
*/
657+
trx_mutex_enter(trx);
658+
const trx_state_t state= trx->state;
659+
trx_mutex_exit(trx);
660+
if (state == TRX_STATE_COMMITTED_IN_MEMORY)
661+
trx= NULL;
662+
else
663+
trx->reference();
664+
}
654665
}
655666
mutex_exit(&element->mutex);
656667
}

storage/innobase/include/trx0trx.h

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -699,10 +699,9 @@ Normally, only the thread that is currently associated with a running
699699
transaction may access (read and modify) the trx object, and it may do
700700
so without holding any mutex. The following are exceptions to this:
701701
702-
* trx_rollback_resurrected() may access resurrected (connectionless)
703-
transactions while the system is already processing new user
704-
transactions. The trx_sys.mutex and trx->is_recovered prevent
705-
a race condition between it and trx_commit().
702+
* trx_rollback_recovered() may access resurrected (connectionless)
703+
transactions (state == TRX_STATE_ACTIVE && is_recovered)
704+
while the system is already processing new user transactions (!is_recovered).
706705
707706
* trx_print_low() may access transactions not associated with the current
708707
thread. The caller must be holding lock_sys.mutex.
@@ -839,10 +838,6 @@ struct trx_t {
839838
840839
Transitions to COMMITTED are protected by trx_t::mutex. */
841840
trx_state_t state;
842-
/** whether this is a recovered transaction that should be
843-
rolled back by trx_rollback_or_clean_recovered().
844-
Protected by trx_t::mutex for transactions that are in trx_sys. */
845-
bool is_recovered;
846841

847842
ReadView read_view; /*!< consistent read view used in the
848843
transaction, or NULL if not yet set */
@@ -852,6 +847,15 @@ struct trx_t {
852847
by trx_t::mutex). */
853848

854849
/* These fields are not protected by any mutex. */
850+
851+
/** false=normal transaction, true=recovered (must be rolled back)
852+
or disconnected transaction in XA PREPARE STATE.
853+
854+
This field is accessed by the thread that owns the transaction,
855+
without holding any mutex.
856+
There is only one foreign-thread access in trx_print_low()
857+
and a possible race condition with trx_disconnect_prepared(). */
858+
bool is_recovered;
855859
const char* op_info; /*!< English text describing the
856860
current operation, or an empty
857861
string */

storage/innobase/lock/lock0lock.cc

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4274,23 +4274,17 @@ lock_check_dict_lock(
42744274
}
42754275
#endif /* UNIV_DEBUG */
42764276

4277-
/*********************************************************************//**
4278-
Releases transaction locks, and releases possible other transactions waiting
4279-
because of these locks. */
4280-
static
4281-
void
4282-
lock_release(
4283-
/*=========*/
4284-
trx_t* trx) /*!< in/out: transaction */
4277+
/** Release the explicit locks of a committing transaction,
4278+
and release possible other transactions waiting because of these locks. */
4279+
void lock_release(trx_t* trx)
42854280
{
4286-
lock_t* lock;
42874281
ulint count = 0;
42884282
trx_id_t max_trx_id = trx_sys.get_max_trx_id();
42894283

4290-
ut_ad(lock_mutex_own());
4284+
lock_mutex_enter();
42914285
ut_ad(!trx_mutex_own(trx));
42924286

4293-
for (lock = UT_LIST_GET_LAST(trx->lock.trx_locks);
4287+
for (lock_t* lock = UT_LIST_GET_LAST(trx->lock.trx_locks);
42944288
lock != NULL;
42954289
lock = UT_LIST_GET_LAST(trx->lock.trx_locks)) {
42964290

@@ -4330,6 +4324,8 @@ lock_release(
43304324

43314325
++count;
43324326
}
4327+
4328+
lock_mutex_exit();
43334329
}
43344330

43354331
/* True if a lock mode is S or X */
@@ -4784,7 +4780,7 @@ lock_table_queue_validate(
47844780
lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock)) {
47854781

47864782
/* lock->trx->state cannot change from or to NOT_STARTED
4787-
while we are holding the trx_sys.mutex. It may change
4783+
while we are holding the lock_sys.mutex. It may change
47884784
from ACTIVE or PREPARED to PREPARED or COMMITTED. */
47894785
trx_mutex_enter(lock->trx);
47904786
check_trx_state(lock->trx);
@@ -5390,13 +5386,13 @@ lock_rec_convert_impl_to_expl_for_trx(
53905386
trx_t* trx, /*!< in/out: active transaction */
53915387
ulint heap_no)/*!< in: rec heap number to lock */
53925388
{
5389+
ut_ad(trx->is_referenced());
53935390
ut_ad(page_rec_is_leaf(rec));
53945391
ut_ad(!rec_is_metadata(rec, index));
53955392

53965393
DEBUG_SYNC_C("before_lock_rec_convert_impl_to_expl_for_trx");
53975394
lock_mutex_enter();
53985395
trx_mutex_enter(trx);
5399-
ut_ad(trx->is_referenced());
54005396
ut_ad(!trx_state_eq(trx, TRX_STATE_NOT_STARTED));
54015397

54025398
if (!trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY)
@@ -6235,27 +6231,6 @@ lock_unlock_table_autoinc(
62356231
}
62366232
}
62376233

6238-
/** Release the explicit locks of a committing transaction,
6239-
and release possible other transactions waiting because of these locks. */
6240-
void lock_trx_release_locks(trx_t* trx)
6241-
{
6242-
ut_ad(UT_LIST_GET_LEN(trx->lock.trx_locks));
6243-
6244-
lock_mutex_enter();
6245-
lock_release(trx);
6246-
trx->lock.n_rec_locks = 0;
6247-
/* We don't remove the locks one by one from the vector for
6248-
efficiency reasons. We simply reset it because we would have
6249-
released all the locks anyway. */
6250-
6251-
trx->lock.table_locks.clear();
6252-
6253-
ut_ad(UT_LIST_GET_LEN(trx->lock.trx_locks) == 0);
6254-
ut_ad(ib_vector_is_empty(trx->autoinc_locks));
6255-
lock_mutex_exit();
6256-
mem_heap_empty(trx->lock.lock_heap);
6257-
}
6258-
62596234
static inline dberr_t lock_trx_handle_wait_low(trx_t* trx)
62606235
{
62616236
ut_ad(lock_mutex_own());

storage/innobase/trx/trx0purge.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ trx_purge_add_undo_to_history(const trx_t* trx, trx_undo_t*& undo, mtr_t* mtr)
252252
/* After the purge thread has been given permission to exit,
253253
we may roll back transactions (trx->undo_no==0)
254254
in THD::cleanup() invoked from unlink_thd() in fast shutdown,
255-
or in trx_rollback_resurrected() in slow shutdown.
255+
or in trx_rollback_recovered() in slow shutdown.
256256
257257
Before any transaction-generating background threads or the
258258
purge have been started, recv_recovery_rollback_active() can

storage/innobase/trx/trx0roll.cc

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -764,12 +764,8 @@ static my_bool trx_rollback_recovered_callback(rw_trx_hash_element_t *element,
764764
mutex_enter(&element->mutex);
765765
if (trx_t *trx= element->trx)
766766
{
767-
/* The trx->is_recovered flag and trx->state are set
768-
atomically under the protection of the trx->mutex in
769-
trx_t::commit_state(). We do not want to accidentally clean up
770-
a non-recovered transaction here. */
771767
mutex_enter(&trx->mutex);
772-
if (trx->is_recovered && trx_state_eq(trx, TRX_STATE_ACTIVE))
768+
if (trx_state_eq(trx, TRX_STATE_ACTIVE) && trx->is_recovered)
773769
trx_list->push_back(trx);
774770
mutex_exit(&trx->mutex);
775771
}
@@ -815,7 +811,8 @@ void trx_rollback_recovered(bool all)
815811

816812
ut_ad(trx);
817813
ut_d(trx_mutex_enter(trx));
818-
ut_ad(trx->is_recovered && trx_state_eq(trx, TRX_STATE_ACTIVE));
814+
ut_ad(trx->is_recovered);
815+
ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE));
819816
ut_d(trx_mutex_exit(trx));
820817

821818
if (!srv_is_being_started && !srv_undo_sources && srv_fast_shutdown)
@@ -831,6 +828,22 @@ void trx_rollback_recovered(bool all)
831828
ut_ad(!srv_undo_sources);
832829
ut_ad(srv_fast_shutdown);
833830
discard:
831+
/* Note: before kill_server() invoked innobase_end() via
832+
unireg_end(), it invoked close_connections(), which should initiate
833+
the rollback of any user transactions via THD::cleanup() in the
834+
connection threads, and wait for all THD::cleanup() to complete.
835+
So, no active user transactions should exist at this point.
836+
837+
srv_undo_sources=false was cleared early in innobase_end().
838+
839+
Generally, the server guarantees that all connections using
840+
InnoDB must be disconnected by the time we are reaching this code,
841+
be it during shutdown or UNINSTALL PLUGIN.
842+
843+
Because there is no possible race condition with any
844+
concurrent user transaction, we do not have to invoke
845+
trx->commit_state() or wait for !trx->is_referenced()
846+
before trx_sys.deregister_rw(trx). */
834847
trx_sys.deregister_rw(trx);
835848
trx_free_at_shutdown(trx);
836849
}

storage/innobase/trx/trx0trx.cc

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,9 @@ void trx_free(trx_t*& trx)
463463
/** Transition to committed state, to release implicit locks. */
464464
inline void trx_t::commit_state()
465465
{
466+
ut_ad(state == TRX_STATE_PREPARED
467+
|| state == TRX_STATE_PREPARED_RECOVERED
468+
|| state == TRX_STATE_ACTIVE);
466469
/* This makes the transaction committed in memory and makes its
467470
changes to data visible to other transactions. NOTE that there is a
468471
small discrepancy from the strict formal visibility rules here: a
@@ -475,42 +478,34 @@ inline void trx_t::commit_state()
475478
makes modifications to the database, will get an lsn larger than the
476479
committing transaction T. In the case where the log flush fails, and
477480
T never gets committed, also T2 will never get committed. */
478-
ut_ad(trx_mutex_own(this));
479-
ut_ad(state != TRX_STATE_NOT_STARTED);
480-
ut_ad(state != TRX_STATE_COMMITTED_IN_MEMORY
481-
|| (is_recovered && !UT_LIST_GET_LEN(lock.trx_locks)));
481+
trx_mutex_enter(this);
482482
state= TRX_STATE_COMMITTED_IN_MEMORY;
483-
484-
/* If the background thread trx_rollback_or_clean_recovered()
485-
is still active then there is a chance that the rollback
486-
thread may see this trx as COMMITTED_IN_MEMORY and goes ahead
487-
to clean it up calling trx_cleanup_at_db_startup(). This can
488-
happen in the case we are committing a trx here that is left
489-
in PREPARED state during the crash. Note that commit of the
490-
rollback of a PREPARED trx happens in the recovery thread
491-
while the rollback of other transactions happen in the
492-
background thread. To avoid this race we unconditionally unset
493-
the is_recovered flag. */
494-
is_recovered= false;
483+
trx_mutex_exit(this);
495484
ut_ad(id || !is_referenced());
496485
}
497486

498487
/** Release any explicit locks of a committing transaction. */
499488
inline void trx_t::release_locks()
500489
{
501490
DBUG_ASSERT(state == TRX_STATE_COMMITTED_IN_MEMORY);
491+
DBUG_ASSERT(!is_referenced());
502492

503493
if (UT_LIST_GET_LEN(lock.trx_locks))
504-
lock_trx_release_locks(this);
505-
else
506-
lock.table_locks.clear(); // Work around a bug
494+
{
495+
lock_release(this);
496+
lock.n_rec_locks = 0;
497+
ut_ad(UT_LIST_GET_LEN(lock.trx_locks) == 0);
498+
ut_ad(ib_vector_is_empty(autoinc_locks));
499+
mem_heap_empty(lock.lock_heap);
500+
}
501+
502+
lock.table_locks.clear(); /* outside "if" to work around MDEV-20483 */
507503
}
508504

509505
/** At shutdown, frees a transaction object. */
510506
void
511507
trx_free_at_shutdown(trx_t *trx)
512508
{
513-
trx_mutex_enter(trx);
514509
ut_ad(trx->is_recovered);
515510
ut_a(trx_state_eq(trx, TRX_STATE_PREPARED)
516511
|| trx_state_eq(trx, TRX_STATE_PREPARED_RECOVERED)
@@ -525,7 +520,6 @@ trx_free_at_shutdown(trx_t *trx)
525520
ut_a(trx->magic_n == TRX_MAGIC_N);
526521

527522
trx->commit_state();
528-
trx_mutex_exit(trx);
529523
trx->release_locks();
530524
trx_undo_free_at_shutdown(trx);
531525

@@ -1369,9 +1363,7 @@ trx_commit_in_memory(
13691363
DBUG_LOG("trx", "Autocommit in memory: " << trx);
13701364
trx->state = TRX_STATE_NOT_STARTED;
13711365
} else {
1372-
trx_mutex_enter(trx);
13731366
trx->commit_state();
1374-
trx_mutex_exit(trx);
13751367

13761368
if (trx->id) {
13771369
trx_sys.deregister_rw(trx);
@@ -1397,6 +1389,7 @@ trx_commit_in_memory(
13971389
} else {
13981390
trx_update_mod_tables_timestamp(trx);
13991391
MONITOR_INC(MONITOR_TRX_RW_COMMIT);
1392+
trx->is_recovered = false;
14001393
}
14011394
}
14021395

0 commit comments

Comments
 (0)