Skip to content

Commit e9fea31

Browse files
Bill QuBill Qu
authored andcommitted
bug#16271657 3-WAY DEADLOCK ON SEMISYNC REPLICATION AND BINLOG ROTATE
Bug#16491597 DEADLOCK WITH GROUP COMMIT Merge from mysql-5.6
2 parents 11db2d1 + 12287af commit e9fea31

File tree

12 files changed

+223
-67
lines changed

12 files changed

+223
-67
lines changed

sql/binlog.cc

Lines changed: 128 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,14 @@ class Thread_excursion
163163
*/
164164
int attach_to(THD *thd)
165165
{
166+
/*
167+
Simulate session attach error.
168+
*/
169+
DBUG_EXECUTE_IF("simulate_session_attach_error",
170+
{
171+
if (rand() % 3 == 0)
172+
return 1;
173+
};);
166174
#ifdef HAVE_PSI_THREAD_INTERFACE
167175
PSI_THREAD_CALL(set_thread)(thd_get_psi(thd));
168176
#endif /* HAVE_PSI_THREAD_INTERFACE */
@@ -2347,6 +2355,7 @@ void MYSQL_BIN_LOG::cleanup()
23472355
mysql_mutex_destroy(&LOCK_index);
23482356
mysql_mutex_destroy(&LOCK_commit);
23492357
mysql_mutex_destroy(&LOCK_sync);
2358+
mysql_mutex_destroy(&LOCK_xids);
23502359
mysql_cond_destroy(&update_cond);
23512360
my_atomic_rwlock_destroy(&m_prep_xids_lock);
23522361
mysql_cond_destroy(&m_prep_xids_cond);
@@ -2364,6 +2373,7 @@ void MYSQL_BIN_LOG::init_pthread_objects()
23642373
mysql_mutex_init(m_key_LOCK_index, &LOCK_index, MY_MUTEX_INIT_SLOW);
23652374
mysql_mutex_init(m_key_LOCK_commit, &LOCK_commit, MY_MUTEX_INIT_FAST);
23662375
mysql_mutex_init(m_key_LOCK_sync, &LOCK_sync, MY_MUTEX_INIT_FAST);
2376+
mysql_mutex_init(m_key_LOCK_xids, &LOCK_xids, MY_MUTEX_INIT_FAST);
23672377
mysql_cond_init(m_key_update_cond, &update_cond, 0);
23682378
my_atomic_rwlock_init(&m_prep_xids_lock);
23692379
mysql_cond_init(m_key_prep_xids_cond, &m_prep_xids_cond, NULL);
@@ -4809,25 +4819,27 @@ int MYSQL_BIN_LOG::new_file_impl(bool need_lock_log, Format_description_log_even
48094819
mysql_mutex_lock(&LOCK_log);
48104820
else
48114821
mysql_mutex_assert_owner(&LOCK_log);
4812-
mysql_mutex_lock(&LOCK_commit);
4822+
DBUG_EXECUTE_IF("semi_sync_3-way_deadlock",
4823+
DEBUG_SYNC(current_thd, "before_rotate_binlog"););
4824+
mysql_mutex_lock(&LOCK_xids);
48134825
/*
48144826
We need to ensure that the number of prepared XIDs are 0.
48154827
48164828
If m_prep_xids is not zero:
4817-
- We release the LOCK_commit lock to allow sessions to commit,
4818-
hence decrease m_prep_xids
4829+
- We wait for storage engine commit, hence decrease m_prep_xids
48194830
- We keep the LOCK_log to block new transactions from being
48204831
written to the binary log.
48214832
*/
48224833
while (get_prep_xids() > 0)
4823-
mysql_cond_wait(&m_prep_xids_cond, &LOCK_commit);
4834+
mysql_cond_wait(&m_prep_xids_cond, &LOCK_xids);
4835+
mysql_mutex_unlock(&LOCK_xids);
4836+
48244837
mysql_mutex_lock(&LOCK_index);
48254838

48264839
if ((error= ha_flush_logs(0)))
48274840
goto end;
48284841

48294842
mysql_mutex_assert_owner(&LOCK_log);
4830-
mysql_mutex_assert_owner(&LOCK_commit);
48314843
mysql_mutex_assert_owner(&LOCK_index);
48324844

48334845
/* Reuse old name if not binlog and not update log */
@@ -4950,7 +4962,6 @@ int MYSQL_BIN_LOG::new_file_impl(bool need_lock_log, Format_description_log_even
49504962
}
49514963

49524964
mysql_mutex_unlock(&LOCK_index);
4953-
mysql_mutex_unlock(&LOCK_commit);
49544965
if (need_lock_log)
49554966
mysql_mutex_unlock(&LOCK_log);
49564967

@@ -5805,6 +5816,15 @@ bool MYSQL_BIN_LOG::write_cache(THD *thd, binlog_cache_data *cache_data)
58055816
IO_CACHE *cache= &cache_data->cache_log;
58065817
bool incident= cache_data->has_incident();
58075818

5819+
DBUG_EXECUTE_IF("simulate_binlog_flush_error",
5820+
{
5821+
if (rand() % 3 == 0)
5822+
{
5823+
write_error=1;
5824+
goto err;
5825+
}
5826+
};);
5827+
58085828
mysql_mutex_assert_owner(&LOCK_log);
58095829

58105830
DBUG_ASSERT(is_open());
@@ -5865,6 +5885,8 @@ bool MYSQL_BIN_LOG::write_cache(THD *thd, binlog_cache_data *cache_data)
58655885
sql_print_error(ER(ER_ERROR_ON_WRITE), name,
58665886
errno, my_strerror(errbuf, sizeof(errbuf), errno));
58675887
}
5888+
thd->commit_error= THD::CE_FLUSH_ERROR;
5889+
58685890
DBUG_RETURN(1);
58695891
}
58705892

@@ -6444,10 +6466,7 @@ MYSQL_BIN_LOG::flush_thread_caches(THD *thd)
64446466
*/
64456467
thd->set_trans_pos(log_file_name, my_b_tell(&log_file));
64466468
if (wrote_xid)
6447-
{
6448-
inc_prep_xids();
6449-
thd->transaction.flags.xid_written= true;
6450-
}
6469+
inc_prep_xids(thd);
64516470
}
64526471
DBUG_PRINT("debug", ("bytes: %llu", bytes));
64536472
return std::make_pair(error, bytes);
@@ -6474,7 +6493,7 @@ MYSQL_BIN_LOG::process_flush_stage_queue(my_off_t *total_bytes_var,
64746493
{
64756494
DBUG_ASSERT(total_bytes_var && rotate_var && out_queue_var);
64766495
my_off_t total_bytes= 0;
6477-
int flush_error= 0;
6496+
int flush_error= 1;
64786497
mysql_mutex_assert_owner(&LOCK_log);
64796498

64806499
my_atomic_rwlock_rdlock(&opt_binlog_max_flush_queue_time_lock);
@@ -6497,7 +6516,7 @@ MYSQL_BIN_LOG::process_flush_stage_queue(my_off_t *total_bytes_var,
64976516
std::pair<int,my_off_t> result= flush_thread_caches(current.second);
64986517
has_more= current.first;
64996518
total_bytes+= result.second;
6500-
if (flush_error == 0)
6519+
if (flush_error == 1)
65016520
flush_error= result.first;
65026521
if (first_seen == NULL)
65036522
first_seen= current.second;
@@ -6515,7 +6534,7 @@ MYSQL_BIN_LOG::process_flush_stage_queue(my_off_t *total_bytes_var,
65156534
{
65166535
std::pair<int,my_off_t> result= flush_thread_caches(head);
65176536
total_bytes+= result.second;
6518-
if (flush_error == 0)
6537+
if (flush_error == 1)
65196538
flush_error= result.first;
65206539
}
65216540
if (first_seen == NULL)
@@ -6542,12 +6561,10 @@ MYSQL_BIN_LOG::process_flush_stage_queue(my_off_t *total_bytes_var,
65426561
65436562
@param thd The "master" thread
65446563
@param first First thread in the queue of threads to commit
6545-
@param flush_error Error code from flush operation.
65466564
*/
65476565

65486566
void
6549-
MYSQL_BIN_LOG::process_commit_stage_queue(THD *thd, THD *first,
6550-
int flush_error)
6567+
MYSQL_BIN_LOG::process_commit_stage_queue(THD *thd, THD *first)
65516568
{
65526569
mysql_mutex_assert_owner(&LOCK_commit);
65536570
Thread_excursion excursion(thd);
@@ -6570,27 +6587,75 @@ MYSQL_BIN_LOG::process_commit_stage_queue(THD *thd, THD *first,
65706587
#ifndef DBUG_OFF
65716588
stage_manager.clear_preempt_status(head);
65726589
#endif
6573-
if (flush_error != 0)
6574-
head->commit_error= flush_error;
6575-
else if (int error= excursion.attach_to(head))
6576-
head->commit_error= error;
6590+
if (head->commit_error != THD::CE_NONE)
6591+
;
6592+
else if (excursion.attach_to(head))
6593+
{
6594+
head->commit_error= THD::CE_COMMIT_ERROR;
6595+
sql_print_error("Out of memory while attaching to session thread "
6596+
"during the group commit phase.");
6597+
}
65776598
else
65786599
{
65796600
bool all= head->transaction.flags.real_commit;
65806601
if (head->transaction.flags.commit_low)
65816602
{
65826603
/* head is parked to have exited append() */
65836604
DBUG_ASSERT(head->transaction.flags.ready_preempt);
6584-
6585-
if (int error= ha_commit_low(head, all))
6586-
head->commit_error= error;
6587-
else if (head->transaction.flags.xid_written)
6588-
dec_prep_xids();
6605+
/*
6606+
storage engine commit
6607+
*/
6608+
if (ha_commit_low(head, all, false))
6609+
head->commit_error= THD::CE_COMMIT_ERROR;
65896610
}
65906611
DBUG_PRINT("debug", ("commit_error: %d, flags.pending: %s",
65916612
head->commit_error,
65926613
YESNO(head->transaction.flags.pending)));
65936614
}
6615+
/*
6616+
Decrement the prepared XID counter after storage engine commit.
6617+
We also need decrement the prepared XID when encountering a
6618+
flush error or session attach error for avoiding 3-way deadlock
6619+
among user thread, rotate thread and dump thread.
6620+
*/
6621+
if (head->transaction.flags.xid_written)
6622+
dec_prep_xids(head);
6623+
}
6624+
}
6625+
6626+
/**
6627+
Process after commit for a sequence of sessions.
6628+
6629+
@param thd The "master" thread
6630+
@param first First thread in the queue of threads to commit
6631+
*/
6632+
6633+
void
6634+
MYSQL_BIN_LOG::process_after_commit_stage_queue(THD *thd, THD *first)
6635+
{
6636+
Thread_excursion excursion(thd);
6637+
for (THD *head= first; head; head= head->next_to_commit)
6638+
{
6639+
if (head->transaction.flags.run_hooks &&
6640+
head->commit_error == THD::CE_NONE)
6641+
{
6642+
if (excursion.attach_to(head))
6643+
{
6644+
head->commit_error= THD::CE_COMMIT_ERROR;
6645+
sql_print_error("Out of memory while attaching to session thread "
6646+
"during the group commit phase.");
6647+
}
6648+
if (head->commit_error == THD::CE_NONE)
6649+
{
6650+
bool all= head->transaction.flags.real_commit;
6651+
(void) RUN_HOOK(transaction, after_commit, (head, all));
6652+
/*
6653+
When after_commit finished for the transaction, clear the run_hooks flag.
6654+
This allow other parts of the system to check if after_commit was called.
6655+
*/
6656+
head->transaction.flags.run_hooks= false;
6657+
}
6658+
}
65946659
}
65956660
}
65966661

@@ -6719,13 +6784,31 @@ MYSQL_BIN_LOG::sync_binlog_file(bool force)
67196784
int
67206785
MYSQL_BIN_LOG::finish_commit(THD *thd)
67216786
{
6722-
if (thd->commit_error == 0 && thd->transaction.flags.commit_low)
6787+
if (thd->transaction.flags.commit_low)
67236788
{
67246789
const bool all= thd->transaction.flags.real_commit;
6725-
thd->commit_error= ha_commit_low(thd, all);
6790+
/*
6791+
storage engine commit
6792+
*/
6793+
if (thd->commit_error == THD::CE_NONE &&
6794+
ha_commit_low(thd, all, false))
6795+
thd->commit_error= THD::CE_COMMIT_ERROR;
6796+
/*
6797+
Decrement the prepared XID counter after storage engine commit
6798+
*/
67266799
if (thd->transaction.flags.xid_written)
6727-
dec_prep_xids();
6800+
dec_prep_xids(thd);
6801+
/*
6802+
If commit succeeded, we call the after_commit hook
6803+
*/
6804+
if (thd->commit_error == THD::CE_NONE)
6805+
{
6806+
(void) RUN_HOOK(transaction, after_commit, (thd, all));
6807+
thd->transaction.flags.run_hooks= false;
6808+
}
67286809
}
6810+
else if (thd->transaction.flags.xid_written)
6811+
dec_prep_xids(thd);
67296812

67306813
thd->variables.gtid_next.set_undefined();
67316814
/*
@@ -6736,7 +6819,7 @@ MYSQL_BIN_LOG::finish_commit(THD *thd)
67366819
gtid_state->update_on_commit(thd);
67376820
global_sid_lock->unlock();
67386821

6739-
DBUG_ASSERT(thd->commit_error || !thd->transaction.flags.commit_low);
6822+
DBUG_ASSERT(thd->commit_error || !thd->transaction.flags.run_hooks);
67406823
DBUG_ASSERT(!thd_get_cache_mngr(thd)->dbug_any_finalized());
67416824
DBUG_PRINT("return", ("Thread ID: %lu, commit_error: %d",
67426825
thd->thread_id, thd->commit_error));
@@ -6814,12 +6897,13 @@ int MYSQL_BIN_LOG::ordered_commit(THD *thd, bool all, bool skip_commit)
68146897
ha_commit_low since that calls st_transaction::cleanup.
68156898
*/
68166899
thd->transaction.flags.pending= true;
6817-
thd->commit_error= 0;
6900+
thd->commit_error= THD::CE_NONE;
68186901
thd->next_to_commit= NULL;
68196902
thd->durability_property= HA_IGNORE_DURABILITY;
68206903
thd->transaction.flags.real_commit= all;
68216904
thd->transaction.flags.xid_written= false;
68226905
thd->transaction.flags.commit_low= !skip_commit;
6906+
thd->transaction.flags.run_hooks= !skip_commit;
68236907
#ifndef DBUG_OFF
68246908
/*
68256909
The group commit Leader may have to wait for follower whose transaction
@@ -6914,8 +6998,15 @@ int MYSQL_BIN_LOG::ordered_commit(THD *thd, bool all, bool skip_commit)
69146998
DBUG_RETURN(finish_commit(thd));
69156999
}
69167000
THD *commit_queue= stage_manager.fetch_queue_for(Stage_manager::COMMIT_STAGE);
6917-
process_commit_stage_queue(thd, commit_queue, flush_error);
7001+
DBUG_EXECUTE_IF("semi_sync_3-way_deadlock",
7002+
DEBUG_SYNC(thd, "before_process_commit_stage_queue"););
7003+
process_commit_stage_queue(thd, commit_queue);
69187004
mysql_mutex_unlock(&LOCK_commit);
7005+
/*
7006+
Process after_commit after LOCK_commit is released for avoiding
7007+
3-way deadlock among user thread, rotate thread and dump thread.
7008+
*/
7009+
process_after_commit_stage_queue(thd, commit_queue);
69197010
final_queue= commit_queue;
69207011
}
69217012
else
@@ -6932,20 +7023,17 @@ int MYSQL_BIN_LOG::ordered_commit(THD *thd, bool all, bool skip_commit)
69327023
(void) finish_commit(thd);
69337024

69347025
/*
6935-
If we need to rotate, we do it now.
7026+
If we need to rotate, we do it without commit error.
7027+
Otherwise the thd->commit_error will be possibly reset.
69367028
*/
6937-
if (do_rotate)
7029+
if (do_rotate && thd->commit_error == THD::CE_NONE)
69387030
{
69397031
/*
6940-
We can force the rotate since we did the check in
6941-
flush_session_queue(). Giving "false" would have the same
6942-
result, but will do the check again.
7032+
Do not force the rotate as several consecutive groups may
7033+
request unnecessary rotations.
69437034
69447035
NOTE: Run purge_logs wo/ holding LOCK_log because it does not
69457036
need the mutex. Otherwise causes various deadlocks.
6946-
6947-
NOTE: The LOCK_commit is necessary when doing a rotate, but that
6948-
is grabbed inside new_file_impl().
69497037
*/
69507038

69517039
DBUG_EXECUTE_IF("crash_commit_before_unlog", DBUG_SUICIDE(););
@@ -6958,7 +7046,7 @@ int MYSQL_BIN_LOG::ordered_commit(THD *thd, bool all, bool skip_commit)
69587046
if (!error && check_purge)
69597047
purge();
69607048
else
6961-
thd->commit_error= error;
7049+
thd->commit_error= THD::CE_COMMIT_ERROR;
69627050
}
69637051
DBUG_RETURN(thd->commit_error);
69647052
}

0 commit comments

Comments
 (0)