1 /* Copyright (C) 2007 Google Inc.
2 Copyright (c) 2008 MySQL AB, 2009 Sun Microsystems, Inc.
3 Use is subject to license terms.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; version 2 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
17
18
19 #ifndef SEMISYNC_MASTER_H
20 #define SEMISYNC_MASTER_H
21
22 #include "semisync.h"
23 #include "semisync_master_ack_receiver.h"
24
25 #ifdef HAVE_PSI_INTERFACE
26 extern PSI_mutex_key key_LOCK_rpl_semi_sync_master_enabled;
27 extern PSI_mutex_key key_LOCK_binlog;
28 extern PSI_cond_key key_COND_binlog_send;
29 #endif
30
31 struct Tranx_node {
32 char log_name[FN_REFLEN];
33 my_off_t log_pos;
34 struct Tranx_node *next; /* the next node in the sorted list */
35 struct Tranx_node *hash_next; /* the next node during hash collision */
completion_handler(Handler & h)36 };
37
38 /**
39 @class Tranx_node_allocator
40
41 This class provides memory allocating and freeing methods for
42 Tranx_node. The main target is performance.
do_complete(void * owner,operation * base,const asio::error_code &,std::size_t)43
44 @section ALLOCATE How to allocate a node
45 The pointer of the first node after 'last_node' in current_block is
46 returned. current_block will move to the next free Block when all nodes of
47 it are in use. A new Block is allocated and is put into the rear of the
48 Block link table if no Block is free.
49
50 The list starts up empty (ie, there is no allocated Block).
51
52 After some nodes are freed, there probably are some free nodes before
53 the sequence of the allocated nodes, but we do not reuse it. It is better
54 to keep the allocated nodes are in the sequence, for it is more efficient
55 for allocating and freeing Tranx_node.
56
57 @section FREENODE How to free nodes
58 There are two methods for freeing nodes. They are free_all_nodes and
59 free_nodes_before.
60
61 'A Block is free' means all of its nodes are free.
62 @subsection free_nodes_before
63 As all allocated nodes are in the sequence, 'Before one node' means all
64 nodes before given node in the same Block and all Blocks before the Block
65 which containing the given node. As such, all Blocks before the given one
66 ('node') are free Block and moved into the rear of the Block link table.
67 The Block containing the given 'node', however, is not. For at least the
68 given 'node' is still in use. This will waste at most one Block, but it is
69 more efficient.
70 */
71 #define BLOCK_TRANX_NODES 16
72 class Tranx_node_allocator
73 {
74 public:
75 /**
76 @param reserved_nodes
77 The number of reserved Tranx_nodes. It is used to set 'reserved_blocks'
78 which can contain at least 'reserved_nodes' number of Tranx_nodes. When
79 freeing memory, we will reserve at least reserved_blocks of Blocks not
80 freed.
81 */
82 Tranx_node_allocator(uint reserved_nodes) :
83 reserved_blocks(reserved_nodes/BLOCK_TRANX_NODES +
84 (reserved_nodes%BLOCK_TRANX_NODES > 1 ? 2 : 1)),
85 first_block(NULL), last_block(NULL),
86 current_block(NULL), last_node(-1), block_num(0) {}
87
88 ~Tranx_node_allocator()
89 {
90 Block *block= first_block;
91 while (block != NULL)
92 {
93 Block *next= block->next;
94 free_block(block);
95 block= next;
96 }
97 }
98
99 /**
100 The pointer of the first node after 'last_node' in current_block is
101 returned. current_block will move to the next free Block when all nodes of
102 it are in use. A new Block is allocated and is put into the rear of the
103 Block link table if no Block is free.
104
105 @return Return a Tranx_node *, or NULL if an error occurred.
106 */
107 Tranx_node *allocate_node()
108 {
109 Tranx_node *trx_node;
110 Block *block= current_block;
111
112 if (last_node == BLOCK_TRANX_NODES-1)
113 {
114 current_block= current_block->next;
115 last_node= -1;
116 }
117
118 if (current_block == NULL && allocate_block())
119 {
120 current_block= block;
121 if (current_block)
122 last_node= BLOCK_TRANX_NODES-1;
123 return NULL;
124 }
125
126 trx_node= &(current_block->nodes[++last_node]);
127 trx_node->log_name[0] = '\0';
128 trx_node->log_pos= 0;
129 trx_node->next= 0;
130 trx_node->hash_next= 0;
131 return trx_node;
132 }
133
134 /**
135 All nodes are freed.
136
137 @return Return 0, or 1 if an error occurred.
138 */
139 int free_all_nodes()
140 {
141 current_block= first_block;
142 last_node= -1;
143 free_blocks();
144 return 0;
145 }
146
147 /**
148 All Blocks before the given 'node' are free Block and moved into the rear
149 of the Block link table.
150
151 @param node All nodes before 'node' will be freed
152
153 @return Return 0, or 1 if an error occurred.
154 */
155 int free_nodes_before(Tranx_node* node)
156 {
157 Block *block;
158 Block *prev_block= NULL;
159
160 block= first_block;
161 while (block != current_block->next)
162 {
163 /* Find the Block containing the given node */
164 if (&(block->nodes[0]) <= node && &(block->nodes[BLOCK_TRANX_NODES]) >= node)
165 {
166 /* All Blocks before the given node are put into the rear */
167 if (first_block != block)
168 {
169 last_block->next= first_block;
170 first_block= block;
171 last_block= prev_block;
172 last_block->next= NULL;
173 free_blocks();
174 }
175 return 0;
176 }
177 prev_block= block;
178 block= block->next;
179 }
180
181 /* Node does not find should never happen */
182 DBUG_ASSERT(0);
183 return 1;
184 }
185
186 private:
187 uint reserved_blocks;
188
189 /**
190 A sequence memory which contains BLOCK_TRANX_NODES Tranx_nodes.
191
192 BLOCK_TRANX_NODES The number of Tranx_nodes which are in a Block.
193
194 next Every Block has a 'next' pointer which points to the next Block.
195 These linking Blocks constitute a Block link table.
196 */
197 struct Block {
198 Block *next;
199 Tranx_node nodes[BLOCK_TRANX_NODES];
200 };
201
202 /**
203 The 'first_block' is the head of the Block link table;
204 */
205 Block *first_block;
206 /**
207 The 'last_block' is the rear of the Block link table;
208 */
209 Block *last_block;
210
211 /**
212 current_block always points the Block in the Block link table in
213 which the last allocated node is. The Blocks before it are all in use
214 and the Blocks after it are all free.
215 */
216 Block *current_block;
217
218 /**
219 It always points to the last node which has been allocated in the
220 current_block.
221 */
222 int last_node;
223
224 /**
225 How many Blocks are in the Block link table.
226 */
227 uint block_num;
228
229 /**
230 Allocate a block and then assign it to current_block.
231 */
232 int allocate_block()
233 {
234 Block *block= (Block *)my_malloc(sizeof(Block), MYF(0));
235 if (block)
236 {
237 block->next= NULL;
238
239 if (first_block == NULL)
240 first_block= block;
241 else
242 last_block->next= block;
243
244 /* New Block is always put into the rear */
245 last_block= block;
246 /* New Block is always the current_block */
247 current_block= block;
248 ++block_num;
249 return 0;
250 }
251 return 1;
252 }
253
254 /**
255 Free a given Block.
256 @param block The Block will be freed.
257 */
258 void free_block(Block *block)
259 {
260 my_free(block);
261 --block_num;
262 }
263
264
265 /**
266 If there are some free Blocks and the total number of the Blocks in the
267 Block link table is larger than the 'reserved_blocks', Some free Blocks
268 will be freed until the total number of the Blocks is equal to the
269 'reserved_blocks' or there is only one free Block behind the
270 'current_block'.
271 */
272 void free_blocks()
273 {
274 if (current_block == NULL || current_block->next == NULL)
275 return;
276
277 /* One free Block is always kept behind the current block */
278 Block *block= current_block->next->next;
279 while (block_num > reserved_blocks && block != NULL)
280 {
281 Block *next= block->next;
282 free_block(block);
283 block= next;
284 }
285 current_block->next->next= block;
286 if (block == NULL)
287 last_block= current_block->next;
288 }
289 };
290
291 /**
292 This class manages memory for active transaction list.
293
294 We record each active transaction with a Tranx_node, each session
295 can have only one open transaction. Because of EVENT, the total
296 active transaction nodes can exceed the maximum allowed
297 connections.
298 */
299 class Active_tranx
300 :public Trace {
301 private:
302
303 Tranx_node_allocator m_allocator;
304 /* These two record the active transaction list in sort order. */
305 Tranx_node *m_trx_front, *m_trx_rear;
306
307 Tranx_node **m_trx_htb; /* A hash table on active transactions. */
308
309 int m_num_entries; /* maximum hash table entries */
310 mysql_mutex_t *m_lock; /* mutex lock */
311
312 inline void assert_lock_owner();
313
314 inline unsigned int calc_hash(const unsigned char *key, size_t length);
315 unsigned int get_hash_value(const char *log_file_name, my_off_t log_file_pos);
316
317 int compare(const char *log_file_name1, my_off_t log_file_pos1,
318 const Tranx_node *node2) {
319 return compare(log_file_name1, log_file_pos1,
320 node2->log_name, node2->log_pos);
321 }
322 int compare(const Tranx_node *node1,
323 const char *log_file_name2, my_off_t log_file_pos2) {
324 return compare(node1->log_name, node1->log_pos,
325 log_file_name2, log_file_pos2);
326 }
327 int compare(const Tranx_node *node1, const Tranx_node *node2) {
328 return compare(node1->log_name, node1->log_pos,
329 node2->log_name, node2->log_pos);
330 }
331
332 public:
333 Active_tranx(mysql_mutex_t *lock, unsigned long trace_level);
334 ~Active_tranx();
335
336 /* Insert an active transaction node with the specified position.
337 *
338 * Return:
339 * 0: success; non-zero: error
340 */
341 int insert_tranx_node(const char *log_file_name, my_off_t log_file_pos);
342
343 /* Clear the active transaction nodes until(inclusive) the specified
344 * position.
345 * If log_file_name is NULL, everything will be cleared: the sorted
346 * list and the hash table will be reset to empty.
347 */
348 void clear_active_tranx_nodes(const char *log_file_name,
349 my_off_t log_file_pos);
350
351 /* Given a position, check to see whether the position is an active
352 * transaction's ending position by probing the hash table.
353 */
354 bool is_tranx_end_pos(const char *log_file_name, my_off_t log_file_pos);
355
356 /* Given two binlog positions, compare which one is bigger based on
357 * (file_name, file_position).
358 */
359 static int compare(const char *log_file_name1, my_off_t log_file_pos1,
360 const char *log_file_name2, my_off_t log_file_pos2);
361
362 };
363
364 /**
365 The extension class for the master of semi-synchronous replication
366 */
367 class Repl_semi_sync_master
368 :public Repl_semi_sync_base {
369 Active_tranx *m_active_tranxs; /* active transaction list: the list will
370 be cleared when semi-sync switches off. */
371
372 /* True when init_object has been called */
373 bool m_init_done;
374
375 /* This cond variable is signaled when enough binlog has been sent to slave,
376 * so that a waiting trx can return the 'ok' to the client for a commit.
377 */
378 mysql_cond_t COND_binlog_send;
379
380 /* Mutex that protects the following state variables and the active
381 * transaction list.
382 * Under no cirumstances we can acquire mysql_bin_log.LOCK_log if we are
383 * already holding m_LOCK_binlog because it can cause deadlocks.
384 */
385 mysql_mutex_t LOCK_binlog;
386
387 /* This is set to true when m_reply_file_name contains meaningful data. */
388 bool m_reply_file_name_inited;
389
390 /* The binlog name up to which we have received replies from any slaves. */
391 char m_reply_file_name[FN_REFLEN];
392
393 /* The position in that file up to which we have the reply from any slaves. */
394 my_off_t m_reply_file_pos;
395
396 /* This is set to true when we know the 'smallest' wait position. */
397 bool m_wait_file_name_inited;
398
399 /* NULL, or the 'smallest' filename that a transaction is waiting for
400 * slave replies.
401 */
402 char m_wait_file_name[FN_REFLEN];
403
404 /* The smallest position in that file that a trx is waiting for: the trx
405 * can proceed and send an 'ok' to the client when the master has got the
406 * reply from the slave indicating that it already got the binlog events.
407 */
408 my_off_t m_wait_file_pos;
409
410 /* This is set to true when we know the 'largest' transaction commit
411 * position in the binlog file.
412 * We always maintain the position no matter whether semi-sync is switched
413 * on switched off. When a transaction wait timeout occurs, semi-sync will
414 * switch off. Binlog-dump thread can use the three fields to detect when
415 * slaves catch up on replication so that semi-sync can switch on again.
416 */
417 bool m_commit_file_name_inited;
418
419 /* The 'largest' binlog filename that a commit transaction is seeing. */
420 char m_commit_file_name[FN_REFLEN];
421
422 /* The 'largest' position in that file that a commit transaction is seeing. */
423 my_off_t m_commit_file_pos;
424
425 /* All global variables which can be set by parameters. */
426 volatile bool m_master_enabled; /* semi-sync is enabled on the master */
427 unsigned long m_wait_timeout; /* timeout period(ms) during tranx wait */
428
429 bool m_state; /* whether semi-sync is switched */
430
431 /*Waiting for ACK before/after innodb commit*/
432 ulong m_wait_point;
433
434 void lock();
435 void unlock();
436 void cond_broadcast();
437 int cond_timewait(struct timespec *wait_time);
438
439 /* Is semi-sync replication on? */
440 bool is_on() {
441 return (m_state);
442 }
443
444 void set_master_enabled(bool enabled) {
445 m_master_enabled = enabled;
446 }
447
448 /* Switch semi-sync off because of timeout in transaction waiting. */
449 void switch_off();
450
451 /* Switch semi-sync on when slaves catch up. */
452 int try_switch_on(int server_id,
453 const char *log_file_name, my_off_t log_file_pos);
454
455 public:
456 Repl_semi_sync_master();
457 ~Repl_semi_sync_master() {}
458
459 void cleanup();
460
461 bool get_master_enabled() {
462 return m_master_enabled;
463 }
464 void set_trace_level(unsigned long trace_level) {
465 m_trace_level = trace_level;
466 if (m_active_tranxs)
467 m_active_tranxs->m_trace_level = trace_level;
468 }
469
470 /* Set the transaction wait timeout period, in milliseconds. */
471 void set_wait_timeout(unsigned long wait_timeout) {
472 m_wait_timeout = wait_timeout;
473 }
474
475 /*set the ACK point, after binlog sync or after transaction commit*/
476 void set_wait_point(unsigned long ack_point)
477 {
478 m_wait_point = ack_point;
479 }
480
481 ulong wait_point() //no cover line
482 {
483 return m_wait_point; //no cover line
484 }
485
486 /* Initialize this class after MySQL parameters are initialized. this
487 * function should be called once at bootstrap time.
488 */
489 int init_object();
490
491 /* Enable the object to enable semi-sync replication inside the master. */
492 int enable_master();
493
494 /* Disable the object to disable semi-sync replication inside the master. */
495 void disable_master();
496
497 /* Add a semi-sync replication slave */
498 void add_slave();
499
500 /* Remove a semi-sync replication slave */
501 void remove_slave();
502
503 /* It parses a reply packet and call report_reply_binlog to handle it. */
504 int report_reply_packet(uint32 server_id, const uchar *packet,
505 ulong packet_len);
506
507 /* In semi-sync replication, reports up to which binlog position we have
508 * received replies from the slave indicating that it already get the events.
509 *
510 * Input:
511 * server_id - (IN) master server id number
512 * log_file_name - (IN) binlog file name
513 * end_offset - (IN) the offset in the binlog file up to which we have
514 * the replies from the slave
515 *
516 * Return:
517 * 0: success; non-zero: error
518 */
519 int report_reply_binlog(uint32 server_id,
520 const char* log_file_name,
521 my_off_t end_offset);
522
523 /* Commit a transaction in the final step. This function is called from
524 * InnoDB before returning from the low commit. If semi-sync is switch on,
525 * the function will wait to see whether binlog-dump thread get the reply for
526 * the events of the transaction. Remember that this is not a direct wait,
527 * instead, it waits to see whether the binlog-dump thread has reached the
528 * point. If the wait times out, semi-sync status will be switched off and
529 * all other transaction would not wait either.
530 *
531 * Input: (the transaction events' ending binlog position)
532 * trx_wait_binlog_name - (IN) ending position's file name
533 * trx_wait_binlog_pos - (IN) ending position's file offset
534 *
535 * Return:
536 * 0: success; non-zero: error
537 */
538 int commit_trx(const char* trx_wait_binlog_name,
539 my_off_t trx_wait_binlog_pos);
540
541 /*Wait for ACK after writing/sync binlog to file*/
542 int wait_after_sync(const char* log_file, my_off_t log_pos);
543
544 /*Wait for ACK after commting the transaction*/
545 int wait_after_commit(THD* thd, bool all);
546
547 /*Wait after the transaction is rollback*/
548 int wait_after_rollback(THD *thd, bool all);
549 /*Store the current binlog position in m_active_tranxs. This position should
550 * be acked by slave*/
551 int report_binlog_update(THD *thd, const char *log_file,my_off_t log_pos);
552
553 int dump_start(THD* thd,
554 const char *log_file,
555 my_off_t log_pos);
556
557 void dump_end(THD* thd);
558
559 /* Reserve space in the replication event packet header:
560 * . slave semi-sync off: 1 byte - (0)
561 * . slave semi-sync on: 3 byte - (0, 0xef, 0/1}
562 *
563 * Input:
564 * packet - (IN) the header buffer
565 *
566 * Return:
567 * size of the bytes reserved for header
568 */
569 int reserve_sync_header(String* packet);
570
571 /* Update the sync bit in the packet header to indicate to the slave whether
572 * the master will wait for the reply of the event. If semi-sync is switched
573 * off and we detect that the slave is catching up, we switch semi-sync on.
574 *
575 * Input:
576 * THD - (IN) current dump thread
577 * packet - (IN) the packet containing the replication event
578 * log_file_name - (IN) the event ending position's file name
579 * log_file_pos - (IN) the event ending position's file offset
580 * need_sync - (IN) identify if flush_net is needed to call.
581 * server_id - (IN) master server id number
582 *
583 * Return:
584 * 0: success; non-zero: error
585 */
586 int update_sync_header(THD* thd, unsigned char *packet,
587 const char *log_file_name,
588 my_off_t log_file_pos,
589 bool* need_sync);
590
591 /* Called when a transaction finished writing binlog events.
592 * . update the 'largest' transactions' binlog event position
593 * . insert the ending position in the active transaction list if
594 * semi-sync is on
595 *
596 * Input: (the transaction events' ending binlog position)
597 * log_file_name - (IN) transaction ending position's file name
598 * log_file_pos - (IN) transaction ending position's file offset
599 *
600 * Return:
601 * 0: success; non-zero: error
602 */
603 int write_tranx_in_binlog(const char* log_file_name, my_off_t log_file_pos);
604
605 /* Read the slave's reply so that we know how much progress the slave makes
606 * on receive replication events.
607 */
608 int flush_net(THD* thd, const char *event_buf);
609
610 /* Export internal statistics for semi-sync replication. */
611 void set_export_stats();
612
613 /* 'reset master' command is issued from the user and semi-sync need to
614 * go off for that.
615 */
616 int after_reset_master();
617
618 /*called before reset master*/
619 int before_reset_master();
620
621 void check_and_switch();
622
623 mysql_mutex_t LOCK_rpl_semi_sync_master_enabled;
624 };
625
626 enum rpl_semi_sync_master_wait_point_t {
627 SEMI_SYNC_MASTER_WAIT_POINT_AFTER_BINLOG_SYNC,
628 SEMI_SYNC_MASTER_WAIT_POINT_AFTER_STORAGE_COMMIT,
629 };
630
631 extern Repl_semi_sync_master repl_semisync_master;
632 extern Ack_receiver ack_receiver;
633
634 /* System and status variables for the master component */
635 extern my_bool rpl_semi_sync_master_enabled;
636 extern my_bool rpl_semi_sync_master_status;
637 extern ulong rpl_semi_sync_master_wait_point;
638 extern ulong rpl_semi_sync_master_clients;
639 extern ulong rpl_semi_sync_master_timeout;
640 extern ulong rpl_semi_sync_master_trace_level;
641 extern ulong rpl_semi_sync_master_yes_transactions;
642 extern ulong rpl_semi_sync_master_no_transactions;
643 extern ulong rpl_semi_sync_master_off_times;
644 extern ulong rpl_semi_sync_master_wait_timeouts;
645 extern ulong rpl_semi_sync_master_timefunc_fails;
646 extern ulong rpl_semi_sync_master_num_timeouts;
647 extern ulong rpl_semi_sync_master_wait_sessions;
648 extern ulong rpl_semi_sync_master_wait_pos_backtraverse;
649 extern ulong rpl_semi_sync_master_avg_trx_wait_time;
650 extern ulong rpl_semi_sync_master_avg_net_wait_time;
651 extern ulonglong rpl_semi_sync_master_net_wait_num;
652 extern ulonglong rpl_semi_sync_master_trx_wait_num;
653 extern ulonglong rpl_semi_sync_master_net_wait_time;
654 extern ulonglong rpl_semi_sync_master_trx_wait_time;
655 extern unsigned long long rpl_semi_sync_master_request_ack;
656 extern unsigned long long rpl_semi_sync_master_get_ack;
657
658 /*
659 This indicates whether we should keep waiting if no semi-sync slave
660 is available.
661 0 : stop waiting if detected no avaialable semi-sync slave.
662 1 (default) : keep waiting until timeout even no available semi-sync slave.
663 */
664 extern char rpl_semi_sync_master_wait_no_slave;
665 extern Repl_semi_sync_master repl_semisync_master;
666
667 extern PSI_stage_info stage_waiting_for_semi_sync_ack_from_slave;
668 extern PSI_stage_info stage_reading_semi_sync_ack;
669 extern PSI_stage_info stage_waiting_for_semi_sync_slave;
670
671 void semi_sync_master_deinit();
672
673 #endif /* SEMISYNC_MASTER_H */
674