1 /* Copyright (c) 2014, 2021, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 #ifndef RPL_SLAVE_COMMIT_ORDER_MANAGER
24 #define RPL_SLAVE_COMMIT_ORDER_MANAGER
25 #ifdef HAVE_REPLICATION
26
27 #include "my_global.h"
28 #include "sql_class.h" // THD
29 #include "rpl_rli_pdb.h"
30
31
32 class Commit_order_manager
33 {
34 public:
35 Commit_order_manager(uint32 worker_numbers);
36 ~Commit_order_manager();
37
38 /**
39 Register the worker into commit order queue when coordinator dispatches a
40 transaction to the worker.
41 @param[in] worker The worker which the transaction will be dispatched to.
42 */
43 void register_trx(Slave_worker *worker);
44
45 /**
46 Wait for its turn to commit or unregister.
47
48 @param[in] worker The worker which is executing the transaction.
49 @param[in] all If it is a real transation commit.
50
51 @return
52 @retval false All previous transactions succeed, so this transaction can
53 go ahead and commit.
54 @retval true One or more previous transactions rollback, so this
55 transaction should rollback.
56 */
57 bool wait_for_its_turn(Slave_worker *worker, bool all);
58
59 /**
60 Unregister the transaction from the commit order queue and signal the next
61 one to go ahead.
62
63 @param[in] worker The worker which is executing the transaction.
64 */
65 void unregister_trx(Slave_worker *worker);
66 /**
67 Wait its turn to rollback and report the rollback.
68
69 @param[in] worker The worker which is executing the transaction.
70 */
71 void report_rollback(Slave_worker *worker);
72
73 /**
74 Wait its turn to unregister and unregister the transaction. It is called for
75 the cases that trx is already committed, but nothing is binlogged.
76
77 @param[in] worker The worker which is executing the transaction.
78 */
report_commit(Slave_worker * worker)79 void report_commit(Slave_worker *worker)
80 {
81 wait_for_its_turn(worker, true);
82 unregister_trx(worker);
83 }
84
85 void report_deadlock(Slave_worker *worker);
86 private:
87 enum order_commit_status
88 {
89 OCS_WAIT,
90 OCS_SIGNAL,
91 OCS_FINISH
92 };
93
94 struct worker_info
95 {
96 uint32 next;
97 mysql_cond_t cond;
98 enum order_commit_status status;
99 };
100
101 mysql_mutex_t m_mutex;
102 bool m_rollback_trx;
103
104 /* It stores order commit information of all workers. */
105 std::vector<worker_info> m_workers;
106 /*
107 They are used to construct a transaction queue with trx_info::next together.
108 both head and tail point to a slot of m_trx_vector, when the queue is not
109 empty, otherwise their value are QUEUE_EOF.
110 */
111 uint32 queue_head;
112 uint32 queue_tail;
113 static const uint32 QUEUE_EOF= 0xFFFFFFFF;
queue_empty()114 bool queue_empty() { return queue_head == QUEUE_EOF; }
115
queue_pop()116 void queue_pop()
117 {
118 queue_head= m_workers[queue_head].next;
119 if (queue_head == QUEUE_EOF)
120 queue_tail= QUEUE_EOF;
121 }
122
queue_push(uint32 index)123 void queue_push(uint32 index)
124 {
125 if (queue_head == QUEUE_EOF)
126 queue_head= index;
127 else
128 m_workers[queue_tail].next= index;
129 queue_tail= index;
130 m_workers[index].next= QUEUE_EOF;
131 }
132
queue_front()133 uint32 queue_front() { return queue_head; }
134
135 // Copy constructor is not implemented
136 Commit_order_manager(const Commit_order_manager&);
137 Commit_order_manager& operator=(const Commit_order_manager&);
138 };
139
has_commit_order_manager(THD * thd)140 inline bool has_commit_order_manager(THD *thd)
141 {
142 return is_mts_worker(thd) &&
143 thd->rli_slave->get_commit_order_manager() != NULL;
144 }
145
146 /**
147 Check if order commit deadlock happens.
148
149 Worker1(trx1) Worker2(trx2)
150 ============= =============
151 ... ...
152 Engine acquires lock A
153 ... Engine acquires lock A(waiting for
154 trx1 to release it.
155 COMMIT(waiting for
156 trx2 to commit first).
157
158 Currently, there are two corner cases can cause the deadlock.
159 - Case 1
160 CREATE TABLE t1(c1 INT PRIMARY KEY, c2 INT, INDEX(c2)) ENGINE = InnoDB;
161 INSERT INTO t1 VALUES(1, NULL),(2, 2), (3, NULL), (4, 4), (5, NULL), (6, 6)
162
163 INSERT INTO t1 VALUES(7, NULL);
164 DELETE FROM t1 WHERE c2 <= 3;
165
166 - Case 2
167 ANALYZE TABLE t1;
168 INSERT INTO t2 SELECT * FROM mysql.innodb_table_stats
169
170 Since this is not a real lock deadlock, it could not be handled by engine.
171 slave need to handle it separately.
172 Worker1(trx1) Worker2(trx2)
173 ============= =============
174 ... ...
175 Engine acquires lock A
176 ... Engine acquires lock A.
177 1. found trx1 is holding the lock.)
178 2. report the lock wait to server code by
179 calling thd_report_row_lock_wait().
180 Then this function is called to check
181 if it causes a order commit deadlock.
182 Report the deadlock to worker1.
183 3. waiting for trx1 to release it.
184 COMMIT(waiting for
185 trx2 to commit first).
186 Found the deadlock flag set
187 by worker2 and then
188 return with ER_LOCK_DEADLOCK.
189
190 Rollback the transaction
191 Get lock A and go ahead.
192 ...
193 Retry the transaction
194
195 To conclude, The transaction A which is waiting for transaction B to commit
196 and is holding a lock which is required by transaction B will be rolled back
197 and try again later.
198
199 @param[in] thd_self The THD object of self session which is acquiring
200 a lock hold by another session.
201 @param[in] thd_wait_for The THD object of a session which is holding
202 a lock being acquired by current session.
203 */
commit_order_manager_check_deadlock(THD * thd_self,THD * thd_wait_for)204 inline void commit_order_manager_check_deadlock(THD* thd_self,
205 THD *thd_wait_for)
206 {
207 DBUG_ENTER("commit_order_manager_check_deadlock");
208
209 Slave_worker *self_w= get_thd_worker(thd_self);
210 Slave_worker *wait_for_w= get_thd_worker(thd_wait_for);
211 Commit_order_manager *mngr= self_w->get_commit_order_manager();
212
213 /* Check if both workers are working for the same channel */
214 if (mngr != NULL && self_w->c_rli == wait_for_w->c_rli &&
215 wait_for_w->sequence_number() > self_w->sequence_number())
216 {
217 DBUG_PRINT("info", ("Found slave order commit deadlock"));
218 mngr->report_deadlock(wait_for_w);
219 }
220 DBUG_VOID_RETURN;
221 }
222
223 #endif //HAVE_REPLICATION
224 #endif /*RPL_SLAVE_COMMIT_ORDER_MANAGER*/
225