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