From: Li-Bing.Song Date: January 29 2010 6:39am Subject: bzr commit into mysql-5.1-rep-semisync branch (Li-Bing.Song:3127) Bug#50157 List-Archive: http://lists.mysql.com/commits/98535 X-Bug: 50157 Message-Id: <201001290640.o0T6eYV3014705@anders-server> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============8734493574605572427==" --===============8734493574605572427== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///home/anders/work/bzrroot/mysql-5.1-rep-semisync/ based on revid:zhenxing.he@stripped 3127 Li-Bing.Song@stripped 2010-01-29 BUG#50157 Assertion !active_tranxs_->is_tranx_end_pos(..) in ReplSemiSyncMaster::commitTrx The root cause of the crash is that a TranxNode is freed before it is used. A TranxNode is allocated and insertted into the active list each time a log events is written and flushed into the binlog file. The memory for TranxNode is allocated with thd_alloc and will be freed at the end of the statement. The after_commit/after_rollback callback was supposed to be called before the end of each statement and remove the node from the active list. However this assumption is not correct in all cases(e.g. call 'CREATE TEMPORARY TABLE myisam_t SELECT * FROM innodb_t' in a transaction and delete all temporary tables automatically when a session closed), and can cause the memory allocated for TranxNode be freed before it was removed from the active list. So The TranxNode pointer in the active list would become a wild pointer and cause the crash. After this patch, We had a class called a TranxNodeAllocate which manages the memory for allocating and freeing TranxNode. It uses my_malloc to allocate memory. @ sql/rpl_handler.cc params are not initialized. modified: mysql-test/suite/rpl/r/rpl_semi_sync.result mysql-test/suite/rpl/t/rpl_semi_sync.test plugin/semisync/semisync_master.cc plugin/semisync/semisync_master.h sql/rpl_handler.cc === modified file 'mysql-test/suite/rpl/r/rpl_semi_sync.result' --- a/mysql-test/suite/rpl/r/rpl_semi_sync.result 2009-10-23 04:56:30 +0000 +++ b/mysql-test/suite/rpl/r/rpl_semi_sync.result 2010-01-29 06:39:23 +0000 @@ -120,8 +120,27 @@ min(a) select max(a) from t1; max(a) 300 + +# BUG#50157 +# semi-sync replication crashes when replicating a transaction which +# include 'CREATE TEMPORARY TABLE `MyISAM_t` SELECT * FROM `Innodb_t` ; +[ on master ] +SET SESSION AUTOCOMMIT= 0; +CREATE TABLE t2(c1 INT) ENGINE=innodb; +BEGIN; + +# Even though it is in a transaction, this statement is binlogged into binlog +# file immediately. +CREATE TEMPORARY TABLE t3 SELECT c1 FROM t2 where 1=1; + +# These statements will not be binlogged until the transaction is committed +INSERT INTO t2 VALUES(11); +INSERT INTO t2 VALUES(22); +COMMIT; +DROP TABLE t2, t3; +SET SESSION AUTOCOMMIT= 1; # -# Test semi-sync master will switch OFF after one transacton +# Test semi-sync master will switch OFF after one transaction # timeout waiting for slave reply. # include/stop_slave.inc @@ -135,7 +154,7 @@ Variable_name Value Rpl_semi_sync_master_no_tx 0 show status like 'Rpl_semi_sync_master_yes_tx'; Variable_name Value -Rpl_semi_sync_master_yes_tx 301 +Rpl_semi_sync_master_yes_tx 304 show status like 'Rpl_semi_sync_master_clients'; Variable_name Value Rpl_semi_sync_master_clients 1 @@ -150,7 +169,7 @@ Variable_name Value Rpl_semi_sync_master_no_tx 1 show status like 'Rpl_semi_sync_master_yes_tx'; Variable_name Value -Rpl_semi_sync_master_yes_tx 301 +Rpl_semi_sync_master_yes_tx 304 insert into t1 values (100); [ master status should be OFF ] show status like 'Rpl_semi_sync_master_status'; @@ -161,7 +180,7 @@ Variable_name Value Rpl_semi_sync_master_no_tx 302 show status like 'Rpl_semi_sync_master_yes_tx'; Variable_name Value -Rpl_semi_sync_master_yes_tx 301 +Rpl_semi_sync_master_yes_tx 304 # # Test semi-sync status on master will be ON again when slave catches up # @@ -194,7 +213,7 @@ Variable_name Value Rpl_semi_sync_master_no_tx 302 show status like 'Rpl_semi_sync_master_yes_tx'; Variable_name Value -Rpl_semi_sync_master_yes_tx 301 +Rpl_semi_sync_master_yes_tx 304 show status like 'Rpl_semi_sync_master_clients'; Variable_name Value Rpl_semi_sync_master_clients 1 @@ -213,7 +232,7 @@ Variable_name Value Rpl_semi_sync_master_no_tx 302 SHOW STATUS LIKE 'Rpl_semi_sync_master_yes_tx'; Variable_name Value -Rpl_semi_sync_master_yes_tx 302 +Rpl_semi_sync_master_yes_tx 305 FLUSH NO_WRITE_TO_BINLOG STATUS; [ Semi-sync master status variables after FLUSH STATUS ] SHOW STATUS LIKE 'Rpl_semi_sync_master_no_tx'; === modified file 'mysql-test/suite/rpl/t/rpl_semi_sync.test' --- a/mysql-test/suite/rpl/t/rpl_semi_sync.test 2009-10-23 04:56:30 +0000 +++ b/mysql-test/suite/rpl/t/rpl_semi_sync.test 2010-01-29 06:39:23 +0000 @@ -193,8 +193,36 @@ select count(distinct a) from t1; select min(a) from t1; select max(a) from t1; +--echo +--echo # BUG#50157 +--echo # semi-sync replication crashes when replicating a transaction which +--echo # include 'CREATE TEMPORARY TABLE `MyISAM_t` SELECT * FROM `Innodb_t` ; + +connection master; +echo [ on master ]; +SET SESSION AUTOCOMMIT= 0; +CREATE TABLE t2(c1 INT) ENGINE=innodb; +sync_slave_with_master; + +connection master; +BEGIN; +--echo +--echo # Even though it is in a transaction, this statement is binlogged into binlog +--echo # file immediately. +CREATE TEMPORARY TABLE t3 SELECT c1 FROM t2 where 1=1; +--echo +--echo # These statements will not be binlogged until the transaction is committed +INSERT INTO t2 VALUES(11); +INSERT INTO t2 VALUES(22); +COMMIT; + +DROP TABLE t2, t3; +SET SESSION AUTOCOMMIT= 1; +sync_slave_with_master; + + --echo # ---echo # Test semi-sync master will switch OFF after one transacton +--echo # Test semi-sync master will switch OFF after one transaction --echo # timeout waiting for slave reply. --echo # connection slave; === modified file 'plugin/semisync/semisync_master.cc' --- a/plugin/semisync/semisync_master.cc 2009-12-04 05:43:38 +0000 +++ b/plugin/semisync/semisync_master.cc 2010-01-29 06:39:23 +0000 @@ -65,7 +65,7 @@ static int gettimeofday(struct timeval * ActiveTranx::ActiveTranx(pthread_mutex_t *lock, unsigned long trace_level) - : Trace(trace_level), + : Trace(trace_level), allocator_(max_connections), num_entries_(max_connections << 1), /* Transaction hash table size * is set to double the size * of max_connections */ @@ -115,25 +115,6 @@ unsigned int ActiveTranx::get_hash_value return (hash1 + hash2) % num_entries_; } -ActiveTranx::TranxNode* ActiveTranx::alloc_tranx_node() -{ - MYSQL_THD thd= (MYSQL_THD)current_thd; - /* The memory allocated for TranxNode will be automatically freed at - the end of the command of current THD. And because - ha_autocommit_or_rollback() will always be called before that, so - we are sure that the node will be removed from the active list - before it get freed. */ - TranxNode *trx_node = (TranxNode *)thd_alloc(thd, sizeof(TranxNode)); - if (trx_node) - { - trx_node->log_name_[0] = '\0'; - trx_node->log_pos_= 0; - trx_node->next_= 0; - trx_node->hash_next_= 0; - } - return trx_node; -} - int ActiveTranx::compare(const char *log_file_name1, my_off_t log_file_pos1, const char *log_file_name2, my_off_t log_file_pos2) { @@ -159,7 +140,7 @@ int ActiveTranx::insert_tranx_node(const function_enter(kWho); - ins_node = alloc_tranx_node(); + ins_node = allocator_.allocate_node(); if (!ins_node) { sql_print_error("%s: transaction node allocation failed for: (%s, %lu)", @@ -271,6 +252,7 @@ int ActiveTranx::clear_active_tranx_node /* Clear the hash table. */ memset(trx_htb_, 0, num_entries_ * sizeof(TranxNode *)); + allocator_.free_all_nodes(); /* Clear the active transaction list. */ if (trx_front_ != NULL) @@ -311,6 +293,7 @@ int ActiveTranx::clear_active_tranx_node } trx_front_ = new_front; + allocator_.free_nodes_before(trx_front_); if (trace_level_ & kTraceDetail) sql_print_information("%s: cleared %d nodes back until pos (%s, %lu)", === modified file 'plugin/semisync/semisync_master.h' --- a/plugin/semisync/semisync_master.h 2009-12-04 01:46:33 +0000 +++ b/plugin/semisync/semisync_master.h 2010-01-29 06:39:23 +0000 @@ -20,6 +20,247 @@ #include "semisync.h" +struct TranxNode { + char log_name_[FN_REFLEN]; + my_off_t log_pos_; + struct TranxNode *next_; /* the next node in the sorted list */ + struct TranxNode *hash_next_; /* the next node during hash collision */ +}; + +/** + @class TranxNodeAllocator + + This class provides memory allocating and freeing methods for + TranxNode. The main target is performance. + +
+ A sequence memory which contains BLOCK_TRANX_NODES TranxNodes. + +
+ The number of TranxNodes which are in a Block. + +
+ Every Block has a 'next' pointer which points to the next Block. These + linking Blocks constitut a Block link table. The 'first_block' is the + head, and the 'last_block' is the rear. + +
+ current_block is always points the Block in the Block link table in + which the last allocated node is. The Blocks before it are all in use + and the Blocks after it are all free. + +
+ It always points to the last node which has been allocated in the + current_block. + +
+ The pointer of the first node after 'last_node' in current_block is + returned. current_block will move to the next free Block when all nodes of + it are in use. A new Block is allocated and is put into the rear of the + Block link table if no Block is free.
+ + The list starts up empty (ie, there is no allocated Block).
+ + After some nodes are freed, there probably are some free nodes before + the sequence of the allocated nodes, but we do not reuse it. It is better + to keep the allocated nodes are in the sequence, for it is more efficient + for allocating and freeing TranxNode. + +
+ There are two methods for freeing nodes. They are free_all_nodes and + free_nodes_before.
+ + 'A Block is free' means all of its nodes are free.
+ As all allcoated nodes are in the sequence, 'Before one node' means all + nodes before given node in the same Block and all Blocks before the Block + which containing the given node. As such, all Blocks before the given one + ('node') are free Block and moved into the rear of the Block link table. + The Block containing the given 'node', however, is not. For at least the + given 'node' is still in use. This will waste at most one Block, but it is + more efficient.
+ +
+ If there are some free Blocks and the total number of the Blocks in the + Block link table is larger than the 'reserved_blocks', Some free Blocks + will be freed until the total number of the Blocks is equal to the + 'reserved_blocks' or there is only one free Block behind the + 'current_block'. + */ +#define BLOCK_TRANX_NODES 16 +class TranxNodeAllocator +{ +public: + /** + @param reserved_nodes + The number of reserved TranxNodes. It is used to set 'reserved_blocks' + which can contain at least 'reserved_nodes' number of TranxNodes. When + freeing memory, we will reserve at least reserved_blocks of Blocks not + freed. + */ + TranxNodeAllocator(uint reserved_nodes) : + reserved_blocks(reserved_nodes/BLOCK_TRANX_NODES + + (reserved_nodes%BLOCK_TRANX_NODES > 1 ? 2 : 1)), + last_node(-1), first_block(NULL), last_block(NULL), + current_block(NULL), block_num(0) {} + + ~TranxNodeAllocator() + { + Block *block= first_block; + while (block != NULL) + { + Block *next= block->next; + free_block(block); + block= next; + } + } + + /** + The pointer of the first node after 'last_node' in current_block is + returned. current_block will move to the next free Block when all nodes of + it are in use. A new Block is allocated and is put into the rear of the + Block link table if no Block is free.
+ + @return Return a TranxNode *, or NULL if an error occured. + */ + TranxNode *allocate_node() + { + TranxNode *trx_node; + Block *block= current_block; + + if (last_node == BLOCK_TRANX_NODES-1) + { + current_block= current_block->next; + last_node= -1; + } + + if (current_block == NULL && allocate_block()) + { + current_block= block; + if (current_block) + last_node= BLOCK_TRANX_NODES-1; + return NULL; + } + + trx_node= &(current_block->nodes[++last_node]); + trx_node->log_name_[0] = '\0'; + trx_node->log_pos_= 0; + trx_node->next_= 0; + trx_node->hash_next_= 0; + return trx_node; + } + + /** + All nodes are freed. + + @return Return 0, or 1 if an error occoured. + */ + int free_all_nodes() + { + current_block= first_block; + last_node= -1; + free_blocks(); + return 0; + } + + /** + All Blocks before the given 'node' are free Block and moved into the rear + of the Block link table. + + @param node All nodes before 'node' will be freed + + @return Return 0, or 1 if an error occoured. + */ + int free_nodes_before(TranxNode* node) + { + Block *block; + Block *prev_block; + + block= first_block; + while (block != current_block->next) + { + /* Find the Block containing the given node */ + if (&(block->nodes[0]) <= node && &(block->nodes[BLOCK_TRANX_NODES]) >= node) + { + /* All Blocks before the given node are put into the rear */ + if (first_block != block) + { + last_block->next= first_block; + first_block= block; + last_block= prev_block; + last_block->next= NULL; + free_blocks(); + } + return 0; + } + prev_block= block; + block= block->next; + } + + /* Node does not find should never happen */ + DBUG_ASSERT(0); + return 1; + } + +private: + uint reserved_blocks; + int last_node; + struct Block { + Block *next; + TranxNode nodes[BLOCK_TRANX_NODES]; + }; + Block *first_block; + Block *last_block; + Block *current_block; + uint block_num; + + int allocate_block() + { + Block *block= (Block *)my_malloc(sizeof(Block), MYF(0)); + if (block) + { + block->next= NULL; + + if (first_block == NULL) + first_block= block; + else + last_block->next= block; + + /* New Block is always put into the rear */ + last_block= block; + /* New Block is always the current_block */ + current_block= block; + ++block_num; + return 0; + } + return 1; + } + + void free_block(Block *block) + { + my_free(block, MYF(0)); + --block_num; + } + + void free_blocks() + { + if (current_block == NULL || current_block->next == NULL) + return; + + /* One free Block is always kept behind the current block */ + Block *block= current_block->next->next; + while (block_num > reserved_blocks && block != NULL) + { + Block *next= block->next; + free_block(block); + block= next; + } + current_block->next->next= block; + if (block == NULL) + last_block= current_block->next; + } +}; + + /** This class manages memory for active transaction list. @@ -31,13 +272,8 @@ class ActiveTranx :public Trace { private: - struct TranxNode { - char log_name_[FN_REFLEN]; - my_off_t log_pos_; - struct TranxNode *next_; /* the next node in the sorted list */ - struct TranxNode *hash_next_; /* the next node during hash collision */ - }; + TranxNodeAllocator allocator_; /* These two record the active transaction list in sort order. */ TranxNode *trx_front_, *trx_rear_; @@ -48,24 +284,22 @@ private: inline void assert_lock_owner(); - inline TranxNode* alloc_tranx_node(); - inline unsigned int calc_hash(const unsigned char *key,unsigned int length); unsigned int get_hash_value(const char *log_file_name, my_off_t log_file_pos); int compare(const char *log_file_name1, my_off_t log_file_pos1, - const TranxNode *node2) { + const TranxNode *node2) { return compare(log_file_name1, log_file_pos1, - node2->log_name_, node2->log_pos_); + node2->log_name_, node2->log_pos_); } int compare(const TranxNode *node1, - const char *log_file_name2, my_off_t log_file_pos2) { + const char *log_file_name2, my_off_t log_file_pos2) { return compare(node1->log_name_, node1->log_pos_, - log_file_name2, log_file_pos2); + log_file_name2, log_file_pos2); } int compare(const TranxNode *node1, const TranxNode *node2) { return compare(node1->log_name_, node1->log_pos_, - node2->log_name_, node2->log_pos_); + node2->log_name_, node2->log_pos_); } public: @@ -88,7 +322,7 @@ public: * 0: success; non-zero: error */ int clear_active_tranx_nodes(const char *log_file_name, - my_off_t log_file_pos); + my_off_t log_file_pos); /* Given a position, check to see whether the position is an active * transaction's ending position by probing the hash table. @@ -99,7 +333,7 @@ public: * (file_name, file_position). */ static int compare(const char *log_file_name1, my_off_t log_file_pos1, - const char *log_file_name2, my_off_t log_file_pos2); + const char *log_file_name2, my_off_t log_file_pos2); }; === modified file 'sql/rpl_handler.cc' --- a/sql/rpl_handler.cc 2009-12-04 01:46:33 +0000 +++ b/sql/rpl_handler.cc 2010-01-29 06:39:23 +0000 @@ -190,8 +190,8 @@ int Trans_delegate::after_commit(THD *th { Trans_param param; bool is_real_trans= (all || thd->transaction.all.ha_list == 0); - if (is_real_trans) - param.flags |= TRANS_IS_REAL_TRANS; + + param.flags = is_real_trans ? TRANS_IS_REAL_TRANS : 0; Trans_binlog_info *log_info= my_pthread_getspecific_ptr(Trans_binlog_info*, RPL_TRANS_BINLOG_INFO); @@ -218,8 +218,8 @@ int Trans_delegate::after_rollback(THD * { Trans_param param; bool is_real_trans= (all || thd->transaction.all.ha_list == 0); - if (is_real_trans) - param.flags |= TRANS_IS_REAL_TRANS; + + param.flags = is_real_trans ? TRANS_IS_REAL_TRANS : 0; Trans_binlog_info *log_info= my_pthread_getspecific_ptr(Trans_binlog_info*, RPL_TRANS_BINLOG_INFO); @@ -228,7 +228,7 @@ int Trans_delegate::after_rollback(THD * param.log_pos= log_info ? log_info->log_pos : 0; int ret= 0; - FOREACH_OBSERVER(ret, after_commit, thd, (¶m)); + FOREACH_OBSERVER(ret, after_rollback, thd, (¶m)); /* This is the end of a real transaction or autocommit statement, we --===============8734493574605572427== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/li-bing.song@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: li-bing.song@stripped # target_branch: file:///home/anders/work/bzrroot/mysql-5.1-rep-\ # semisync/ # testament_sha1: 788495e94e326718bc7b18f03c62e6a82b37469b # timestamp: 2010-01-29 14:39:34 +0800 # base_revision_id: zhenxing.he@stripped\ # a4nbo9003sb8j9gu # # Begin bundle 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 --===============8734493574605572427==--