From be86e4428010f41804608c2d2ff7adcd9d6fb270 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 23 Oct 2012 11:19:42 +0200 Subject: MDEV-26: Global transaction id. Partial commit. --- sql/sql_repl.cc | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 6b8d0f5153f..5698b4679b6 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -616,10 +616,34 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, } /* - Do not send binlog checkpoint events to a slave that does not understand it. + Replace GTID events with old-style BEGIN events for slaves that do not + understand global transaction IDs. For stand-alone events, where there is + no terminating COMMIT query event, omit the GTID event or replace it with + a dummy event, as appropriate. */ - if (unlikely(event_type == BINLOG_CHECKPOINT_EVENT) && - mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_BINLOG_CHECKPOINT) + if (event_type == GTID_EVENT && + mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_GTID) + { + bool need_dummy= + mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES; + bool err= Gtid_log_event::make_compatible_event(packet, &need_dummy, + ev_offset, + current_checksum_alg); + if (err) + return "Failed to replace GTID event with backwards-compatible event: " + "currupt event."; + if (!need_dummy) + return NULL; + } + + /* + Do not send binlog checkpoint or gtid list events to a slave that does not + understand it. + */ + if ((unlikely(event_type == BINLOG_CHECKPOINT_EVENT) && + mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_BINLOG_CHECKPOINT) || + (unlikely(event_type == GTID_LIST_EVENT) && + mariadb_slave_capability < MARIA_SLAVE_CAPABILITY_GTID)) { if (mariadb_slave_capability >= MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES) { -- cgit v1.2.1 From ab8e8f4b277c23e6989650e0590ec0a5fa03fb3a Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 23 Oct 2012 12:46:29 +0200 Subject: MDEV-500: Session variable for server_id MDEV-26: Global transaction id, partial commit Change server_id to be a session variable. User with SUPER can set it to binlog with different server_id. Implement backward-compatible ::server_id mirror for plugins. --- sql/sql_repl.cc | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 5698b4679b6..f6329092ed0 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -81,7 +81,7 @@ static int fake_rotate_event(NET* net, String* packet, char* log_file_name, uint ident_len = (uint) strlen(p); ulong event_len = ident_len + LOG_EVENT_HEADER_LEN + ROTATE_HEADER_LEN + (do_checksum ? BINLOG_CHECKSUM_LEN : 0); - int4store(header + SERVER_ID_OFFSET, server_id); + int4store(header + SERVER_ID_OFFSET, global_system_variables.server_id); int4store(header + EVENT_LEN_OFFSET, event_len); int2store(header + FLAGS_OFFSET, LOG_EVENT_ARTIFICIAL_F); @@ -539,7 +539,7 @@ static int send_heartbeat_event(NET* net, String* packet, uint ident_len = strlen(p); ulong event_len = ident_len + LOG_EVENT_HEADER_LEN + (do_checksum ? BINLOG_CHECKSUM_LEN : 0); - int4store(header + SERVER_ID_OFFSET, server_id); + int4store(header + SERVER_ID_OFFSET, global_system_variables.server_id); int4store(header + EVENT_LEN_OFFSET, event_len); int2store(header + FLAGS_OFFSET, 0); @@ -748,7 +748,7 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, mariadb_slave_capability= get_mariadb_slave_capability(thd); if (global_system_variables.log_warnings > 1) sql_print_information("Start binlog_dump to slave_server(%d), pos(%s, %lu)", - thd->server_id, log_ident, (ulong)pos); + thd->variables.server_id, log_ident, (ulong)pos); if (RUN_HOOK(binlog_transmit, transmit_start, (thd, flags, log_ident, pos))) { errmsg= "Failed to run hook 'transmit_start'"; @@ -1127,7 +1127,8 @@ impossible position"; int ret; ulong signal_cnt; DBUG_PRINT("wait",("waiting for data in binary log")); - if (thd->server_id==0) // for mysqlbinlog (mysqlbinlog.server_id==0) + /* For mysqlbinlog (mysqlbinlog.server_id==0). */ + if (thd->variables.server_id==0) { mysql_mutex_unlock(log_lock); goto end; @@ -1647,7 +1648,7 @@ void kill_zombie_dump_threads(uint32 slave_server_id) while ((tmp=it++)) { if (tmp->command == COM_BINLOG_DUMP && - tmp->server_id == slave_server_id) + tmp->variables.server_id == slave_server_id) { mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete break; @@ -1856,7 +1857,7 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) { ulong s_id; get_dynamic(&lex_mi->repl_ignore_server_ids, (uchar*) &s_id, i); - if (s_id == ::server_id && replicate_same_server_id) + if (s_id == global_system_variables.server_id && replicate_same_server_id) { my_error(ER_SLAVE_IGNORE_SERVER_IDS, MYF(0), static_cast(s_id)); ret= TRUE; -- cgit v1.2.1 From 03f28863e845976f899c8c35dab3add918f4a8f6 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 5 Nov 2012 15:01:49 +0100 Subject: MDEV-26: Global transaction commit. Intermediate commit. Now slave records GTID in mysql.rpl_slave_state when applying XID log event. --- sql/sql_repl.cc | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index f6329092ed0..3c206a8857f 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -16,10 +16,12 @@ #include "sql_priv.h" #include "unireg.h" +#include "sql_base.h" #include "sql_parse.h" // check_access #ifdef HAVE_REPLICATION #include "rpl_mi.h" +#include "rpl_rli.h" #include "sql_repl.h" #include "sql_acl.h" // SUPER_ACL #include "log_event.h" @@ -748,7 +750,7 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, mariadb_slave_capability= get_mariadb_slave_capability(thd); if (global_system_variables.log_warnings > 1) sql_print_information("Start binlog_dump to slave_server(%d), pos(%s, %lu)", - thd->variables.server_id, log_ident, (ulong)pos); + (int)thd->variables.server_id, log_ident, (ulong)pos); if (RUN_HOOK(binlog_transmit, transmit_start, (thd, flags, log_ident, pos))) { errmsg= "Failed to run hook 'transmit_start'"; @@ -2442,4 +2444,118 @@ int log_loaded_block(IO_CACHE* file) DBUG_RETURN(0); } + +/** + Initialise the slave replication state from the mysql.rpl_slave_state table. + + This is called each time an SQL thread starts, but the data is only actually + loaded on the first call. + + The slave state is the last GTID applied on the slave within each + replication domain. + + To avoid row lock contention, there are multiple rows for each domain_id. + The one containing the current slave state is the one with the maximal + sub_id value, within each domain_id. + + CREATE TABLE mysql.rpl_slave_state ( + domain_id INT UNSIGNED NOT NULL, + sub_id BIGINT UNSIGNED NOT NULL, + server_id INT UNSIGNED NOT NULL, + seq_no BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (domain_id, sub_id)) +*/ + +void +rpl_init_gtid_slave_state() +{ + rpl_global_gtid_slave_state.init(); +} + + +void +rpl_deinit_gtid_slave_state() +{ + rpl_global_gtid_slave_state.deinit(); +} + + +int +rpl_load_gtid_slave_state(THD *thd) +{ + TABLE_LIST tlist; + TABLE *table; + bool table_opened= false; + bool table_scanned= false; + DBUG_ENTER("rpl_load_gtid_slave_state"); + + int err= 0; + rpl_global_gtid_slave_state.lock(); + if (rpl_global_gtid_slave_state.loaded) + goto end; + + mysql_reset_thd_for_next_command(thd, 0); + + tlist.init_one_table(STRING_WITH_LEN("mysql"), + rpl_gtid_slave_state_table_name.str, + rpl_gtid_slave_state_table_name.length, + NULL, TL_READ); + if ((err= open_and_lock_tables(thd, &tlist, FALSE, 0))) + goto end; + table_opened= true; + table= tlist.table; + + /* + ToDo: Check the table definition, error if not as expected. + We need the correct first 4 columns with correct type, and the primary key. + */ + + bitmap_set_bit(table->read_set, table->field[0]->field_index); + bitmap_set_bit(table->read_set, table->field[1]->field_index); + bitmap_set_bit(table->read_set, table->field[2]->field_index); + bitmap_set_bit(table->read_set, table->field[3]->field_index); + if ((err= table->file->ha_rnd_init_with_error(1))) + goto end; + table_scanned= true; + for (;;) + { + uint32 domain_id, server_id; + uint64 sub_id, seq_no; + if ((err= table->file->ha_rnd_next(table->record[0]))) + { + if (err == HA_ERR_RECORD_DELETED) + continue; + else if (err == HA_ERR_END_OF_FILE) + break; + else + goto end; + } + domain_id= (ulonglong)table->field[0]->val_int(); + sub_id= (ulonglong)table->field[1]->val_int(); + server_id= (ulonglong)table->field[2]->val_int(); + seq_no= (ulonglong)table->field[3]->val_int(); + DBUG_PRINT("info", ("Read slave state row: %u:%u-%lu sub_id=%lu\n", + (unsigned)domain_id, (unsigned)server_id, + (ulong)seq_no, (ulong)sub_id)); + if ((err= rpl_global_gtid_slave_state.update(domain_id, server_id, + sub_id, seq_no))) + goto end; + } + err= 0; /* Clear HA_ERR_END_OF_FILE */ + + rpl_global_gtid_slave_state.loaded= true; + +end: + if (table_scanned) + { + table->file->ha_index_or_rnd_end(); + ha_commit_trans(thd, FALSE); + ha_commit_trans(thd, TRUE); + } + if (table_opened) + close_thread_tables(thd); + rpl_global_gtid_slave_state.unlock(); + DBUG_RETURN(err); +} + #endif /* HAVE_REPLICATION */ -- cgit v1.2.1 From bdbce30dee9bf550cf09e5d9098bc2a7e0ad940e Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 15 Nov 2012 13:11:35 +0100 Subject: MDEV-26: Global transaction id: Intermediate commit. Now slave can connect to master, sending start position as slave state rather than old-style binlog name/position. This enables to switch to a new master by changing just connection information, replication slave GTID state ensures that slave starts at the correct point in the new master. --- sql/sql_repl.cc | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 323 insertions(+), 6 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 3c206a8857f..cd32dd7148a 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -506,6 +506,26 @@ get_mariadb_slave_capability(THD *thd) } +/* + Get the value of the @slave_connect_state user variable into the supplied + String (this is the GTID connect state requested by the connecting slave). + + Returns false if error (ie. slave did not set the variable and does not + want to use GTID to set start position), true if success. +*/ +static bool +get_slave_connect_state(THD *thd, String *out_str) +{ + bool null_value; + + const LEX_STRING name= { C_STRING_WITH_LEN("slave_connect_state") }; + user_var_entry *entry= + (user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str, + name.length); + return entry && entry->val_str(&null_value, out_str, 0) && !null_value; +} + + /* Function prepares and sends repliation heartbeat event. @@ -569,6 +589,216 @@ static int send_heartbeat_event(NET* net, String* packet, } +struct binlog_file_entry +{ + binlog_file_entry *next; + char *name; +}; + +static binlog_file_entry * +get_binlog_list(MEM_ROOT *memroot) +{ + IO_CACHE *index_file; + char fname[FN_REFLEN]; + size_t length; + binlog_file_entry *current_list= NULL, *e; + DBUG_ENTER("get_binlog_list"); + + if (!mysql_bin_log.is_open()) + { + my_error(ER_NO_BINARY_LOGGING, MYF(0)); + DBUG_RETURN(NULL); + } + + mysql_bin_log.lock_index(); + index_file=mysql_bin_log.get_index_file(); + reinit_io_cache(index_file, READ_CACHE, (my_off_t) 0, 0, 0); + + /* The file ends with EOF or empty line */ + while ((length=my_b_gets(index_file, fname, sizeof(fname))) > 1) + { + --length; /* Remove the newline */ + if (!(e= (binlog_file_entry *)alloc_root(memroot, sizeof(*e))) || + !(e->name= strmake_root(memroot, fname, length))) + { + mysql_bin_log.unlock_index(); + my_error(ER_OUTOFMEMORY, MYF(0), length + 1 + sizeof(*e)); + DBUG_RETURN(NULL); + } + e->next= current_list; + current_list= e; + } + mysql_bin_log.unlock_index(); + + DBUG_RETURN(current_list); +} + +/* + Find the Gtid_list_log_event at the start of a binlog. + + NULL for ok, non-NULL error message for error. + + If ok, then the event is returned in *out_gtid_list. This can be NULL if we + get back to binlogs written by old server version without GTID support. If + so, it means we have reached the point to start from, as no GTID events can + exist in earlier binlogs. +*/ +static const char * +get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list) +{ + Format_description_log_event init_fdle(BINLOG_VERSION); + Format_description_log_event *fdle; + Log_event *ev; + const char *errormsg = NULL; + + *out_gtid_list= NULL; + + if (!(ev= Log_event::read_log_event(cache, 0, &init_fdle, + opt_master_verify_checksum)) || + ev->get_type_code() != FORMAT_DESCRIPTION_EVENT) + return "Could not read format description log event while looking for " + "GTID position in binlog"; + + fdle= static_cast(ev); + + for (;;) + { + Log_event_type typ; + + ev= Log_event::read_log_event(cache, 0, fdle, opt_master_verify_checksum); + if (!ev) + { + errormsg= "Could not read GTID list event while looking for GTID " + "position in binlog"; + break; + } + typ= ev->get_type_code(); + if (typ == GTID_LIST_EVENT) + break; /* Done, found it */ + if (typ == ROTATE_EVENT || typ == STOP_EVENT || + typ == FORMAT_DESCRIPTION_EVENT) + continue; /* Continue looking */ + + /* We did not find any Gtid_list_log_event, must be old binlog. */ + ev= NULL; + break; + } + + delete fdle; + *out_gtid_list= static_cast(ev); + return errormsg; +} + + +/* + Check if every GTID requested by the slave is contained in this (or a later) + binlog file. Return true if so, false if not. +*/ +static bool +contains_all_slave_gtid(slave_connection_state *st, Gtid_list_log_event *glev) +{ + uint32 i; + + for (i= 0; i < glev->count; ++i) + { + const rpl_gtid *gtid= st->find(glev->list[i].domain_id); + if (gtid != NULL && + gtid->server_id == glev->list[i].server_id && + gtid->seq_no <= glev->list[i].seq_no) + { + /* + The slave needs to receive gtid, but it is contained in an earlier + binlog file. So we need to serch back further. + */ + return false; + } + } + return true; +} + + +/* + Find the name of the binlog file to start reading for a slave that connects + using GTID state. + + Returns the file name in out_name, which must be of size at least FN_REFLEN. + + Returns NULL on ok, error message on error. +*/ +static const char * +gtid_find_binlog_file(slave_connection_state *state, char *out_name) +{ + MEM_ROOT memroot; + binlog_file_entry *list; + Gtid_list_log_event *glev; + const char *errormsg= NULL; + IO_CACHE cache; + File file = (File)-1; + char buf[FN_REFLEN]; + + bzero((char*) &cache, sizeof(cache)); + init_alloc_root(&memroot, 10*(FN_REFLEN+sizeof(binlog_file_entry)), 0); + if (!(list= get_binlog_list(&memroot))) + { + errormsg= "Out of memory while looking for GTID position in binlog"; + goto end; + } + + while (list) + { + if (!list->next) + { + /* + It should be safe to read the currently used binlog, as we will only + read the header part that is already written. + + But if that does not work on windows, then we will need to cache the + event somewhere in memory I suppose - that could work too. + */ + } + /* + Read the Gtid_list_log_event at the start of the binlog file to + get the binlog state. + */ + if (normalize_binlog_name(buf, list->name, false)) + { + errormsg= "Failed to determine binlog file name while looking for " + "GTID position in binlog"; + goto end; + } + if ((file= open_binlog(&cache, buf, &errormsg)) == (File)-1 || + (errormsg= get_gtid_list_event(&cache, &glev))) + goto end; + + if (!glev || contains_all_slave_gtid(state, glev)) + { + strmake(out_name, buf, FN_REFLEN); + goto end; + } + list= list->next; + } + + /* We reached the end without finding anything. */ + errormsg= "Could not find GTID state requested by slave in any binlog " + "files. Probably the slave state is too old and required binlog files " + "have been purged."; + +end: + if (file != (File)-1) + { + end_io_cache(&cache); + mysql_file_close(file, MYF(MY_WME)); + } + + free_root(&memroot, MYF(0)); + return errormsg; +} + + +enum enum_gtid_skip_type { + GTID_SKIP_NOT, GTID_SKIP_STANDALONE, GTID_SKIP_TRANSACTION +}; + /* Helper function for mysql_binlog_send() to write an event down the slave connection. @@ -579,10 +809,65 @@ static const char * send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, Log_event_type event_type, char *log_file_name, IO_CACHE *log, int mariadb_slave_capability, - ulong ev_offset, uint8 current_checksum_alg) + ulong ev_offset, uint8 current_checksum_alg, + bool using_gtid_state, slave_connection_state *gtid_state, + enum_gtid_skip_type *gtid_skip_group) { my_off_t pos; + /* Skip GTID event groups until we reach slave position within a domain_id. */ + if (event_type == GTID_EVENT && using_gtid_state && gtid_state->count() > 0) + { + uint32 server_id, domain_id; + uint64 seq_no; + uchar flags2; + rpl_gtid *gtid; + size_t len= packet->length(); + + if (ev_offset > len || + Gtid_log_event::peek(packet->ptr()+ev_offset, len - ev_offset, + &domain_id, &server_id, &seq_no, &flags2)) + return "Failed to read Gtid_log_event: corrupt binlog"; + gtid= gtid_state->find(domain_id); + if (gtid != NULL) + { + /* Skip this event group if we have not yet reached slave start pos. */ + if (server_id != gtid->server_id || seq_no <= gtid->seq_no) + *gtid_skip_group = (flags2 & Gtid_log_event::FL_STANDALONE ? + GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION); + /* + Delete this entry if we have reached slave start position (so we will + not skip subsequent events and won't have to look them up and check). + */ + if (server_id == gtid->server_id && seq_no >= gtid->seq_no) + gtid_state->remove(gtid); + } + } + + /* + Skip event group if we have not yet reached the correct slave GTID position. + + Note that slave that understands GTID can also tolerate holes, so there is + no need to supply dummy event. + */ + switch (*gtid_skip_group) + { + case GTID_SKIP_STANDALONE: + if (event_type != INTVAR_EVENT && + event_type != RAND_EVENT && + event_type != USER_VAR_EVENT && + event_type != TABLE_MAP_EVENT && + event_type != ANNOTATE_ROWS_EVENT) + *gtid_skip_group= GTID_SKIP_NOT; + return NULL; + case GTID_SKIP_TRANSACTION: + if (event_type == XID_EVENT /* ToDo || is_COMMIT_query_event() */) + *gtid_skip_group= GTID_SKIP_NOT; + return NULL; + case GTID_SKIP_NOT: + break; + } + /* Do not send annotate_rows events unless slave requested it. */ if (event_type == ANNOTATE_ROWS_EVENT && !(flags & BINLOG_SEND_ANNOTATE_ROWS_EVENT)) { @@ -722,6 +1007,11 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, mysql_mutex_t *log_lock; mysql_cond_t *log_cond; int mariadb_slave_capability; + char str_buf[256]; + String connect_gtid_state(str_buf, sizeof(str_buf), system_charset_info); + bool using_gtid_state; + slave_connection_state gtid_state; + enum_gtid_skip_type gtid_skip_group= GTID_SKIP_NOT; uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; int old_max_allowed_packet= thd->variables.max_allowed_packet; @@ -748,6 +1038,10 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, set_timespec_nsec(*heartbeat_ts, 0); } mariadb_slave_capability= get_mariadb_slave_capability(thd); + + connect_gtid_state.length(0); + using_gtid_state= get_slave_connect_state(thd, &connect_gtid_state); + if (global_system_variables.log_warnings > 1) sql_print_information("Start binlog_dump to slave_server(%d), pos(%s, %lu)", (int)thd->variables.server_id, log_ident, (ulong)pos); @@ -781,10 +1075,30 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, } name=search_file_name; - if (log_ident[0]) - mysql_bin_log.make_log_name(search_file_name, log_ident); + if (using_gtid_state) + { + if (gtid_state.load(connect_gtid_state.c_ptr_quick(), + connect_gtid_state.length())) + { + errmsg= "Out of memory or malformed slave request when obtaining start " + "position from GTID state"; + my_errno= ER_UNKNOWN_ERROR; + goto err; + } + if ((errmsg= gtid_find_binlog_file(>id_state, search_file_name))) + { + my_errno= ER_UNKNOWN_ERROR; + goto err; + } + pos= 4; + } else - name=0; // Find first log + { + if (log_ident[0]) + mysql_bin_log.make_log_name(search_file_name, log_ident); + else + name=0; // Find first log + } linfo.index_file_offset = 0; @@ -1036,7 +1350,8 @@ impossible position"; if ((tmp_msg= send_event_to_slave(thd, net, packet, flags, event_type, log_file_name, &log, mariadb_slave_capability, ev_offset, - current_checksum_alg))) + current_checksum_alg, using_gtid_state, + >id_state, >id_skip_group))) { errmsg= tmp_msg; my_errno= ER_UNKNOWN_ERROR; @@ -1197,7 +1512,9 @@ impossible position"; (tmp_msg= send_event_to_slave(thd, net, packet, flags, event_type, log_file_name, &log, mariadb_slave_capability, ev_offset, - current_checksum_alg))) + current_checksum_alg, + using_gtid_state, >id_state, + >id_skip_group))) { errmsg= tmp_msg; my_errno= ER_UNKNOWN_ERROR; -- cgit v1.2.1 From 0b36233acd53299f8721d9229698e97b72c90d93 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 22 Jan 2013 15:18:36 +0100 Subject: MDEV-26. Intermediate commit. Implement CHANGE MASTER TO MASTER_GTID_POS=xxx. --- sql/sql_repl.cc | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index cd32dd7148a..886ef79067b 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -2236,6 +2236,13 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) mi->rli.group_relay_log_pos= mi->rli.event_relay_log_pos= lex_mi->relay_log_pos; } + if (lex_mi->gtid_pos_auto) + mi->gtid_pos_auto= true; + else if (lex_mi->gtid_pos_str.str || + lex_mi->log_file_name || lex_mi->pos || + lex_mi->relay_log_name || lex_mi->relay_log_pos) + mi->gtid_pos_auto= false; + /* If user did specify neither host nor port nor any log name nor any log pos, i.e. he specified only user/password/master_connect_retry, he probably -- cgit v1.2.1 From 041f26d349f1afb9edf6b0f17dfecfb3f29a9d25 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Feb 2013 16:44:38 +0100 Subject: MDEV-26. Intermediate commit. Implement binlog_gtid_pos() function. This will be used so that the slave can obtain the gtid position automatically from first connect with old-style position - then MASTER_GTID_POS=AUTO will work the next time. Can also be used by mysqldump --master-data to give the current gtid position directly. --- sql/sql_repl.cc | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 178 insertions(+), 3 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 886ef79067b..b92ca2720ed 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -656,8 +656,12 @@ get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list) if (!(ev= Log_event::read_log_event(cache, 0, &init_fdle, opt_master_verify_checksum)) || ev->get_type_code() != FORMAT_DESCRIPTION_EVENT) + { + if (ev) + delete ev; return "Could not read format description log event while looking for " "GTID position in binlog"; + } fdle= static_cast(ev); @@ -795,6 +799,177 @@ end: } +/* + Given an old-style binlog position with file name and file offset, find the + corresponding gtid position. If the offset is not at an event boundary, give + an error. + + Return NULL on ok, error message string on error. + + ToDo: Improve the performance of this by using binlog index files. +*/ +static const char * +gtid_state_from_pos(const char *name, uint32 offset, + slave_connection_state *gtid_state) +{ + IO_CACHE cache; + File file; + const char *errormsg= NULL; + bool found_gtid_list_event= false; + bool found_format_description_event= false; + bool valid_pos= false; + uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; + int err; + String packet; + + if (gtid_state->load((const rpl_gtid *)NULL, 0)) + { + errormsg= "Internal error (out of memory?) initializing slave state " + "while scanning binlog to find start position"; + return errormsg; + } + + if ((file= open_binlog(&cache, name, &errormsg)) == (File)-1) + return errormsg; + + /* + First we need to find the initial GTID_LIST_EVENT. We need this even + if the offset is at the very start of the binlog file. + + But if we do not find any GTID_LIST_EVENT, then this is an old binlog + with no GTID information, so we return empty GTID state. + */ + for (;;) + { + Log_event_type typ; + uint32 cur_pos; + + cur_pos= (uint32)my_b_tell(&cache); + if (cur_pos == offset) + valid_pos= true; + if (found_format_description_event && found_gtid_list_event && + cur_pos >= offset) + break; + + packet.length(0); + err= Log_event::read_log_event(&cache, &packet, NULL, + current_checksum_alg); + if (err) + { + errormsg= "Could not read binlog while searching for slave start " + "position on master"; + goto end; + } + /* + The cast to uchar is needed to avoid a signed char being converted to a + negative number. + */ + typ= (Log_event_type)(uchar)packet[EVENT_TYPE_OFFSET]; + if (typ == FORMAT_DESCRIPTION_EVENT) + { + if (found_format_description_event) + { + errormsg= "Duplicate format description log event found while " + "searching for old-style position in binlog"; + goto end; + } + + current_checksum_alg= get_checksum_alg(packet.ptr(), packet.length()); + found_format_description_event= true; + } + else if (typ != FORMAT_DESCRIPTION_EVENT && !found_format_description_event) + { + errormsg= "Did not find format description log event while searching " + "for old-style position in binlog"; + goto end; + } + else if (typ == ROTATE_EVENT || typ == STOP_EVENT || + typ == BINLOG_CHECKPOINT_EVENT) + continue; /* Continue looking */ + else if (typ == GTID_LIST_EVENT) + { + rpl_gtid *gtid_list; + bool status; + uint32 list_len; + + if (found_gtid_list_event) + { + errormsg= "Found duplicate Gtid_list_log_event while scanning binlog " + "to find slave start position"; + goto end; + } + status= Gtid_list_log_event::peek(packet.ptr(), packet.length(), + >id_list, &list_len); + if (status) + { + errormsg= "Error reading Gtid_list_log_event while searching " + "for old-style position in binlog"; + goto end; + } + err= gtid_state->load(gtid_list, list_len); + my_free(gtid_list); + if (err) + { + errormsg= "Internal error (out of memory?) initialising slave state " + "while scanning binlog to find start position"; + goto end; + } + found_gtid_list_event= true; + } + else if (!found_gtid_list_event) + { + /* We did not find any Gtid_list_log_event, must be old binlog. */ + goto end; + } + else if (typ == GTID_EVENT) + { + rpl_gtid gtid; + uchar flags2; + if (Gtid_log_event::peek(packet.ptr(), packet.length(), >id.domain_id, + >id.server_id, >id.seq_no, &flags2)) + { + errormsg= "Corrupt gtid_log_event found while scanning binlog to find " + "initial slave position"; + goto end; + } + if (gtid_state->update(>id)) + { + errormsg= "Internal error (out of memory?) updating slave state while " + "scanning binlog to find start position"; + goto end; + } + } + } + + if (!valid_pos) + { + errormsg= "Slave requested incorrect position in master binlog. " + "Requested position %u in file '%s', but this position does not " + "correspond to the location of any binlog event."; + } + +end: + end_io_cache(&cache); + mysql_file_close(file, MYF(MY_WME)); + + return errormsg; +} + + +int +gtid_state_from_binlog_pos(const char *name, uint32 pos, String *out_str) +{ + slave_connection_state gtid_state; + + if (pos < 4) + pos= 4; + if (gtid_state_from_pos(name, pos, >id_state) || + gtid_state.to_string(out_str)) + return 1; + return 0; +} + + enum enum_gtid_skip_type { GTID_SKIP_NOT, GTID_SKIP_STANDALONE, GTID_SKIP_TRANSACTION }; @@ -945,8 +1120,8 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, binlog positions. */ if (Query_log_event::dummy_event(packet, ev_offset, current_checksum_alg)) - return "Failed to replace binlog checkpoint event with dummy: " - "too small event."; + return "Failed to replace binlog checkpoint or gtid list event with " + "dummy: too small event."; } } @@ -1010,7 +1185,7 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, char str_buf[256]; String connect_gtid_state(str_buf, sizeof(str_buf), system_charset_info); bool using_gtid_state; - slave_connection_state gtid_state; + slave_connection_state gtid_state, return_gtid_state; enum_gtid_skip_type gtid_skip_group= GTID_SKIP_NOT; uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; -- cgit v1.2.1 From d98e2ea9fe0346c656ecfa7fc4a5aed96e1f7fd9 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 13 Feb 2013 13:36:46 +0100 Subject: MDEV-26. Intermediary commit. Fix binlog_gtid_pos() to handle empty file name, and to not allow user to open arbitrary file on the system. --- sql/sql_repl.cc | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index b92ca2720ed..e2b360611df 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -957,13 +957,28 @@ end: int -gtid_state_from_binlog_pos(const char *name, uint32 pos, String *out_str) +gtid_state_from_binlog_pos(const char *in_name, uint32 pos, String *out_str) { slave_connection_state gtid_state; + const char *lookup_name; + char name_buf[FN_REFLEN]; + LOG_INFO linfo; + + if (in_name && in_name[0]) + { + mysql_bin_log.make_log_name(name_buf, in_name); + lookup_name= name_buf; + } + else + lookup_name= NULL; + linfo.index_file_offset= 0; + if (mysql_bin_log.find_log_pos(&linfo, lookup_name, 1)) + return 1; if (pos < 4) pos= 4; - if (gtid_state_from_pos(name, pos, >id_state) || + + if (gtid_state_from_pos(linfo.log_file_name, pos, >id_state) || gtid_state.to_string(out_str)) return 1; return 0; -- cgit v1.2.1 From 1c6271b36a8a8b85e5af87915fc5d680e2c10575 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 15 Feb 2013 15:55:17 +0100 Subject: MDEV-26: Global Transaction ID. Implement CHANGE MASTER TO ... MASTER_GTID_POS = "x-y-z,a-b-c". --- sql/sql_repl.cc | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index e2b360611df..bd491b17e7c 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -2231,6 +2231,7 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) char relay_log_info_file_tmp[FN_REFLEN]; my_off_t saved_log_pos; LEX_MASTER_INFO* lex_mi= &thd->lex->mi; + slave_connection_state tmp_slave_state; DBUG_ENTER("change_master"); *master_info_added= false; @@ -2261,6 +2262,27 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) goto err; } + if (lex_mi->gtid_pos_str.str) + { + if (master_info_index->give_error_if_slave_running()) + { + ret= TRUE; + goto err; + } + /* + First load it into a dummy object, to check for parse errors. + We do not want to wipe the previous state if there is an error + in the syntax of the new state! + */ + if (tmp_slave_state.load(lex_mi->gtid_pos_str.str, + lex_mi->gtid_pos_str.length)) + { + my_error(ER_INCORRECT_GTID_STATE, MYF(0)); + ret= TRUE; + goto err; + } + } + thd_proc_info(thd, "Changing master"); create_logfile_name_with_suffix(master_info_file_tmp, @@ -2426,7 +2448,7 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) mi->rli.group_relay_log_pos= mi->rli.event_relay_log_pos= lex_mi->relay_log_pos; } - if (lex_mi->gtid_pos_auto) + if (lex_mi->gtid_pos_auto || lex_mi->gtid_pos_str.str) mi->gtid_pos_auto= true; else if (lex_mi->gtid_pos_str.str || lex_mi->log_file_name || lex_mi->pos || @@ -2463,6 +2485,18 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) strmake(mi->master_log_name, mi->rli.group_master_log_name, sizeof(mi->master_log_name)-1); } + + if (lex_mi->gtid_pos_str.str) + { + if (rpl_global_gtid_slave_state.load(thd, lex_mi->gtid_pos_str.str, + lex_mi->gtid_pos_str.length, true)) + { + my_error(ER_FAILED_GTID_STATE_INIT, MYF(0)); + ret= TRUE; + goto err; + } + } + /* Relay log's IO_CACHE may not be inited, if rli->inited==0 (server was never a slave before). -- cgit v1.2.1 From ff8676e0e1f9665a92f44ef7685a7221a451ddc3 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 18 Feb 2013 15:41:17 +0100 Subject: MDEV-26: Global transaction ID. Fix initial loading of mysql.rpl_slave_state, the code had several issues. Some very basic MTR stuff, more to come. --- sql/sql_repl.cc | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 7 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index bd491b17e7c..46e27abd7f5 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -3035,11 +3035,21 @@ rpl_load_gtid_slave_state(THD *thd) TABLE *table; bool table_opened= false; bool table_scanned= false; + struct local_element { uint64 sub_id; rpl_gtid gtid; }; + struct local_element *entry; + HASH hash; + int err= 0; + uint32 i; DBUG_ENTER("rpl_load_gtid_slave_state"); - int err= 0; + my_hash_init(&hash, &my_charset_bin, 32, + offsetof(local_element, gtid) + offsetof(rpl_gtid, domain_id), + sizeof(uint32), NULL, my_free, HASH_UNIQUE); + rpl_global_gtid_slave_state.lock(); - if (rpl_global_gtid_slave_state.loaded) + bool loaded= rpl_global_gtid_slave_state.loaded; + rpl_global_gtid_slave_state.unlock(); + if (loaded) goto end; mysql_reset_thd_for_next_command(thd, 0); @@ -3069,6 +3079,8 @@ rpl_load_gtid_slave_state(THD *thd) { uint32 domain_id, server_id; uint64 sub_id, seq_no; + uchar *rec; + if ((err= table->file->ha_rnd_next(table->record[0]))) { if (err == HA_ERR_RECORD_DELETED) @@ -3085,13 +3097,50 @@ rpl_load_gtid_slave_state(THD *thd) DBUG_PRINT("info", ("Read slave state row: %u:%u-%lu sub_id=%lu\n", (unsigned)domain_id, (unsigned)server_id, (ulong)seq_no, (ulong)sub_id)); - if ((err= rpl_global_gtid_slave_state.update(domain_id, server_id, - sub_id, seq_no))) - goto end; + if ((rec= my_hash_search(&hash, (const uchar *)&domain_id, 0))) + { + entry= (struct local_element *)rec; + if (entry->sub_id >= sub_id) + continue; + } + else + { + if (!(entry= (struct local_element *)my_malloc(sizeof(*entry), + MYF(MY_WME)))) + { + err= 1; + goto end; + } + if ((err= my_hash_insert(&hash, (uchar *)entry))) + { + my_free(entry); + goto end; + } + } + + entry->sub_id= sub_id; + entry->gtid.domain_id= domain_id; + entry->gtid.server_id= server_id; + entry->gtid.seq_no= seq_no; } - err= 0; /* Clear HA_ERR_END_OF_FILE */ + rpl_global_gtid_slave_state.lock(); + for (i= 0; i < hash.records; ++i) + { + entry= (struct local_element *)my_hash_element(&hash, i); + if ((err= rpl_global_gtid_slave_state.update(entry->gtid.domain_id, + entry->gtid.server_id, + entry->sub_id, + entry->gtid.seq_no))) + { + rpl_global_gtid_slave_state.unlock(); + goto end; + } + } rpl_global_gtid_slave_state.loaded= true; + rpl_global_gtid_slave_state.unlock(); + + err= 0; /* Clear HA_ERR_END_OF_FILE */ end: if (table_scanned) @@ -3102,7 +3151,7 @@ end: } if (table_opened) close_thread_tables(thd); - rpl_global_gtid_slave_state.unlock(); + my_hash_free(&hash); DBUG_RETURN(err); } -- cgit v1.2.1 From bfec9c64b8a6e25c1c0da3e219a2f083b31bb9f4 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 19 Feb 2013 11:45:29 +0100 Subject: MDEV-26: Global transaction ID, intermediate commit. - Fix that slave GTID state was updated from the wrong place in the code, causing random crashing and other misery. - Fix updates to mysql.rpl_slave_state to not go to binlog (this would cause duplicate key errors on the slave and is generally the wrong thing to do). --- sql/sql_repl.cc | 1 + 1 file changed, 1 insertion(+) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 46e27abd7f5..9094b292433 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -3062,6 +3062,7 @@ rpl_load_gtid_slave_state(THD *thd) goto end; table_opened= true; table= tlist.table; + table->no_replicate= 1; /* ToDo: Check the table definition, error if not as expected. -- cgit v1.2.1 From 7426a466dcf364885a3cc670928b23a6f2fc2a5d Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 19 Feb 2013 14:22:29 +0100 Subject: MDEV-26: Global transaction ID, intermediate commit. Fix memory leak. --- sql/sql_repl.cc | 1 + 1 file changed, 1 insertion(+) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 9094b292433..a6810d748cd 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -679,6 +679,7 @@ get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list) typ= ev->get_type_code(); if (typ == GTID_LIST_EVENT) break; /* Done, found it */ + delete ev; if (typ == ROTATE_EVENT || typ == STOP_EVENT || typ == FORMAT_DESCRIPTION_EVENT) continue; /* Continue looking */ -- cgit v1.2.1 From cb46c333861a281d45ece73ae777389ca0357f75 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 21 Feb 2013 14:46:11 +0100 Subject: MDEV-26: Global transaction ID. Fix crash in binlog_gtid_pos('', 4) (and thus slave connect) when master does not have binlog enabled. --- sql/sql_repl.cc | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index a6810d748cd..62b7135641d 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -965,6 +965,12 @@ gtid_state_from_binlog_pos(const char *in_name, uint32 pos, String *out_str) char name_buf[FN_REFLEN]; LOG_INFO linfo; + if (!mysql_bin_log.is_open()) + { + my_error(ER_NO_BINARY_LOGGING, MYF(0)); + return 1; + } + if (in_name && in_name[0]) { mysql_bin_log.make_log_name(name_buf, in_name); -- cgit v1.2.1 From fff47f40f6588bf723e25d3c47f52bddac18a956 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 22 Feb 2013 10:23:17 +0100 Subject: MDEV-26: Global Transaction ID - Fix that binlog_gtid_pos() (and hence slave connect) does not work correctly in the very first binlog file (due to not logging empty Gtid_list_log_event). - Remove one instance of the stupid domain_id-0-is-implicit. - Rename the confusing Gtid_Pos_Auto in SHOW SLAVE STATUS to Using_Gtid. - Fix memory leak. --- sql/sql_repl.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 62b7135641d..f4c902c5954 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -735,7 +735,7 @@ gtid_find_binlog_file(slave_connection_state *state, char *out_name) { MEM_ROOT memroot; binlog_file_entry *list; - Gtid_list_log_event *glev; + Gtid_list_log_event *glev= NULL; const char *errormsg= NULL; IO_CACHE cache; File file = (File)-1; @@ -780,6 +780,8 @@ gtid_find_binlog_file(slave_connection_state *state, char *out_name) strmake(out_name, buf, FN_REFLEN); goto end; } + delete glev; + glev= NULL; list= list->next; } @@ -789,6 +791,8 @@ gtid_find_binlog_file(slave_connection_state *state, char *out_name) "have been purged."; end: + if (glev) + delete glev; if (file != (File)-1) { end_io_cache(&cache); -- cgit v1.2.1 From 2cf3d61fc23053c1a1acc412599cab350ceb31eb Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 22 Feb 2013 12:31:55 +0100 Subject: MDEV-26: Global Transaction ID - Fix skipping initial MyISAM DML when connecting using GTID. - Fix RESET MASTER not clearing in-memory binlog state. - Fix not reading standalone flag in Gtid_log_event::peek(). - Fix skipping DDL that the slave has already seen when using GTID position. --- sql/sql_repl.cc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index f4c902c5954..423e67d2165 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1015,6 +1015,7 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, enum_gtid_skip_type *gtid_skip_group) { my_off_t pos; + size_t len= packet->length(); /* Skip GTID event groups until we reach slave position within a domain_id. */ if (event_type == GTID_EVENT && using_gtid_state && gtid_state->count() > 0) @@ -1023,7 +1024,6 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, uint64 seq_no; uchar flags2; rpl_gtid *gtid; - size_t len= packet->length(); if (ev_offset > len || Gtid_log_event::peek(packet->ptr()+ev_offset, len - ev_offset, @@ -1054,7 +1054,8 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, switch (*gtid_skip_group) { case GTID_SKIP_STANDALONE: - if (event_type != INTVAR_EVENT && + if (event_type != GTID_EVENT && + event_type != INTVAR_EVENT && event_type != RAND_EVENT && event_type != USER_VAR_EVENT && event_type != TABLE_MAP_EVENT && @@ -1062,7 +1063,10 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, *gtid_skip_group= GTID_SKIP_NOT; return NULL; case GTID_SKIP_TRANSACTION: - if (event_type == XID_EVENT /* ToDo || is_COMMIT_query_event() */) + if (event_type == XID_EVENT || + (event_type == QUERY_EVENT && + Query_log_event::peek_is_commit_rollback(packet->ptr() + ev_offset, + len - ev_offset))) *gtid_skip_group= GTID_SKIP_NOT; return NULL; case GTID_SKIP_NOT: @@ -1173,7 +1177,7 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, (thd, flags, packet, log_file_name, pos))) return "run 'before_send_event' hook failed"; - if (my_net_write(net, (uchar*) packet->ptr(), packet->length())) + if (my_net_write(net, (uchar*) packet->ptr(), len)) return "Failed on my_net_write()"; DBUG_PRINT("info", ("log event code %d", (*packet)[LOG_EVENT_OFFSET+1] )); -- cgit v1.2.1 From 861830f9c24e965cf5fbd2f7753b285eac070148 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 26 Feb 2013 17:25:07 +0100 Subject: MDEV-26: Global transaction ID. Remove the two-component form of GTID with implicit domain_id=0, as it is likely to cause more confusion than help. Give a better error for CHANGE MASTER ... MASTER_GTID_POS='gtid,gitd,...' when two specified GTIDs have conflicting domain_id. --- sql/sql_repl.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 423e67d2165..d9f4c1f0b0e 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -2292,7 +2292,6 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) if (tmp_slave_state.load(lex_mi->gtid_pos_str.str, lex_mi->gtid_pos_str.length)) { - my_error(ER_INCORRECT_GTID_STATE, MYF(0)); ret= TRUE; goto err; } @@ -3110,7 +3109,7 @@ rpl_load_gtid_slave_state(THD *thd) sub_id= (ulonglong)table->field[1]->val_int(); server_id= (ulonglong)table->field[2]->val_int(); seq_no= (ulonglong)table->field[3]->val_int(); - DBUG_PRINT("info", ("Read slave state row: %u:%u-%lu sub_id=%lu\n", + DBUG_PRINT("info", ("Read slave state row: %u-%u-%lu sub_id=%lu\n", (unsigned)domain_id, (unsigned)server_id, (ulong)seq_no, (ulong)sub_id)); if ((rec= my_hash_search(&hash, (const uchar *)&domain_id, 0))) -- cgit v1.2.1 From 06ffea8c5bfc351cace3997f4ea54b8dcfce2ac4 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 27 Feb 2013 21:10:40 +0100 Subject: MDEV-26: Global Transaction ID Fix that CHANGE MASTER ... MASTER_GTID_POS="" works to start from the very beginning of the binary log (with test case). Fix that not finding the requested GTID position in master binlog results in fatal error, not endless connect retry. --- sql/sql_repl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index d9f4c1f0b0e..db4153becdf 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1292,7 +1292,7 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, } if ((errmsg= gtid_find_binlog_file(>id_state, search_file_name))) { - my_errno= ER_UNKNOWN_ERROR; + my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; goto err; } pos= 4; -- cgit v1.2.1 From 008363e87cad08b88ca0c0ef68131e2f1672f794 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Mar 2013 16:16:55 +0100 Subject: MDEV-26: Global transaction ID. Add a missing check for correct table definition. --- sql/sql_repl.cc | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index db4153becdf..5eeddd66966 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -3076,17 +3076,11 @@ rpl_load_gtid_slave_state(THD *thd) goto end; table_opened= true; table= tlist.table; - table->no_replicate= 1; - /* - ToDo: Check the table definition, error if not as expected. - We need the correct first 4 columns with correct type, and the primary key. - */ + if ((err= gtid_check_rpl_slave_state_table(table))) + goto end; - bitmap_set_bit(table->read_set, table->field[0]->field_index); - bitmap_set_bit(table->read_set, table->field[1]->field_index); - bitmap_set_bit(table->read_set, table->field[2]->field_index); - bitmap_set_bit(table->read_set, table->field[3]->field_index); + bitmap_set_all(table->read_set); if ((err= table->file->ha_rnd_init_with_error(1))) goto end; table_scanned= true; -- cgit v1.2.1 From 379819d8c06ee086e9a3f5cf7e6c225fdb626596 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 14 Mar 2013 10:39:16 +0100 Subject: MDEV-26: Global transaction ID. After-rebase fixes. Rebase was done to be able to build on top of the binlog background thread of MDEV-532. --- sql/sql_repl.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 5eeddd66966..fb2c75e0902 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -742,7 +742,8 @@ gtid_find_binlog_file(slave_connection_state *state, char *out_name) char buf[FN_REFLEN]; bzero((char*) &cache, sizeof(cache)); - init_alloc_root(&memroot, 10*(FN_REFLEN+sizeof(binlog_file_entry)), 0); + init_alloc_root(&memroot, 10*(FN_REFLEN+sizeof(binlog_file_entry)), 0, + MYF(MY_THREAD_SPECIFIC)); if (!(list= get_binlog_list(&memroot))) { errormsg= "Out of memory while looking for GTID position in binlog"; -- cgit v1.2.1 From 9d9ddad759a432967934baac596ef20d613214b3 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 18 Mar 2013 15:09:36 +0100 Subject: MDEV-26: Global transaction ID. Fix things so that a master can switch with MASTER_GTID_POS=AUTO to a slave that was previously running with log_slave_updates=0, by looking into the slave replication state on the master when the slave requests something not present in the binlog. Be a bit more strict about what position the slave can ask for, to avoid some easy-to-hit misconfiguration errors. Start over with seq_no counter when RESET MASTER. --- sql/sql_repl.cc | 378 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 252 insertions(+), 126 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index fb2c75e0902..d369dbc4dc2 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -698,6 +698,17 @@ get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list) /* Check if every GTID requested by the slave is contained in this (or a later) binlog file. Return true if so, false if not. + + We do the check with a single scan of the list of GTIDs, avoiding the need + to build an in-memory hash or stuff like that. + + We need to check that slave did not request GTID D-S-N1, when the + Gtid_list_log_event for this binlog file has D-S-N2 with N2 >= N1. + + In addition, we need to check that we do not have a GTID D-S-N3 in the + Gtid_list_log_event where D is not present in the requested slave state at + all. Since if D is not in requested slave state, it means that slave needs + to start at the very first GTID in domain D. */ static bool contains_all_slave_gtid(slave_connection_state *st, Gtid_list_log_event *glev) @@ -707,21 +718,189 @@ contains_all_slave_gtid(slave_connection_state *st, Gtid_list_log_event *glev) for (i= 0; i < glev->count; ++i) { const rpl_gtid *gtid= st->find(glev->list[i].domain_id); - if (gtid != NULL && - gtid->server_id == glev->list[i].server_id && + if (!gtid) + { + /* + The slave needs to start from the very beginning of this domain, which + is in an earlier binlog file. So we need to search back further. + */ + return false; + } + if (gtid->server_id == glev->list[i].server_id && gtid->seq_no <= glev->list[i].seq_no) { /* The slave needs to receive gtid, but it is contained in an earlier - binlog file. So we need to serch back further. + binlog file. So we need to search back further. */ return false; } } + return true; } +/* + Check the start GTID state requested by the slave against our binlog state. + + Give an error if the slave requests something that we do not have in our + binlog. + + T +*/ + +static int +check_slave_start_position(THD *thd, slave_connection_state *st, + const char **errormsg, rpl_gtid *error_gtid) +{ + uint32 i; + bool found; + int err; + rpl_gtid **delete_list= NULL; + uint32 delete_idx= 0; + bool slave_state_loaded= false; + uint32 missing_domains= 0; + rpl_gtid missing_domain_gtid; + + for (i= 0; i < st->hash.records; ++i) + { + rpl_gtid *slave_gtid= (rpl_gtid *)my_hash_element(&st->hash, i); + rpl_gtid master_gtid; + rpl_gtid master_replication_gtid; + rpl_gtid start_gtid; + + if ((found= mysql_bin_log.find_in_binlog_state(slave_gtid->domain_id, + slave_gtid->server_id, + &master_gtid)) && + master_gtid.seq_no >= slave_gtid->seq_no) + continue; + + if (!slave_state_loaded) + { + if (rpl_load_gtid_slave_state(thd)) + { + *errormsg= "Failed to load replication slave GTID state"; + err= ER_CANNOT_LOAD_SLAVE_GTID_STATE; + goto end; + } + slave_state_loaded= true; + } + + if (!rpl_global_gtid_slave_state.domain_to_gtid(slave_gtid->domain_id, + &master_replication_gtid) || + slave_gtid->server_id != master_replication_gtid.server_id || + slave_gtid->seq_no != master_replication_gtid.seq_no) + { + rpl_gtid domain_gtid; + + if (!mysql_bin_log.lookup_domain_in_binlog_state(slave_gtid->domain_id, + &domain_gtid)) + { + /* + We do not have anything in this domain, neither in the binlog nor + in the slave state. So we are probably one master in a multi-master + setup, and this domain is served by a different master. + + This is not an error, however if we are missing _all_ domains + requested by the slave, then we still give error (below, after + the loop). + */ + if (!(missing_domains++)) + missing_domain_gtid= domain_gtid; + continue; + } + *errormsg= "Requested slave GTID state not found in binlog"; + *error_gtid= *slave_gtid; + err= ER_GTID_POSITION_NOT_FOUND_IN_BINLOG; + goto end; + } + + /* + Ok, so connecting slave asked to start at a GTID that we do not have in + our binlog, but it was in fact the last GTID we applied earlier, when we + were acting as a replication slave. + + So this means that we were running as a replication slave without + --log-slave-updates, but now we switched to be a master. It is worth it + to handle this special case, as it allows users to run a simple + master -> slave without --log-slave-updates, and then exchange slave and + master, as long as they make sure the slave is caught up before switching. + */ + + /* + First check if we logged something ourselves as a master after being a + slave. This will be seen as a GTID with our own server_id and bigger + seq_no than what is in the slave state. + + If we did not log anything ourselves, then start the connecting slave + replicating from the current binlog end position, which in this case + corresponds to our replication slave state and hence what the connecting + slave is requesting. + */ + if (mysql_bin_log.find_in_binlog_state(slave_gtid->domain_id, + global_system_variables.server_id, + &start_gtid) && + start_gtid.seq_no > slave_gtid->seq_no) + { + /* + Start replication within this domain at the first GTID that we logged + ourselves after becoming a master. + */ + slave_gtid->server_id= global_system_variables.server_id; + } + else if (mysql_bin_log.lookup_domain_in_binlog_state(slave_gtid->domain_id, + &start_gtid)) + { + slave_gtid->server_id= start_gtid.server_id; + slave_gtid->seq_no= start_gtid.seq_no; + } + else + { + /* + We do not have _anything_ in our own binlog for this domain. Just + delete the entry in the slave connection state, then it will pick up + anything new that arrives. + + We just queue up the deletion and do it later, after the loop, so that + we do not mess up the iteration over the hash. + */ + if (!delete_list) + { + if ((delete_list= (rpl_gtid **)my_malloc(sizeof(*delete_list), + MYF(MY_WME)))) + { + *errormsg= "Out of memory while checking slave start position"; + err= ER_OUT_OF_RESOURCES; + goto end; + } + } + delete_list[delete_idx++]= slave_gtid; + } + } + + if (missing_domains == st->hash.records && missing_domains > 0) + { + *errormsg= "Requested slave GTID state not found in binlog"; + *error_gtid= missing_domain_gtid; + err= ER_GTID_POSITION_NOT_FOUND_IN_BINLOG; + goto end; + } + + /* Do any delayed deletes from the hash. */ + if (delete_list) + { + for (i= 0; i < delete_idx; ++i) + st->remove(delete_list[i]); + } + err= 0; + +end: + if (delete_list) + my_free(delete_list); + return err; +} + /* Find the name of the binlog file to start reading for a slave that connects using GTID state. @@ -1217,6 +1396,7 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, String connect_gtid_state(str_buf, sizeof(str_buf), system_charset_info); bool using_gtid_state; slave_connection_state gtid_state, return_gtid_state; + rpl_gtid error_gtid; enum_gtid_skip_type gtid_skip_group= GTID_SKIP_NOT; uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; @@ -1291,6 +1471,12 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, my_errno= ER_UNKNOWN_ERROR; goto err; } + if ((error= check_slave_start_position(thd, >id_state, &errmsg, + &error_gtid))) + { + my_errno= error; + goto err; + } if ((errmsg= gtid_find_binlog_file(>id_state, search_file_name))) { my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; @@ -1812,6 +1998,22 @@ err: my_basename(p_coord->file_name), p_coord->pos, my_basename(log_file_name), my_b_tell(&log)); } + else if (my_errno == ER_GTID_POSITION_NOT_FOUND_IN_BINLOG) + { + my_snprintf(error_text, sizeof(error_text), + "Error: connecting slave requested to start from GTID " + "%u-%u-%llu, which is not in the master's binlog", + error_gtid.domain_id, error_gtid.server_id, error_gtid.seq_no); + /* Use this error code so slave will know not to try reconnect. */ + my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG; + } + else if (my_errno == ER_CANNOT_LOAD_SLAVE_GTID_STATE) + { + my_snprintf(error_text, sizeof(error_text), + "Failed to load replication slave GTID state from table %s.%s", + "mysql", rpl_gtid_slave_state_table_name.str); + my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG; + } else strcpy(error_text, errmsg); end_io_cache(&log); @@ -2296,6 +2498,53 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) ret= TRUE; goto err; } + + /* + Check our own binlog for any of our own transactions that are newer + than the GTID state the user is requesting. Any such transactions would + result in an out-of-order binlog, which could break anyone replicating + with us as master. + + So give an error if this is found, requesting the user to do a + RESET MASTER (to clean up the binlog) if they really want this. + */ + if (mysql_bin_log.is_open()) + { + rpl_gtid *binlog_gtid_list= NULL; + uint32 num_binlog_gtids= 0; + uint32 i; + + if (mysql_bin_log.get_most_recent_gtid_list(&binlog_gtid_list, + &num_binlog_gtids)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(MY_WME)); + ret= TRUE; + goto err; + } + for (i= 0; i < num_binlog_gtids; ++i) + { + rpl_gtid *binlog_gtid= &binlog_gtid_list[i]; + rpl_gtid *slave_gtid; + if (binlog_gtid->server_id != global_system_variables.server_id) + continue; + if (!(slave_gtid= tmp_slave_state.find(binlog_gtid->domain_id))) + continue; + if (slave_gtid->seq_no < binlog_gtid->seq_no) + { + my_error(ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG, MYF(0), + slave_gtid->domain_id, slave_gtid->server_id, + slave_gtid->seq_no, binlog_gtid->domain_id, + binlog_gtid->server_id, binlog_gtid->seq_no); + break; + } + } + my_free(binlog_gtid_list); + if (i != num_binlog_gtids) + { + ret= TRUE; + goto err; + } + } } thd_proc_info(thd, "Changing master"); @@ -3042,127 +3291,4 @@ rpl_deinit_gtid_slave_state() rpl_global_gtid_slave_state.deinit(); } - -int -rpl_load_gtid_slave_state(THD *thd) -{ - TABLE_LIST tlist; - TABLE *table; - bool table_opened= false; - bool table_scanned= false; - struct local_element { uint64 sub_id; rpl_gtid gtid; }; - struct local_element *entry; - HASH hash; - int err= 0; - uint32 i; - DBUG_ENTER("rpl_load_gtid_slave_state"); - - my_hash_init(&hash, &my_charset_bin, 32, - offsetof(local_element, gtid) + offsetof(rpl_gtid, domain_id), - sizeof(uint32), NULL, my_free, HASH_UNIQUE); - - rpl_global_gtid_slave_state.lock(); - bool loaded= rpl_global_gtid_slave_state.loaded; - rpl_global_gtid_slave_state.unlock(); - if (loaded) - goto end; - - mysql_reset_thd_for_next_command(thd, 0); - - tlist.init_one_table(STRING_WITH_LEN("mysql"), - rpl_gtid_slave_state_table_name.str, - rpl_gtid_slave_state_table_name.length, - NULL, TL_READ); - if ((err= open_and_lock_tables(thd, &tlist, FALSE, 0))) - goto end; - table_opened= true; - table= tlist.table; - - if ((err= gtid_check_rpl_slave_state_table(table))) - goto end; - - bitmap_set_all(table->read_set); - if ((err= table->file->ha_rnd_init_with_error(1))) - goto end; - table_scanned= true; - for (;;) - { - uint32 domain_id, server_id; - uint64 sub_id, seq_no; - uchar *rec; - - if ((err= table->file->ha_rnd_next(table->record[0]))) - { - if (err == HA_ERR_RECORD_DELETED) - continue; - else if (err == HA_ERR_END_OF_FILE) - break; - else - goto end; - } - domain_id= (ulonglong)table->field[0]->val_int(); - sub_id= (ulonglong)table->field[1]->val_int(); - server_id= (ulonglong)table->field[2]->val_int(); - seq_no= (ulonglong)table->field[3]->val_int(); - DBUG_PRINT("info", ("Read slave state row: %u-%u-%lu sub_id=%lu\n", - (unsigned)domain_id, (unsigned)server_id, - (ulong)seq_no, (ulong)sub_id)); - if ((rec= my_hash_search(&hash, (const uchar *)&domain_id, 0))) - { - entry= (struct local_element *)rec; - if (entry->sub_id >= sub_id) - continue; - } - else - { - if (!(entry= (struct local_element *)my_malloc(sizeof(*entry), - MYF(MY_WME)))) - { - err= 1; - goto end; - } - if ((err= my_hash_insert(&hash, (uchar *)entry))) - { - my_free(entry); - goto end; - } - } - - entry->sub_id= sub_id; - entry->gtid.domain_id= domain_id; - entry->gtid.server_id= server_id; - entry->gtid.seq_no= seq_no; - } - - rpl_global_gtid_slave_state.lock(); - for (i= 0; i < hash.records; ++i) - { - entry= (struct local_element *)my_hash_element(&hash, i); - if ((err= rpl_global_gtid_slave_state.update(entry->gtid.domain_id, - entry->gtid.server_id, - entry->sub_id, - entry->gtid.seq_no))) - { - rpl_global_gtid_slave_state.unlock(); - goto end; - } - } - rpl_global_gtid_slave_state.loaded= true; - rpl_global_gtid_slave_state.unlock(); - - err= 0; /* Clear HA_ERR_END_OF_FILE */ - -end: - if (table_scanned) - { - table->file->ha_index_or_rnd_end(); - ha_commit_trans(thd, FALSE); - ha_commit_trans(thd, TRUE); - } - if (table_opened) - close_thread_tables(thd); - my_hash_free(&hash); - DBUG_RETURN(err); -} - #endif /* HAVE_REPLICATION */ -- cgit v1.2.1 From 9bb989a9d196755fe411551f27e6198ef6819159 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 21 Mar 2013 11:03:31 +0100 Subject: MDEV-26: Global transaction ID. Fix MDEV-4275 - I/O thread restart duplicates events in the relay log. The first time we connect to master after CHANGE MASTER or restart, we connect from the GTID position. But then subsequent reconnects or IO thread restarts reconnect with the old-style file/offset binlog pos from where it left off at last disconnect. This is necessary to avoid duplicate events in the relay logs, as there is nothing that synchronises the SQL thread update of GTID state (multiple threads in case of multi-source) with IO thread reconnects. Test cases. Some small cleanups and fixes. --- sql/sql_repl.cc | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index d369dbc4dc2..572b0e67ae3 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -806,8 +806,9 @@ check_slave_start_position(THD *thd, slave_connection_state *st, requested by the slave, then we still give error (below, after the loop). */ - if (!(missing_domains++)) - missing_domain_gtid= domain_gtid; + if (!missing_domains) + missing_domain_gtid= *slave_gtid; + ++missing_domains; continue; } *errormsg= "Requested slave GTID state not found in binlog"; @@ -1176,10 +1177,6 @@ gtid_state_from_binlog_pos(const char *in_name, uint32 pos, String *out_str) } -enum enum_gtid_skip_type { - GTID_SKIP_NOT, GTID_SKIP_STANDALONE, GTID_SKIP_TRANSACTION -}; - /* Helper function for mysql_binlog_send() to write an event down the slave connection. @@ -1234,12 +1231,7 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, switch (*gtid_skip_group) { case GTID_SKIP_STANDALONE: - if (event_type != GTID_EVENT && - event_type != INTVAR_EVENT && - event_type != RAND_EVENT && - event_type != USER_VAR_EVENT && - event_type != TABLE_MAP_EVENT && - event_type != ANNOTATE_ROWS_EVENT) + if (!Log_event::is_part_of_group(event_type)) *gtid_skip_group= GTID_SKIP_NOT; return NULL; case GTID_SKIP_TRANSACTION: @@ -2713,11 +2705,11 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) } if (lex_mi->gtid_pos_auto || lex_mi->gtid_pos_str.str) - mi->gtid_pos_auto= true; + mi->using_gtid= true; else if (lex_mi->gtid_pos_str.str || lex_mi->log_file_name || lex_mi->pos || lex_mi->relay_log_name || lex_mi->relay_log_pos) - mi->gtid_pos_auto= false; + mi->using_gtid= false; /* If user did specify neither host nor port nor any log name nor any log @@ -2783,6 +2775,16 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) ret= TRUE; goto err; } + + if (mi->using_gtid) + { + /* + Clear the position in the master binlogs, so that we request the + correct GTID position. + */ + mi->master_log_name[0]= 0; + mi->master_log_pos= 0; + } } else { -- cgit v1.2.1 From e590f89114bd205e30488cb8b1433f645babc170 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 21 Mar 2013 12:16:04 +0100 Subject: MDEV-26: Global transaction ID. Fix MDEV-4278: Slave does not check that master understands GTID. Now the slave will abort with a suitable error if an attempt is made to connect with GTID to a master that does not support GTID. --- sql/sql_repl.cc | 1 + 1 file changed, 1 insertion(+) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 572b0e67ae3..5b52163eb0e 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1419,6 +1419,7 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, connect_gtid_state.length(0); using_gtid_state= get_slave_connect_state(thd, &connect_gtid_state); + DBUG_EXECUTE_IF("simulate_non_gtid_aware_master", using_gtid_state= false;); if (global_system_variables.log_warnings > 1) sql_print_information("Start binlog_dump to slave_server(%d), pos(%s, %lu)", -- cgit v1.2.1 From a3f651d0f635df1a29d152c7088150b3ba144b72 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 22 Mar 2013 11:26:28 +0100 Subject: MDEV-26: Global transaction ID. Fix checksums for the new GTID events (reported by Pavel Ivanov). Fix printing of GTID_LIST_EVENT. --- sql/sql_repl.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 5b52163eb0e..bf758c4bdb0 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1420,6 +1420,17 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, connect_gtid_state.length(0); using_gtid_state= get_slave_connect_state(thd, &connect_gtid_state); DBUG_EXECUTE_IF("simulate_non_gtid_aware_master", using_gtid_state= false;); + /* + We want to corrupt the first event, in Log_event::read_log_event(). + But we do not want the corruption to happen early, eg. when client does + BINLOG_GTID_POS(). So test case sets a DBUG trigger which causes us to + set the real DBUG injection here. + */ + DBUG_EXECUTE_IF("corrupt_read_log_event2_set", + { + DBUG_SET("-d,corrupt_read_log_event2_set"); + DBUG_SET("+d,corrupt_read_log_event2"); + }); if (global_system_variables.log_warnings > 1) sql_print_information("Start binlog_dump to slave_server(%d), pos(%s, %lu)", -- cgit v1.2.1 From f66e3474a82e0b44c4738f8c722305bb1a65cee7 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 26 Mar 2013 14:58:14 +0100 Subject: MDEV-26: Global transaction ID. Fix missing error check for applying Gtid_log_event. Fix a couple compiler warnings. --- sql/sql_repl.cc | 1 + 1 file changed, 1 insertion(+) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index bf758c4bdb0..3c6a4588c36 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1400,6 +1400,7 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, DBUG_PRINT("enter",("log_ident: '%s' pos: %ld", log_ident, (long) pos)); bzero((char*) &log,sizeof(log)); + bzero(&error_gtid, sizeof(error_gtid)); /* heartbeat_period from @master_heartbeat_period user variable */ -- cgit v1.2.1 From 20e021115fac345c8c6a9b6c4d6512e0f93f816a Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 27 Mar 2013 09:37:54 +0100 Subject: MDEV-26: Global transaction ID. Fix MDEV-4329. When user does CHANGE MASTER TO MASTER_GTID_POS='', we check that this state does not conflict with the binlog. But the code forgot to give an error in the case where a domain was completely missing from the requested position (eg. MASTER_GTID_POS=''). --- sql/sql_repl.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 3c6a4588c36..57e85810808 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -2533,7 +2533,12 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) if (binlog_gtid->server_id != global_system_variables.server_id) continue; if (!(slave_gtid= tmp_slave_state.find(binlog_gtid->domain_id))) - continue; + { + my_error(ER_MASTER_GTID_POS_MISSING_DOMAIN, MYF(0), + binlog_gtid->domain_id, binlog_gtid->domain_id, + binlog_gtid->server_id, binlog_gtid->seq_no); + break; + } if (slave_gtid->seq_no < binlog_gtid->seq_no) { my_error(ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG, MYF(0), -- cgit v1.2.1 From cb65cee82a192e8e0fa77dcc7d49cec0d5334ba0 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 27 Mar 2013 12:29:02 +0100 Subject: MDEV-26: Global transaction ID. When slave requested to start at some GTID, and that GTID was the very last event (within its replication domain) in some binlog file, we did not allow the binlog dump thread on the master to start from the beginning of a following binlog file. This is a problem, since the binlog file containing the GTID is likely to be purged if the replication domain is unused for long. With this fix, if the Gtid list event at the start of a binlog file contains exactly the GTID requested by the slave, we allow to start the binlog dump thread from this file, taking care not to skip any events from that domain in the file. --- sql/sql_repl.cc | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 57e85810808..f6f47399271 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -703,7 +703,7 @@ get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list) to build an in-memory hash or stuff like that. We need to check that slave did not request GTID D-S-N1, when the - Gtid_list_log_event for this binlog file has D-S-N2 with N2 >= N1. + Gtid_list_log_event for this binlog file has D-S-N2 with N2 > N1. In addition, we need to check that we do not have a GTID D-S-N3 in the Gtid_list_log_event where D is not present in the requested slave state at @@ -727,7 +727,7 @@ contains_all_slave_gtid(slave_connection_state *st, Gtid_list_log_event *glev) return false; } if (gtid->server_id == glev->list[i].server_id && - gtid->seq_no <= glev->list[i].seq_no) + gtid->seq_no < glev->list[i].seq_no) { /* The slave needs to receive gtid, but it is contained in an earlier @@ -909,6 +909,25 @@ end: Returns the file name in out_name, which must be of size at least FN_REFLEN. Returns NULL on ok, error message on error. + + In case of non-error return, the returned binlog file is guaranteed to + contain the first event to be transmitted to the slave for every domain + present in our binlogs. It is still necessary to skip all GTIDs up to + and including the GTID requested by slave within each domain. + + However, as a special case, if the event to be sent to the slave is the very + first event (within that domain) in the returned binlog, then nothing should + be skipped, so that domain is deleted from the passed in slave connection + state. + + This is necessary in case the slave requests a GTID within a replication + domain that has long been inactive. The binlog file containing that GTID may + have been long since purged. However, as long as no GTIDs after that have + been purged, we have the GTID requested by slave in the Gtid_list_log_event + of the latest binlog. So we can start from there, as long as we delete the + corresponding entry in the slave state so we do not wrongly skip any events + that might turn up if that domain becomes active again, vainly looking for + the requested GTID that was already purged. */ static const char * gtid_find_binlog_file(slave_connection_state *state, char *out_name) @@ -958,7 +977,37 @@ gtid_find_binlog_file(slave_connection_state *state, char *out_name) if (!glev || contains_all_slave_gtid(state, glev)) { + uint32 i; + strmake(out_name, buf, FN_REFLEN); + + /* + As a special case, we allow to start from binlog file N if the + requested GTID is the last event (in the corresponding domain) in + binlog file (N-1), but then we need to remove that GTID from the slave + state, rather than skipping events waiting for it to turn up. + */ + for (i= 0; i < glev->count; ++i) + { + const rpl_gtid *gtid= state->find(glev->list[i].domain_id); + if (!gtid) + { + /* contains_all_slave_gtid() would have returned false if so. */ + DBUG_ASSERT(0); + continue; + } + if (gtid->server_id == glev->list[i].server_id && + gtid->seq_no == glev->list[i].seq_no) + { + /* + The slave requested to start from the very beginning of this + domain in this binlog file. So delete the entry from the state, + we do not need to skip anything. + */ + state->remove(gtid); + } + } + goto end; } delete glev; -- cgit v1.2.1 From 0fdbdde4747445cfb0d7fbc2e8fc0e766be70c8b Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 27 Mar 2013 16:06:45 +0100 Subject: MDEV-26: Global transaction ID. Implement test case rpl_gtid_stop_start.test to test normal stop and restart of master and slave mysqld servers. Fix a couple bugs found with the test: - When InnoDB is disabled (no XA), the binlog state was not read when master mysqld starts. - Remove old code that puts a bogus D-S-0 into the initial binlog state, it is not correct in current design. - Fix memory leak in gtid_find_binlog_file(). --- sql/sql_repl.cc | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index f6f47399271..0f8dbdd5112 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -936,11 +936,8 @@ gtid_find_binlog_file(slave_connection_state *state, char *out_name) binlog_file_entry *list; Gtid_list_log_event *glev= NULL; const char *errormsg= NULL; - IO_CACHE cache; - File file = (File)-1; char buf[FN_REFLEN]; - bzero((char*) &cache, sizeof(cache)); init_alloc_root(&memroot, 10*(FN_REFLEN+sizeof(binlog_file_entry)), 0, MYF(MY_THREAD_SPECIFIC)); if (!(list= get_binlog_list(&memroot))) @@ -951,6 +948,9 @@ gtid_find_binlog_file(slave_connection_state *state, char *out_name) while (list) { + File file; + IO_CACHE cache; + if (!list->next) { /* @@ -971,8 +971,13 @@ gtid_find_binlog_file(slave_connection_state *state, char *out_name) "GTID position in binlog"; goto end; } - if ((file= open_binlog(&cache, buf, &errormsg)) == (File)-1 || - (errormsg= get_gtid_list_event(&cache, &glev))) + bzero((char*) &cache, sizeof(cache)); + if ((file= open_binlog(&cache, buf, &errormsg)) == (File)-1) + goto end; + errormsg= get_gtid_list_event(&cache, &glev); + end_io_cache(&cache); + mysql_file_close(file, MYF(MY_WME)); + if (errormsg) goto end; if (!glev || contains_all_slave_gtid(state, glev)) @@ -1023,11 +1028,6 @@ gtid_find_binlog_file(slave_connection_state *state, char *out_name) end: if (glev) delete glev; - if (file != (File)-1) - { - end_io_cache(&cache); - mysql_file_close(file, MYF(MY_WME)); - } free_root(&memroot, MYF(0)); return errormsg; -- cgit v1.2.1 From c2cbc9cee6a3f8f745501ee2f5fcfd4586f53bbd Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 4 Apr 2013 17:38:10 +0200 Subject: MDEV-26: Global transaction ID. Move combining slave and gtid binlog state into a separate function. Make SHOW ALL SLAVES STATUS use the same function, so it shows the same value used by slave connect. Add a test case. --- sql/sql_repl.cc | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 0f8dbdd5112..ad5e5f64dad 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -3360,4 +3360,30 @@ rpl_deinit_gtid_slave_state() rpl_global_gtid_slave_state.deinit(); } + +/* + Format the current GTID state as a string, for use when connecting to a + master server with GTID, or for returning the value of @@global.gtid_state. + + If the flag use_binlog is true, then the contents of the binary log (if + enabled) is merged into the current GTID state. +*/ +int +rpl_append_gtid_state(String *dest, bool use_binlog) +{ + int err; + rpl_gtid *gtid_list= NULL; + uint32 num_gtids= 0; + + if (opt_bin_log && + (err= mysql_bin_log.get_most_recent_gtid_list(>id_list, &num_gtids))) + return err; + + rpl_global_gtid_slave_state.tostring(dest, gtid_list, num_gtids); + my_free(gtid_list); + + return 0; +} + + #endif /* HAVE_REPLICATION */ -- cgit v1.2.1 From b7363eb4acd958bfac73ecad67367c44a6fa830d Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 5 Apr 2013 16:20:58 +0200 Subject: MDEV-26: Global transaction ID. Replace CHANGE MASTER TO ... master_gtid_pos='xxx' with a new system variable @@global.gtid_pos. This is more logical; @@gtid_pos is global, not per-master, and it is not affected by RESET SLAVE. Also rename master_gtid_pos=AUTO to master_use_gtid=1, which again is more logical. --- sql/sql_repl.cc | 162 ++++++++++++++++++++++++++------------------------------ 1 file changed, 76 insertions(+), 86 deletions(-) (limited to 'sql/sql_repl.cc') diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index ad5e5f64dad..c2fcf67fc21 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -2503,7 +2503,6 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) char relay_log_info_file_tmp[FN_REFLEN]; my_off_t saved_log_pos; LEX_MASTER_INFO* lex_mi= &thd->lex->mi; - slave_connection_state tmp_slave_state; DBUG_ENTER("change_master"); *master_info_added= false; @@ -2534,78 +2533,6 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) goto err; } - if (lex_mi->gtid_pos_str.str) - { - if (master_info_index->give_error_if_slave_running()) - { - ret= TRUE; - goto err; - } - /* - First load it into a dummy object, to check for parse errors. - We do not want to wipe the previous state if there is an error - in the syntax of the new state! - */ - if (tmp_slave_state.load(lex_mi->gtid_pos_str.str, - lex_mi->gtid_pos_str.length)) - { - ret= TRUE; - goto err; - } - - /* - Check our own binlog for any of our own transactions that are newer - than the GTID state the user is requesting. Any such transactions would - result in an out-of-order binlog, which could break anyone replicating - with us as master. - - So give an error if this is found, requesting the user to do a - RESET MASTER (to clean up the binlog) if they really want this. - */ - if (mysql_bin_log.is_open()) - { - rpl_gtid *binlog_gtid_list= NULL; - uint32 num_binlog_gtids= 0; - uint32 i; - - if (mysql_bin_log.get_most_recent_gtid_list(&binlog_gtid_list, - &num_binlog_gtids)) - { - my_error(ER_OUT_OF_RESOURCES, MYF(MY_WME)); - ret= TRUE; - goto err; - } - for (i= 0; i < num_binlog_gtids; ++i) - { - rpl_gtid *binlog_gtid= &binlog_gtid_list[i]; - rpl_gtid *slave_gtid; - if (binlog_gtid->server_id != global_system_variables.server_id) - continue; - if (!(slave_gtid= tmp_slave_state.find(binlog_gtid->domain_id))) - { - my_error(ER_MASTER_GTID_POS_MISSING_DOMAIN, MYF(0), - binlog_gtid->domain_id, binlog_gtid->domain_id, - binlog_gtid->server_id, binlog_gtid->seq_no); - break; - } - if (slave_gtid->seq_no < binlog_gtid->seq_no) - { - my_error(ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG, MYF(0), - slave_gtid->domain_id, slave_gtid->server_id, - slave_gtid->seq_no, binlog_gtid->domain_id, - binlog_gtid->server_id, binlog_gtid->seq_no); - break; - } - } - my_free(binlog_gtid_list); - if (i != num_binlog_gtids) - { - ret= TRUE; - goto err; - } - } - } - thd_proc_info(thd, "Changing master"); create_logfile_name_with_suffix(master_info_file_tmp, @@ -2771,9 +2698,9 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) mi->rli.group_relay_log_pos= mi->rli.event_relay_log_pos= lex_mi->relay_log_pos; } - if (lex_mi->gtid_pos_auto || lex_mi->gtid_pos_str.str) + if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_MI_ENABLE) mi->using_gtid= true; - else if (lex_mi->gtid_pos_str.str || + else if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_MI_DISABLE || lex_mi->log_file_name || lex_mi->pos || lex_mi->relay_log_name || lex_mi->relay_log_pos) mi->using_gtid= false; @@ -2809,17 +2736,6 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) sizeof(mi->master_log_name)-1); } - if (lex_mi->gtid_pos_str.str) - { - if (rpl_global_gtid_slave_state.load(thd, lex_mi->gtid_pos_str.str, - lex_mi->gtid_pos_str.length, true)) - { - my_error(ER_FAILED_GTID_STATE_INIT, MYF(0)); - ret= TRUE; - goto err; - } - } - /* Relay log's IO_CACHE may not be inited, if rli->inited==0 (server was never a slave before). @@ -3386,4 +3302,78 @@ rpl_append_gtid_state(String *dest, bool use_binlog) } +bool +rpl_gtid_pos_check(char *str, size_t len) +{ + slave_connection_state tmp_slave_state; + + /* Check that we can parse the supplied string. */ + if (tmp_slave_state.load(str, len)) + return true; + + /* + Check our own binlog for any of our own transactions that are newer + than the GTID state the user is requesting. Any such transactions would + result in an out-of-order binlog, which could break anyone replicating + with us as master. + + So give an error if this is found, requesting the user to do a + RESET MASTER (to clean up the binlog) if they really want this. + */ + if (mysql_bin_log.is_open()) + { + rpl_gtid *binlog_gtid_list= NULL; + uint32 num_binlog_gtids= 0; + uint32 i; + + if (mysql_bin_log.get_most_recent_gtid_list(&binlog_gtid_list, + &num_binlog_gtids)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(MY_WME)); + return true; + } + for (i= 0; i < num_binlog_gtids; ++i) + { + rpl_gtid *binlog_gtid= &binlog_gtid_list[i]; + rpl_gtid *slave_gtid; + if (binlog_gtid->server_id != global_system_variables.server_id) + continue; + if (!(slave_gtid= tmp_slave_state.find(binlog_gtid->domain_id))) + { + my_error(ER_MASTER_GTID_POS_MISSING_DOMAIN, MYF(0), + binlog_gtid->domain_id, binlog_gtid->domain_id, + binlog_gtid->server_id, binlog_gtid->seq_no); + break; + } + if (slave_gtid->seq_no < binlog_gtid->seq_no) + { + my_error(ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG, MYF(0), + slave_gtid->domain_id, slave_gtid->server_id, + slave_gtid->seq_no, binlog_gtid->domain_id, + binlog_gtid->server_id, binlog_gtid->seq_no); + break; + } + } + my_free(binlog_gtid_list); + if (i != num_binlog_gtids) + return true; + } + + return false; +} + + +bool +rpl_gtid_pos_update(THD *thd, char *str, size_t len) +{ + if (rpl_global_gtid_slave_state.load(thd, str, len, true)) + { + my_error(ER_FAILED_GTID_STATE_INIT, MYF(0)); + return true; + } + else + return false; +} + + #endif /* HAVE_REPLICATION */ -- cgit v1.2.1