forked from StoneAtom/StoneDB
225 lines
7.4 KiB
C++
225 lines
7.4 KiB
C++
/* Copyright (c) 2014, 2021, Oracle and/or its affiliates.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL hereby grant you an additional
|
|
permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with MySQL.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
#ifndef RPL_SLAVE_COMMIT_ORDER_MANAGER
|
|
#define RPL_SLAVE_COMMIT_ORDER_MANAGER
|
|
#ifdef HAVE_REPLICATION
|
|
|
|
#include "my_global.h"
|
|
#include "sql_class.h" // THD
|
|
#include "rpl_rli_pdb.h"
|
|
|
|
|
|
class Commit_order_manager
|
|
{
|
|
public:
|
|
Commit_order_manager(uint32 worker_numbers);
|
|
~Commit_order_manager();
|
|
|
|
/**
|
|
Register the worker into commit order queue when coordinator dispatches a
|
|
transaction to the worker.
|
|
@param[in] worker The worker which the transaction will be dispatched to.
|
|
*/
|
|
void register_trx(Slave_worker *worker);
|
|
|
|
/**
|
|
Wait for its turn to commit or unregister.
|
|
|
|
@param[in] worker The worker which is executing the transaction.
|
|
@param[in] all If it is a real transation commit.
|
|
|
|
@return
|
|
@retval false All previous transactions succeed, so this transaction can
|
|
go ahead and commit.
|
|
@retval true One or more previous transactions rollback, so this
|
|
transaction should rollback.
|
|
*/
|
|
bool wait_for_its_turn(Slave_worker *worker, bool all);
|
|
|
|
/**
|
|
Unregister the transaction from the commit order queue and signal the next
|
|
one to go ahead.
|
|
|
|
@param[in] worker The worker which is executing the transaction.
|
|
*/
|
|
void unregister_trx(Slave_worker *worker);
|
|
/**
|
|
Wait its turn to rollback and report the rollback.
|
|
|
|
@param[in] worker The worker which is executing the transaction.
|
|
*/
|
|
void report_rollback(Slave_worker *worker);
|
|
|
|
/**
|
|
Wait its turn to unregister and unregister the transaction. It is called for
|
|
the cases that trx is already committed, but nothing is binlogged.
|
|
|
|
@param[in] worker The worker which is executing the transaction.
|
|
*/
|
|
void report_commit(Slave_worker *worker)
|
|
{
|
|
wait_for_its_turn(worker, true);
|
|
unregister_trx(worker);
|
|
}
|
|
|
|
void report_deadlock(Slave_worker *worker);
|
|
private:
|
|
enum order_commit_status
|
|
{
|
|
OCS_WAIT,
|
|
OCS_SIGNAL,
|
|
OCS_FINISH
|
|
};
|
|
|
|
struct worker_info
|
|
{
|
|
uint32 next;
|
|
mysql_cond_t cond;
|
|
enum order_commit_status status;
|
|
};
|
|
|
|
mysql_mutex_t m_mutex;
|
|
bool m_rollback_trx;
|
|
|
|
/* It stores order commit information of all workers. */
|
|
std::vector<worker_info> m_workers;
|
|
/*
|
|
They are used to construct a transaction queue with trx_info::next together.
|
|
both head and tail point to a slot of m_trx_vector, when the queue is not
|
|
empty, otherwise their value are QUEUE_EOF.
|
|
*/
|
|
uint32 queue_head;
|
|
uint32 queue_tail;
|
|
static const uint32 QUEUE_EOF= 0xFFFFFFFF;
|
|
bool queue_empty() { return queue_head == QUEUE_EOF; }
|
|
|
|
void queue_pop()
|
|
{
|
|
queue_head= m_workers[queue_head].next;
|
|
if (queue_head == QUEUE_EOF)
|
|
queue_tail= QUEUE_EOF;
|
|
}
|
|
|
|
void queue_push(uint32 index)
|
|
{
|
|
if (queue_head == QUEUE_EOF)
|
|
queue_head= index;
|
|
else
|
|
m_workers[queue_tail].next= index;
|
|
queue_tail= index;
|
|
m_workers[index].next= QUEUE_EOF;
|
|
}
|
|
|
|
uint32 queue_front() { return queue_head; }
|
|
|
|
// Copy constructor is not implemented
|
|
Commit_order_manager(const Commit_order_manager&);
|
|
Commit_order_manager& operator=(const Commit_order_manager&);
|
|
};
|
|
|
|
inline bool has_commit_order_manager(THD *thd)
|
|
{
|
|
return is_mts_worker(thd) &&
|
|
thd->rli_slave->get_commit_order_manager() != NULL;
|
|
}
|
|
|
|
/**
|
|
Check if order commit deadlock happens.
|
|
|
|
Worker1(trx1) Worker2(trx2)
|
|
============= =============
|
|
... ...
|
|
Engine acquires lock A
|
|
... Engine acquires lock A(waiting for
|
|
trx1 to release it.
|
|
COMMIT(waiting for
|
|
trx2 to commit first).
|
|
|
|
Currently, there are two corner cases can cause the deadlock.
|
|
- Case 1
|
|
CREATE TABLE t1(c1 INT PRIMARY KEY, c2 INT, INDEX(c2)) ENGINE = InnoDB;
|
|
INSERT INTO t1 VALUES(1, NULL),(2, 2), (3, NULL), (4, 4), (5, NULL), (6, 6)
|
|
|
|
INSERT INTO t1 VALUES(7, NULL);
|
|
DELETE FROM t1 WHERE c2 <= 3;
|
|
|
|
- Case 2
|
|
ANALYZE TABLE t1;
|
|
INSERT INTO t2 SELECT * FROM mysql.innodb_table_stats
|
|
|
|
Since this is not a real lock deadlock, it could not be handled by engine.
|
|
slave need to handle it separately.
|
|
Worker1(trx1) Worker2(trx2)
|
|
============= =============
|
|
... ...
|
|
Engine acquires lock A
|
|
... Engine acquires lock A.
|
|
1. found trx1 is holding the lock.)
|
|
2. report the lock wait to server code by
|
|
calling thd_report_row_lock_wait().
|
|
Then this function is called to check
|
|
if it causes a order commit deadlock.
|
|
Report the deadlock to worker1.
|
|
3. waiting for trx1 to release it.
|
|
COMMIT(waiting for
|
|
trx2 to commit first).
|
|
Found the deadlock flag set
|
|
by worker2 and then
|
|
return with ER_LOCK_DEADLOCK.
|
|
|
|
Rollback the transaction
|
|
Get lock A and go ahead.
|
|
...
|
|
Retry the transaction
|
|
|
|
To conclude, The transaction A which is waiting for transaction B to commit
|
|
and is holding a lock which is required by transaction B will be rolled back
|
|
and try again later.
|
|
|
|
@param[in] thd_self The THD object of self session which is acquiring
|
|
a lock hold by another session.
|
|
@param[in] thd_wait_for The THD object of a session which is holding
|
|
a lock being acquired by current session.
|
|
*/
|
|
inline void commit_order_manager_check_deadlock(THD* thd_self,
|
|
THD *thd_wait_for)
|
|
{
|
|
DBUG_ENTER("commit_order_manager_check_deadlock");
|
|
|
|
Slave_worker *self_w= get_thd_worker(thd_self);
|
|
Slave_worker *wait_for_w= get_thd_worker(thd_wait_for);
|
|
Commit_order_manager *mngr= self_w->get_commit_order_manager();
|
|
|
|
/* Check if both workers are working for the same channel */
|
|
if (mngr != NULL && self_w->c_rli == wait_for_w->c_rli &&
|
|
wait_for_w->sequence_number() > self_w->sequence_number())
|
|
{
|
|
DBUG_PRINT("info", ("Found slave order commit deadlock"));
|
|
mngr->report_deadlock(wait_for_w);
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
#endif //HAVE_REPLICATION
|
|
#endif /*RPL_SLAVE_COMMIT_ORDER_MANAGER*/
|