/* Copyright (c) 2011, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is designed to work with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have either included with the program or referenced in the documentation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "my_config.h" #include #include #include #include #include #include "mysql/components/services/bits/psi_mutex_bits.h" #include "mysql/components/services/log_builtins.h" #include "mysql/my_loglevel.h" #include "mysql/utils/enumeration_utils.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #include "m_string.h" // my_strtoll #include "my_byteorder.h" #include "my_dbug.h" #include "my_inttypes.h" #include "my_stacktrace.h" // my_safe_printf_stderr #include "my_sys.h" #include "mysql/binlog/event/control_events.h" #include "mysql/psi/mysql_mutex.h" #include "mysql/service_mysql_alloc.h" #include "mysql/strings/int2str.h" #include "prealloced_array.h" #include "sql/rpl_gtid.h" #include "sql/sql_const.h" #include "sql/thr_malloc.h" #ifdef MYSQL_SERVER #include "mysql/psi/psi_memory.h" #include "mysqld_error.h" // ER_* #include "sql/log.h" #endif #ifndef MYSQL_SERVER #include "client/mysqlbinlog.h" #endif PSI_memory_key key_memory_Gtid_set_to_string; PSI_memory_key key_memory_Gtid_set_Interval_chunk; using std::list; using std::max; using std::min; #define MAX_NEW_CHUNK_ALLOCATE_TRIES 10 PSI_mutex_key Gtid_set::key_gtid_executed_free_intervals_mutex; const Gtid_set::String_format Gtid_set::default_string_format = { "", "", ":", ":", "-", ":", ",\n", "", 0, 0, 1, 1, 1, 1, 2, 0}; const Gtid_set::String_format Gtid_set::sql_string_format = { "'", "'", ":", ":", "-", ":", "',\n'", "''", 1, 1, 1, 1, 1, 1, 4, 2}; const Gtid_set::String_format Gtid_set::commented_string_format = { "# ", "", ":", ":", "-", ":", ",\n# ", "# [empty]", 2, 0, 1, 1, 1, 1, 4, 9}; Gtid_set::Gtid_set(Tsid_map *_tsid_map, Checkable_rwlock *_tsid_lock) : tsid_lock(_tsid_lock), tsid_map(_tsid_map), m_intervals(key_memory_Gtid_set_Interval_chunk) { init(); } Gtid_set::Gtid_set(Tsid_map *_tsid_map, const char *text, enum_return_status *status, Checkable_rwlock *_tsid_lock) : tsid_lock(_tsid_lock), tsid_map(_tsid_map), m_intervals(key_memory_Gtid_set_Interval_chunk) { assert(_tsid_map != nullptr); init(); *status = add_gtid_text(text); } void Gtid_set::init() { DBUG_TRACE; has_cached_string_length = false; cached_string_length = 0; cached_string_format = nullptr; chunks = nullptr; free_intervals = nullptr; if (tsid_lock) mysql_mutex_init(key_gtid_executed_free_intervals_mutex, &free_intervals_mutex, nullptr); #ifndef NDEBUG n_chunks = 0; #endif } Gtid_set::~Gtid_set() { DBUG_TRACE; Interval_chunk *chunk = chunks; while (chunk != nullptr) { Interval_chunk *next_chunk = chunk->next; my_free(chunk); chunk = next_chunk; #ifndef NDEBUG n_chunks--; #endif } assert(n_chunks == 0); if (tsid_lock) mysql_mutex_destroy(&free_intervals_mutex); } enum_return_status Gtid_set::ensure_sidno(rpl_sidno sidno) { DBUG_TRACE; if (tsid_lock != nullptr) tsid_lock->assert_some_lock(); DBUG_PRINT("info", ("sidno=%d get_max_sidno()=%d tsid_map=%p " "sid_map->get_max_sidno()=%d", sidno, get_max_sidno(), tsid_map, tsid_map != nullptr ? tsid_map->get_max_sidno() : 0)); assert(sidno <= tsid_map->get_max_sidno()); assert(get_max_sidno() <= tsid_map->get_max_sidno()); rpl_sidno max_sidno = get_max_sidno(); if (sidno > max_sidno) { /* Not all Gtid_sets are protected by an rwlock. But if this Gtid_set is, we assume that the read lock has been taken. Then we temporarily upgrade it to a write lock while resizing the array, and then we restore it to a read lock at the end. */ bool is_wrlock = false; if (tsid_lock != nullptr) { is_wrlock = tsid_lock->is_wrlock(); if (!is_wrlock) { tsid_lock->unlock(); tsid_lock->wrlock(); // maybe a concurrent thread already resized the Gtid_set // while we released the lock; check the condition again if (sidno <= max_sidno) { tsid_lock->unlock(); tsid_lock->rdlock(); RETURN_OK; } } } Interval *null_p = nullptr; for (rpl_sidno i = max_sidno; i < sidno; i++) if (m_intervals.push_back(null_p)) goto error; if (tsid_lock != nullptr) { if (!is_wrlock) { tsid_lock->unlock(); tsid_lock->rdlock(); } } } RETURN_OK; error: BINLOG_ERROR(("Out of memory."), (ER_OUT_OF_RESOURCES, MYF(0))); RETURN_REPORTED_ERROR; } void Gtid_set::add_interval_memory_lock_taken(int n_ivs, Interval *ivs) { DBUG_TRACE; assert_free_intervals_locked(); // make ivs a linked list for (int i = 0; i < n_ivs - 1; i++) ivs[i].next = &(ivs[i + 1]); Interval_iterator ivit(this); ivs[n_ivs - 1].next = ivit.get(); // add intervals to list of free intervals ivit.set(&(ivs[0])); } void Gtid_set::create_new_chunk(int size) { DBUG_TRACE; int i = 0; Interval_chunk *new_chunk = nullptr; assert_free_intervals_locked(); /* Try to allocate the new chunk in MAX_NEW_CHUNK_ALLOCATE_TRIES tries when encountering 'out of memory' situation. */ while (i < MAX_NEW_CHUNK_ALLOCATE_TRIES) { /* Allocate the new chunk. one element is already pre-allocated, so we only add size-1 elements to the size of the struct. */ new_chunk = (Interval_chunk *)my_malloc( key_memory_Gtid_set_Interval_chunk, sizeof(Interval_chunk) + sizeof(Interval) * (size - 1), MYF(MY_WME)); if (new_chunk != nullptr) { #ifdef MYSQL_SERVER if (i > 0) LogErr(WARNING_LEVEL, ER_RPL_GTID_MEMORY_FINALLY_AVAILABLE, i + 1); #endif break; } /* Sleep 1 microsecond per try to avoid temporary 'out of memory' */ my_sleep(1); i++; } /* Terminate the server after failed to allocate the new chunk in MAX_NEW_CHUNK_ALLOCATE_TRIES tries. */ if (MAX_NEW_CHUNK_ALLOCATE_TRIES == i || DBUG_EVALUATE_IF("rpl_simulate_new_chunk_allocate_failure", 1, 0)) { my_safe_print_system_time(); my_safe_printf_stderr("%s", "[Fatal] Out of memory while allocating " "a new chunk of intervals for storing GTIDs.\n"); _exit(MYSQLD_FAILURE_EXIT); } // store the chunk in the list of chunks new_chunk->next = chunks; chunks = new_chunk; #ifndef NDEBUG n_chunks++; #endif // add the intervals in the chunk to the list of free intervals add_interval_memory_lock_taken(size, new_chunk->intervals); } void Gtid_set::get_free_interval(Interval **out) { DBUG_TRACE; assert_free_intervals_locked(); Interval_iterator ivit(this); bool simulate_failure = DBUG_EVALUATE_IF( "rpl_gtid_get_free_interval_simulate_out_of_memory", true, false); if (simulate_failure) DBUG_SET("+d,rpl_simulate_new_chunk_allocate_failure"); if (ivit.get() == nullptr || simulate_failure) create_new_chunk(CHUNK_GROW_SIZE); *out = ivit.get(); ivit.set((*out)->next); } void Gtid_set::put_free_interval(Interval *iv) { DBUG_TRACE; assert_free_intervals_locked(); Interval_iterator ivit(this); iv->next = ivit.get(); ivit.set(iv); } void Gtid_set::clear() { DBUG_TRACE; has_cached_string_length = false; cached_string_length = 0; rpl_sidno max_sidno = get_max_sidno(); if (max_sidno == 0) return; Interval_iterator free_ivit(this); for (rpl_sidno sidno = 1; sidno <= max_sidno; sidno++) { /* Link in this list of intervals at the end of the list of free intervals. */ Interval_iterator ivit(this, sidno); Interval *iv = ivit.get(); if (iv != nullptr) { // find the end of the list of free intervals while (free_ivit.get() != nullptr) free_ivit.next(); // append the present list free_ivit.set(iv); // clear the pointer to the head of this list ivit.set(nullptr); } } } void Gtid_set::clear_set_and_tsid_map() { DBUG_TRACE; clear(); /* Cleaning the TSID map without cleaning up the Gtid_set intervals may lead to a condition were the Gtid_set->get_max_sidno() will be greater than the Tsid_map->get_max_sidno(). */ m_intervals.clear(); tsid_map->clear(); assert(get_max_sidno() == tsid_map->get_max_sidno()); } void Gtid_set::add_gno_interval(Interval_iterator *ivitp, rpl_gno start, rpl_gno end, Free_intervals_lock *lock) { DBUG_TRACE; assert(start > 0); assert(start < end); DBUG_PRINT("info", ("start=%" PRId64 " end=%" PRId64, start, end)); Interval *iv; Interval_iterator ivit = *ivitp; has_cached_string_length = false; cached_string_length = 0; while ((iv = ivit.get()) != nullptr) { if (iv->end >= start) { if (iv->start > end) // (start, end) is strictly before the current interval break; // (start, end) and (iv->start, iv->end) touch or intersect. // Save the start of the merged interval. if (iv->start < start) start = iv->start; // Remove the current interval as long as the new interval // intersects with the next interval. while (iv->next && end >= iv->next->start) { lock->lock_if_not_locked(); ivit.remove(this); iv = ivit.get(); } // Store the interval in the current interval. iv->start = start; if (iv->end < end) iv->end = end; *ivitp = ivit; return; } ivit.next(); } /* We come here if the interval cannot be combined with any existing interval: it is after the previous interval (if any) and before the current interval (if any). So we allocate a new interval and insert it at the current position. */ Interval *new_iv; lock->lock_if_not_locked(); get_free_interval(&new_iv); new_iv->start = start; new_iv->end = end; ivit.insert(new_iv); *ivitp = ivit; } void Gtid_set::remove_gno_interval(Interval_iterator *ivitp, rpl_gno start, rpl_gno end, Free_intervals_lock *lock) { DBUG_TRACE; assert(start < end); Interval_iterator ivit = *ivitp; Interval *iv; has_cached_string_length = false; cached_string_length = -1; // Skip intervals of 'this' that are completely before the removed interval. while (true) { iv = ivit.get(); if (iv == nullptr) goto ok; if (iv->end > start) break; ivit.next(); } // Now iv ends after the beginning of the removed interval. assert(iv != nullptr && iv->end > start); if (iv->start < start) { if (iv->end > end) { // iv cuts also the end of the removed interval: split iv in two Interval *new_iv; lock->lock_if_not_locked(); get_free_interval(&new_iv); new_iv->start = end; new_iv->end = iv->end; iv->end = start; ivit.next(); ivit.insert(new_iv); goto ok; } // iv cuts the beginning but not the end of the removed interval: // truncate iv, and iterate one step to next interval iv->end = start; ivit.next(); iv = ivit.get(); if (iv == nullptr) goto ok; } // Now iv starts after the beginning of the removed interval. assert(iv != nullptr && iv->start >= start); while (iv->end <= end) { // iv ends before the end of the removed interval, so it is // completely covered: remove iv. lock->lock_if_not_locked(); ivit.remove(this); iv = ivit.get(); if (iv == nullptr) goto ok; } // Now iv ends after the removed interval. assert(iv != nullptr && iv->end > end); if (iv->start < end) { // iv begins before the end of the removed interval: truncate iv iv->start = end; } ok: *ivitp = ivit; } rpl_gno parse_gno(const char **s) { char *endp; long long ret = my_strtoll(*s, &endp, 0); if (ret < 0 || ret >= GNO_END) return -1; *s = endp; return static_cast(ret); } int format_gno(char *s, rpl_gno gno) { return static_cast(longlong10_to_str(gno, s, 10) - s); } enum_return_status Gtid_set::add_gtid(const mysql::gtid::Gtid >id) { DBUG_TRACE; assert(tsid_map != nullptr); if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); rpl_sidno sidno = tsid_map->add_tsid(gtid.get_tsid()); if (sidno <= 0) { RETURN_REPORTED_ERROR; } PROPAGATE_REPORTED_ERROR(ensure_sidno(sidno)); _add_gtid(sidno, gtid.get_gno()); return RETURN_STATUS_OK; } enum_return_status Gtid_set::add_gtid_text(const char *text, bool *anonymous, bool *starts_with_plus) { DBUG_TRACE; assert(tsid_map != nullptr); if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); const char *s = text; DBUG_PRINT("info", ("adding '%s'", text)); if (anonymous != nullptr) *anonymous = false; if (starts_with_plus != nullptr) { SKIP_WHITESPACE(); if (*s == '+') { *starts_with_plus = true; s++; } else *starts_with_plus = false; } SKIP_WHITESPACE(); if (*s == 0) { DBUG_PRINT("info", ("'%s' is empty", text)); RETURN_OK; } Free_intervals_lock lock(this); DBUG_PRINT("info", ("'%s' not only whitespace", text)); // Allocate space for all intervals at once, if nothing is allocated. if (chunks == nullptr) { // compute number of intervals in text: it is equal to the number of // colons int n_intervals = 0; text = s; for (; *s; s++) if (*s == Gtid::gtid_separator) n_intervals++; // allocate all intervals in one chunk lock.lock_if_not_locked(); create_new_chunk(n_intervals); lock.unlock_if_locked(); s = text; } while (true) { // Skip commas (we allow empty SID:GNO specifications). while (*s == ',') { s++; SKIP_WHITESPACE(); } // We allow empty Gtid_sets containing only commas. if (*s == 0) { DBUG_PRINT("info", ("successfully parsed")); RETURN_OK; } if (anonymous != nullptr && strncmp(s, "ANONYMOUS", 9) == 0) { *anonymous = true; s += 9; } else { // Parse TSID. mysql::gtid::Tsid tsid; std::size_t characters_read = tsid.from_cstring(s); if (characters_read == 0) { DBUG_PRINT("info", ("expected UUID; found garbage '%.80s' at char %d in '%s'", s, (int)(s - text), text)); goto parse_error; } s += characters_read; rpl_sidno sidno = tsid_map->add_tsid(tsid); if (sidno <= 0) { RETURN_REPORTED_ERROR; } PROPAGATE_REPORTED_ERROR(ensure_sidno(sidno)); SKIP_WHITESPACE(); while (*s == Gtid::gtid_separator) { // Skip gtid_separator. ++s; // parse Tag if any mysql::gtid::Tag tag; auto tag_chars = tag.from_cstring(s); s += tag_chars; SKIP_WHITESPACE(); if (tag_chars > 0) { tsid = mysql::gtid::Tsid(tsid.get_uuid(), tag); sidno = tsid_map->add_tsid(tsid); if (sidno <= 0) { RETURN_REPORTED_ERROR; } PROPAGATE_REPORTED_ERROR(ensure_sidno(sidno)); } else { Interval_iterator ivit(this, sidno); // Read start of interval. rpl_gno start = parse_gno(&s); if (start <= 0) { if (start == 0) DBUG_PRINT("info", ("expected positive NUMBER; found zero " "('%.80s') at char %d in '%s'", s - 1, (int)(s - text) - 1, text)); else DBUG_PRINT("info", ("expected positive NUMBER; found zero or " "garbage '%.80s' at char %d in '%s'", s, (int)(s - text), text)); goto parse_error; } SKIP_WHITESPACE(); // Read end of interval. rpl_gno end; if (*s == '-') { s++; end = parse_gno(&s); if (end < 0) { DBUG_PRINT( "info", ("expected NUMBER; found garbage '%.80s' at char %d in '%s'", s, (int)(s - text), text)); goto parse_error; } end++; SKIP_WHITESPACE(); } else end = start + 1; if (end > start) { // Add interval. Use the existing iterator position if the // current interval does not begin before it. Otherwise iterate // from the beginning. Interval *current = ivit.get(); if (current == nullptr || start < current->start) ivit.init(this, sidno); add_gno_interval(&ivit, start, end, &lock); } } } } // Must be end of string or comma. (Commas are consumed and // end-of-loop is detected at the beginning of the loop.) if (*s != ',' && *s != 0) { DBUG_PRINT("info", ("expected end of string, UUID, or :NUMBER; found " "garbage '%.80s' at char %d in '%s'", s, (int)(s - text), text)); goto parse_error; } } assert(0); parse_error: BINLOG_ERROR(("Malformed Gtid_set specification '%.200s'.", text), (ER_MALFORMED_GTID_SET_SPECIFICATION, MYF(0), text)); RETURN_REPORTED_ERROR; } bool Gtid_set::is_valid(const char *text) { DBUG_TRACE; const char *s = text; SKIP_WHITESPACE(); if (*s == '+') s++; SKIP_WHITESPACE(); do { // Skip commas (we allow empty SID:GNO specifications). while (*s == ',') { s++; SKIP_WHITESPACE(); } if (*s == 0) return true; // Parse SID. mysql::gtid::Uuid uuid; if (uuid.parse(s, mysql::gtid::Uuid::TEXT_LENGTH) != 0) { return false; } s += mysql::gtid::Uuid::TEXT_LENGTH; SKIP_WHITESPACE(); mysql::gtid::Tag tag; // empty tag // Iterate over intervals. while (*s == Gtid::gtid_separator) { // Skip gtid_separator. s++; // Parse the next "gtid_separator" separated item, // which may be either a tag or an interval SKIP_WHITESPACE(); mysql::gtid::Tag tag_read; auto tag_len = tag_read.from_cstring(s); s += tag_len; if (tag_len > 0) { tag = tag_read; } else { // Read start of interval. if (parse_gno(&s) <= 0) return false; SKIP_WHITESPACE(); // Read end of interval if (*s == '-') { s++; if (parse_gno(&s) < 0) return false; SKIP_WHITESPACE(); } } } } while (*s == ','); if (*s != 0) return false; return true; } void Gtid_set::add_gno_intervals(rpl_sidno sidno, Const_interval_iterator other_ivit, Free_intervals_lock *lock) { DBUG_TRACE; assert(sidno >= 1 && sidno <= get_max_sidno()); const Interval *other_iv; Interval_iterator ivit(this, sidno); while ((other_iv = other_ivit.get()) != nullptr) { add_gno_interval(&ivit, other_iv->start, other_iv->end, lock); other_ivit.next(); } } void Gtid_set::remove_gno_intervals(rpl_sidno sidno, Const_interval_iterator other_ivit, Free_intervals_lock *lock) { DBUG_TRACE; assert(sidno >= 1 && sidno <= get_max_sidno()); const Interval *other_iv; Interval_iterator ivit(this, sidno); while ((other_iv = other_ivit.get()) != nullptr) { remove_gno_interval(&ivit, other_iv->start, other_iv->end, lock); if (ivit.get() == nullptr) break; other_ivit.next(); } } void Gtid_set::remove_intervals_for_sidno(Gtid_set *other, rpl_sidno sidno) { // Currently only works if this and other use the same Tsid_map. assert(other->tsid_map == tsid_map); Const_interval_iterator other_ivit(other, sidno); Free_intervals_lock lock(this); remove_gno_intervals(sidno, other_ivit, &lock); } enum_return_status Gtid_set::add_gtid_set(const Gtid_set *other) { /* @todo refactor this and remove_gtid_set to avoid duplicated code */ DBUG_TRACE; if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); rpl_sidno max_other_sidno = other->get_max_sidno(); Free_intervals_lock lock(this); if (other->tsid_map == tsid_map) { PROPAGATE_REPORTED_ERROR(ensure_sidno(max_other_sidno)); for (rpl_sidno sidno = 1; sidno <= max_other_sidno; sidno++) add_gno_intervals(sidno, Const_interval_iterator(other, sidno), &lock); } else { Tsid_map *other_tsid_map = other->tsid_map; Checkable_rwlock *other_tsid_lock = other->tsid_lock; if (other_tsid_lock != nullptr) other_tsid_lock->assert_some_wrlock(); for (rpl_sidno other_sidno = 1; other_sidno <= max_other_sidno; other_sidno++) { Const_interval_iterator other_ivit(other, other_sidno); if (other_ivit.get() != nullptr) { const auto &tsid = other_tsid_map->sidno_to_tsid(other_sidno); rpl_sidno this_sidno = tsid_map->add_tsid(tsid); if (this_sidno <= 0) RETURN_REPORTED_ERROR; PROPAGATE_REPORTED_ERROR(ensure_sidno(this_sidno)); add_gno_intervals(this_sidno, other_ivit, &lock); } } } RETURN_OK; } void Gtid_set::remove_gtid_set(const Gtid_set *other) { DBUG_TRACE; if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); rpl_sidno max_other_sidno = other->get_max_sidno(); Free_intervals_lock lock(this); if (other->tsid_map == tsid_map) { rpl_sidno max_sidno = min(max_other_sidno, get_max_sidno()); for (rpl_sidno sidno = 1; sidno <= max_sidno; sidno++) remove_gno_intervals(sidno, Const_interval_iterator(other, sidno), &lock); } else { Tsid_map *other_tsid_map = other->tsid_map; Checkable_rwlock *other_tsid_lock = other->tsid_lock; if (other_tsid_lock != nullptr) other_tsid_lock->assert_some_wrlock(); for (rpl_sidno other_sidno = 1; other_sidno <= max_other_sidno; other_sidno++) { Const_interval_iterator other_ivit(other, other_sidno); if (other_ivit.get() != nullptr) { const auto &tsid = other_tsid_map->sidno_to_tsid(other_sidno); rpl_sidno this_sidno = tsid_map->tsid_to_sidno(tsid); if (this_sidno != 0) remove_gno_intervals(this_sidno, other_ivit, &lock); } } } } bool Gtid_set::contains_gtid(rpl_sidno sidno, rpl_gno gno) const { DBUG_TRACE; if (tsid_lock != nullptr) tsid_lock->assert_some_lock(); if (sidno > get_max_sidno()) return false; assert(sidno >= 1); assert(gno >= 1); Const_interval_iterator ivit(this, sidno); const Interval *iv; while ((iv = ivit.get()) != nullptr) { if (gno < iv->start) return false; else if (gno < iv->end) return true; ivit.next(); } return false; } rpl_gno Gtid_set::get_last_gno(rpl_sidno sidno) const { DBUG_TRACE; rpl_gno gno = 0; if (tsid_lock != nullptr) tsid_lock->assert_some_lock(); if (sidno > get_max_sidno()) return gno; Const_interval_iterator ivit(this, sidno); const Gtid_set::Interval *iv = ivit.get(); while (iv != nullptr) { gno = iv->end - 1; ivit.next(); iv = ivit.get(); } return gno; } long Gtid_set::to_string(char **buf_arg, bool need_lock, const Gtid_set::String_format *sf_arg) const { DBUG_TRACE; if (tsid_lock != nullptr) { if (need_lock) tsid_lock->wrlock(); else tsid_lock->assert_some_wrlock(); } size_t len = get_string_length(sf_arg); *buf_arg = (char *)my_malloc(key_memory_Gtid_set_to_string, len + 1, MYF(MY_WME)); if (*buf_arg == nullptr) return -1; to_string(*buf_arg, false /*need_lock*/, sf_arg); if (tsid_lock != nullptr && need_lock) tsid_lock->unlock(); return len; } size_t Gtid_set::to_string(char *buf, bool need_lock, const Gtid_set::String_format *sf) const { DBUG_TRACE; assert(tsid_map != nullptr); if (tsid_lock != nullptr) { if (need_lock) tsid_lock->wrlock(); else tsid_lock->assert_some_wrlock(); } if (sf == nullptr) sf = &default_string_format; if (sf->empty_set_string != nullptr && is_empty()) { memcpy(buf, sf->empty_set_string, sf->empty_set_string_length); buf[sf->empty_set_string_length] = '\0'; if (tsid_lock != nullptr && need_lock) tsid_lock->unlock(); return sf->empty_set_string_length; } assert(get_max_sidno() <= tsid_map->get_max_sidno()); memcpy(buf, sf->begin, sf->begin_length); char *s = buf + sf->begin_length; bool first_sidno = true; mysql::gtid::Uuid prev_uuid; for (const auto &tsid_it : tsid_map->get_sorted_sidno()) { rpl_sidno sidno = tsid_it.second; if (contains_sidno(sidno)) { Const_interval_iterator ivit(this, sidno); const Interval *iv = ivit.get(); auto tsid = tsid_map->sidno_to_tsid(sidno); // save UUID if (first_sidno || tsid.get_uuid() != prev_uuid) { if (first_sidno == false) { memcpy(s, sf->gno_sid_separator, sf->gno_sid_separator_length); s += sf->gno_sid_separator_length; } s += tsid.get_uuid().to_string(s); prev_uuid = tsid.get_uuid(); first_sidno = false; } // save tag and intervals if (tsid.is_tagged()) { memcpy(s, sf->tag_sid_separator, strlen(sf->tag_sid_separator)); s += strlen(sf->tag_sid_separator); s += tsid.get_tag().to_string(s); } bool first_gno = true; do { if (first_gno) { memcpy(s, sf->tsid_gno_separator, sf->tsid_gno_separator_length); s += sf->tsid_gno_separator_length; } else { memcpy(s, sf->gno_gno_separator, sf->gno_gno_separator_length); s += sf->gno_gno_separator_length; } s += format_gno(s, iv->start); if (iv->end > iv->start + 1) { memcpy(s, sf->gno_start_end_separator, sf->gno_start_end_separator_length); s += sf->gno_start_end_separator_length; s += format_gno(s, iv->end - 1); } ivit.next(); iv = ivit.get(); } while (iv != nullptr); } } memcpy(s, sf->end, sf->end_length); s += sf->end_length; *s = '\0'; DBUG_PRINT("info", ("ret='%s' strlen(s)=%zu s-buf=%lu get_string_length=%llu", buf, strlen(buf), (ulong)(s - buf), static_cast(get_string_length(sf)))); assert((ulong)(s - buf) == get_string_length(sf)); if (tsid_lock != nullptr && need_lock) tsid_lock->unlock(); return (int)(s - buf); } void Gtid_set::get_gtid_intervals(list *gtid_intervals) const { DBUG_TRACE; assert(tsid_map != nullptr); if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); assert(get_max_sidno() <= tsid_map->get_max_sidno()); for (const auto &sid_it : tsid_map->get_sorted_sidno()) { rpl_sidno sidno = sid_it.second; if (contains_sidno(sidno)) { Const_interval_iterator ivit(this, sidno); const Interval *iv = ivit.get(); while (iv != nullptr) { Gtid_interval gtid_interval; gtid_interval.set(sidno, iv->start, iv->end - 1); gtid_intervals->push_back(gtid_interval); ivit.next(); iv = ivit.get(); }; } } } /** Returns the length that the given rpl_sidno (64 bit integer) would have, if it was encoded as a string. */ static size_t get_string_length(rpl_gno gno) { assert(gno >= 1); assert(gno < GNO_END); rpl_gno tmp_gno = gno; size_t len = 0; do { tmp_gno /= 10; len++; } while (tmp_gno != 0); #ifndef NDEBUG char buf[22]; assert(snprintf(buf, 22, "%" PRId64, gno) == ssize_t(len)); #endif return len; } bool Gtid_set::contains_tags() const { assert(tsid_map != nullptr); for (const auto &tsid_it : tsid_map->get_sorted_sidno()) { rpl_sidno sidno = tsid_it.second; if (contains_sidno(sidno)) { Const_interval_iterator ivit(this, sidno); if (ivit.get() != nullptr) { auto tsid = tsid_map->sidno_to_tsid(sidno); if (tsid.is_tagged()) { return true; } } } } return false; } size_t Gtid_set::get_string_length(const Gtid_set::String_format *sf) const { assert(tsid_map != nullptr); if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); if (sf == nullptr) sf = &default_string_format; if (has_cached_string_length == false || cached_string_format != sf) { int n_sids = 0, n_sidnos = 0, n_intervals = 0, n_long_intervals = 0; size_t total_interval_length = 0; size_t total_tsids_length = 0; mysql::gtid::Uuid prev_uuid; bool first_sidno = true; for (const auto &tsid_it : tsid_map->get_sorted_sidno()) { rpl_sidno sidno = tsid_it.second; if (contains_sidno(sidno)) { Const_interval_iterator ivit(this, sidno); const Interval *iv = ivit.get(); if (iv != nullptr) { auto tsid = tsid_map->sidno_to_tsid(sidno); ++n_sidnos; if (tsid.is_tagged()) { total_tsids_length += tsid.get_tag().get_length() + sf->tag_sid_separator_length; } if (first_sidno || tsid.get_uuid() != prev_uuid) { total_tsids_length += mysql::gtid::Uuid::TEXT_LENGTH; prev_uuid = tsid.get_uuid(); first_sidno = false; ++n_sids; } do { total_interval_length += ::get_string_length(iv->start); ++n_intervals; if (iv->end - 1 > iv->start) { ++n_long_intervals; total_interval_length += ::get_string_length(iv->end - 1); } ivit.next(); iv = ivit.get(); } while (iv != nullptr); } } } if (n_sids == 0 && sf->empty_set_string != nullptr) cached_string_length = sf->empty_set_string_length; else { cached_string_length = sf->begin_length + sf->end_length; if (n_sids > 0) cached_string_length += total_tsids_length + ((n_sids - 1) * sf->gno_sid_separator_length) + total_interval_length + n_sidnos * sf->tsid_gno_separator_length + (n_intervals - n_sidnos) * sf->gno_gno_separator_length + n_long_intervals * sf->gno_start_end_separator_length; } has_cached_string_length = true; cached_string_format = sf; } return cached_string_length; } bool Gtid_set::sidno_equals(rpl_sidno sidno, const Gtid_set *other, rpl_sidno other_sidno) const { DBUG_TRACE; Const_interval_iterator ivit(this, sidno); Const_interval_iterator other_ivit(other, other_sidno); const Interval *iv = ivit.get(); const Interval *other_iv = other_ivit.get(); while (iv != nullptr && other_iv != nullptr) { if (!iv->equals(*other_iv)) return false; ivit.next(); other_ivit.next(); iv = ivit.get(); other_iv = other_ivit.get(); } if (iv != nullptr || other_iv != nullptr) return false; return true; } bool Gtid_set::equals(const Gtid_set *other) const { DBUG_TRACE; auto contains_any_sidno = [](auto tsid_iter, auto &tsid_map_sorted, const Gtid_set *set) -> bool { while (tsid_iter != tsid_map_sorted.end()) { const auto &sidno = tsid_iter->second; if (set->contains_sidno(sidno)) { return true; } ++tsid_iter; } return false; }; const Tsid_map *other_tsid_map = other->tsid_map; if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); if (other->tsid_lock != nullptr) other->tsid_lock->assert_some_wrlock(); assert(tsid_map != nullptr); assert(other_tsid_map != nullptr); const auto &map_sorted = tsid_map->get_sorted_sidno(); const auto &other_map_sorted = other_tsid_map->get_sorted_sidno(); auto other_tsid_it = other_map_sorted.begin(); auto sid_it = map_sorted.begin(); // iterate over potentially common sidnos while (sid_it != map_sorted.end() && other_tsid_it != other_map_sorted.end()) { const auto &sidno = sid_it->second; const auto &other_sidno = other_tsid_it->second; // continue in case sidno from a tsid_map is not in corresponding GTID set if (!contains_sidno(sidno)) { ++sid_it; continue; } if (!other->contains_sidno(other_sidno)) { ++other_tsid_it; continue; } // compare tsids if (sid_it->first != other_tsid_it->first) { return false; } // comparing intervals if (!sidno_equals(sidno, other, other_sidno)) return false; ++sid_it; ++other_tsid_it; } // end of common sidnos, check that GTID sets do not contain other sidnos than // common ones return (contains_any_sidno(sid_it, map_sorted, this) == false) && (contains_any_sidno(other_tsid_it, other_map_sorted, other) == false); } bool Gtid_set::is_interval_subset(Const_interval_iterator *sub, Const_interval_iterator *super) { DBUG_TRACE; // check if all intervals for this sidno are contained in some // interval of super const Interval *super_iv = super->get(); const Interval *sub_iv = sub->get(); /* Algorithm: Let sub_iv iterate over intervals of sub. For each sub_iv, skip over intervals of super that end before sub_iv. When we find the first super-interval that does not end before sub_iv, check if it covers sub_iv. */ do { if (super_iv == nullptr) return false; // Skip over 'smaller' intervals of super. while (sub_iv->start > super_iv->end) { super->next(); super_iv = super->get(); // If we reach end of super, then no interval covers sub_iv, so // sub is not a subset of super. if (super_iv == nullptr) return false; } // If super_iv does not cover sub_iv, then sub is not a subset of // super. if (sub_iv->start < super_iv->start || sub_iv->end > super_iv->end) return false; // Next iteration. sub->next(); sub_iv = sub->get(); } while (sub_iv != nullptr); // If every GNO in sub also exists in super, then it was a subset. return true; } bool Gtid_set::is_subset_for_sid(const Gtid_set *super, const rpl_sid &sid) const { DBUG_TRACE; if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); if (super->tsid_lock != nullptr) super->tsid_lock->assert_some_wrlock(); auto *super_tsid_map = super->tsid_map; auto *tsid_map = this->tsid_map; assert(tsid_map != nullptr); assert(super_tsid_map != nullptr); const auto &sorted_sidno_data = tsid_map->get_sorted_sidno(); for (auto tsid_item = sorted_sidno_data.lower_bound(sid); tsid_item != sorted_sidno_data.end(); ++tsid_item) { const auto &tsid = tsid_map->sidno_to_tsid(tsid_item->second); if (tsid.get_uuid() != sid) { return true; } auto super_sidno = super_tsid_map->tsid_to_sidno(tsid); auto this_sidno = tsid_map->tsid_to_sidno(tsid); if (!is_subset_for_sidno(super, super_sidno, this_sidno)) { return false; } } return true; } bool Gtid_set::is_subset_for_sidno(const Gtid_set *super, rpl_sidno superset_sidno, rpl_sidno subset_sidno) const { DBUG_TRACE; /* The following assert code is to see that caller acquired either write or read lock on global_tsid_lock. Note that if it is read lock, then it should also acquire lock on sidno. i.e., the caller must acquire lock either A1 way or A2 way. A1. global_tsid_lock.wrlock() A2. global_tsid_lock.rdlock(); gtid_state.lock_sidno(sidno) */ if (tsid_lock != nullptr) super->tsid_lock->assert_some_wrlock(); if (super->tsid_lock != nullptr) super->tsid_lock->assert_some_wrlock(); /* If subset(i.e, this object) does not have required sid in it, i.e., subset_sidno is zero, then it means it is subset of any given super set. Hence return true. */ if (subset_sidno == 0) return true; /* If superset (i.e., the passed gtid_set) does not have given sid in it, i.e., superset_sidno is zero, then it means it cannot be superset to any given subset. Hence return false. */ if (superset_sidno == 0) return false; /* Once we have valid(non-zero) subset's and superset's sid numbers, call is_interval_subset(). */ Const_interval_iterator subset_ivit(this, subset_sidno); Const_interval_iterator superset_ivit(super, superset_sidno); if (!is_interval_subset(&subset_ivit, &superset_ivit)) return false; return true; } bool Gtid_set::is_subset(const Gtid_set *super) const { DBUG_TRACE; if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); if (super->tsid_lock != nullptr) super->tsid_lock->assert_some_wrlock(); Tsid_map *super_tsid_map = super->tsid_map; rpl_sidno max_sidno = get_max_sidno(); rpl_sidno super_max_sidno = super->get_max_sidno(); assert(tsid_map != nullptr); assert(super_tsid_map != nullptr); /* Iterate over sidnos of this Gtid_set where there is at least one interval. For each such sidno, get the corresponding sidno of super, and then use is_interval_subset to look for GTIDs that exist in this but not in super. */ for (int sidno = 1; sidno <= max_sidno; sidno++) { Const_interval_iterator ivit(this, sidno); const Interval *iv = ivit.get(); if (iv != nullptr) { // Get the corresponding super_sidno int super_sidno = sidno; if (super_tsid_map != tsid_map) { super_sidno = super_tsid_map->tsid_to_sidno(tsid_map->sidno_to_tsid(sidno)); if (super_sidno == 0) return false; } if (super_sidno > super_max_sidno) return false; // Check if all GNOs in this Gtid_set for sidno exist in other // Gtid_set for super_ Const_interval_iterator super_ivit(super, super_sidno); if (!is_interval_subset(&ivit, &super_ivit)) return false; } } // If the GNOs for every SIDNO of sub existed in super, then it was // a subset. return true; } bool Gtid_set::is_interval_intersection_nonempty( Const_interval_iterator *ivit1, Const_interval_iterator *ivit2) { DBUG_TRACE; const Interval *iv1 = ivit1->get(); const Interval *iv2 = ivit2->get(); assert(iv1 != nullptr); if (iv2 == nullptr) return false; /* Algorithm: Let iv1 iterate over all intervals of ivit1. For each iv1, skip over intervals of iv2 that end before iv1. When we reach the first interval that does not end before iv1, check if it intersects with iv1. */ do { // Skip over intervals of iv2 that end before iv1. while (iv2->end <= iv1->start) { ivit2->next(); iv2 = ivit2->get(); // If we reached the end of ivit2, then there is no intersection. if (iv2 == nullptr) return false; } // If iv1 and iv2 intersect, return true. if (iv2->start < iv1->end) return true; // Next iteration. ivit1->next(); iv1 = ivit1->get(); } while (iv1 != nullptr); // If we iterated over all intervals of ivit1 without finding any // intersection with ivit2, then there is no intersection. return false; } bool Gtid_set::is_intersection_nonempty(const Gtid_set *other) const { DBUG_TRACE; /* This could in principle be implemented as follows: Gtid_set this_minus_other(tsid_map); this_minus_other.add_gtid_set(this); this_minus_other.remove_gtid_set(other); bool ret= equals(&this_minus_other); return ret; However, that does not check the return values from add_gtid_set or remove_gtid_set, and there is no way for this function to return an error. */ if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); if (other->tsid_lock != nullptr) other->tsid_lock->assert_some_wrlock(); Tsid_map *other_tsid_map = other->tsid_map; rpl_sidno max_sidno = get_max_sidno(); rpl_sidno other_max_sidno = other->get_max_sidno(); assert(tsid_map != nullptr); assert(other_tsid_map != nullptr); /* Algorithm: iterate over all sidnos of this Gtid_set where there is at least one interval. For each such sidno, find the corresponding sidno of the other set. Then use is_interval_intersection_nonempty to check if there are any GTIDs that are common to the two sets for this sidno. */ for (int sidno = 1; sidno <= max_sidno; sidno++) { Const_interval_iterator ivit(this, sidno); const Interval *iv = ivit.get(); if (iv != nullptr) { // Get the corresponding other_sidno. int other_sidno = sidno; if (other_tsid_map != tsid_map) { other_sidno = other_tsid_map->tsid_to_sidno(tsid_map->sidno_to_tsid(sidno)); if (other_sidno == 0) continue; } if (other_sidno > other_max_sidno) continue; // Check if there is any GNO in this for sidno that also exists // in other for other_sidno. Const_interval_iterator other_ivit(other, other_sidno); if (is_interval_intersection_nonempty(&ivit, &other_ivit)) return true; } } return false; } enum_return_status Gtid_set::intersection(const Gtid_set *other, Gtid_set *result) { DBUG_TRACE; if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); assert(result != nullptr); assert(other != nullptr); assert(result != this); assert(result != other); assert(other != this); /** @todo: This algorithm is simple, a little bit slower than necessary. It would be more efficient to iterate over intervals of 'this' and 'other' similar to add_gno_interval(). At the moment the performance of this is not super-important. /Sven */ Gtid_set this_minus_other(tsid_map); Gtid_set intersection(tsid_map); // In set theory, intersection(A, B) == A - (A - B) PROPAGATE_REPORTED_ERROR(this_minus_other.add_gtid_set(this)); this_minus_other.remove_gtid_set(other); PROPAGATE_REPORTED_ERROR(intersection.add_gtid_set(this)); intersection.remove_gtid_set(&this_minus_other); PROPAGATE_REPORTED_ERROR(result->add_gtid_set(&intersection)); RETURN_OK; } bool Gtid_set::is_size_greater_than_or_equal(ulonglong num) const { if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); rpl_sidno max_sidno = get_max_sidno(); ulonglong count = 0; for (rpl_sidno sidno = 1; sidno <= max_sidno; sidno++) { count += get_gtid_count(sidno); if (count >= num) return true; } return false; } namespace { using namespace mysql::gtid; void encode_nsids_format(uchar *buf, uint64_t n_sids, Gtid_format gtid_format) { uint64_t format_encoded = static_cast(mysql::utils::to_underlying(gtid_format)); uint64_t format_shifted = format_encoded << 56; uint64_t n_sids_encoded = n_sids | format_shifted; if (gtid_format == Gtid_format::tagged) { n_sids_encoded = format_shifted | (n_sids << 8) | format_encoded; } int8store(buf, n_sids_encoded); } std::tuple decode_nsids_format(const uchar *buf) { uint64_t n_sids_encoded = uint8korr(buf); uint64_t format_mask = static_cast(0xff) << 56; uint64_t n_sids_mask = ~format_mask; uint8_t format_encoded = static_cast((n_sids_encoded & format_mask) >> 56); uint64_t n_sids = n_sids_encoded & n_sids_mask; auto [gtid_format, conversion_code] = mysql::utils::to_enumeration(format_encoded); if (gtid_format == Gtid_format::tagged) { n_sids_mask = static_cast(0x00ffffffffffff00ULL); n_sids = (n_sids_encoded & n_sids_mask) >> 8; } return std::make_tuple(conversion_code, n_sids, gtid_format); } enum_return_status report_gtid_encoding_error() { BINLOG_ERROR(("Malformed GTID_set encoding."), (ER_MALFORMED_GTID_SET_ENCODING, MYF(0))); RETURN_REPORTED_ERROR; } } // namespace void Gtid_set::encode(uchar *buf, bool skip_tagged_gtids) const { DBUG_TRACE; if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); // make place for number of sids uint64 n_sids = 0; uchar *n_sids_p = buf; buf += 8; auto format = analyze_encoding_format(skip_tagged_gtids); // iterate over sidnos rpl_sidno max_sidno = get_max_sidno(); for (const auto &tsid_item : tsid_map->get_sorted_sidno()) { rpl_sidno sidno = tsid_item.second; // it is possible that the tsid_map has more SIDNOs than the set. if (sidno > max_sidno) continue; DBUG_PRINT("info", ("sidno=%d max_sidno=%d tsid_map->max_sidno=%d", sidno, max_sidno, tsid_map->get_max_sidno())); auto tsid = tsid_map->sidno_to_tsid(sidno); if (tsid.is_tagged() == false || skip_tagged_gtids == false) { Const_interval_iterator ivit(this, sidno); const Interval *iv = ivit.get(); if (iv != nullptr) { n_sids++; // store SID auto num_tsid_bytes = tsid.encode_tsid(buf, format); buf += num_tsid_bytes; // make place for number of intervals uint64 n_intervals = 0; uchar *n_intervals_p = buf; buf += 8; // iterate over intervals do { n_intervals++; // store one interval int8store(buf, iv->start); buf += 8; int8store(buf, iv->end); buf += 8; // iterate to next interval ivit.next(); iv = ivit.get(); } while (iv != nullptr); // store number of intervals int8store(n_intervals_p, n_intervals); } } } // store number of sids encode_nsids_format(n_sids_p, n_sids, format); assert(buf - n_sids_p == (int)get_encoded_length(format, skip_tagged_gtids)); } enum_return_status Gtid_set::add_gtid_encoding(const uchar *encoded, size_t length, size_t *actual_length) { DBUG_TRACE; if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); size_t pos = 0; Free_intervals_lock lock(this); // read number of TSIDs if (length < 8) { DBUG_PRINT("error", ("(length=%lu) < 8", (ulong)length)); return report_gtid_encoding_error(); } auto [decoding_code, n_sids, gtid_format] = decode_nsids_format(encoded); if (decoding_code == mysql::utils::Return_status::error) { DBUG_PRINT("error", ("unknown or corrupted GTID set encoding format")); return report_gtid_encoding_error(); } pos += 8; // iterate over TSIDs for (uint sid_counter = 0; sid_counter < n_sids; sid_counter++) { // read TSID and number of intervals if (length - pos < 16 + 8) { DBUG_PRINT("error", ("(length=%lu) - (pos=%lu) < 16 + 8. " "[n_sids=%" PRIu64 " i=%u]", (ulong)length, (ulong)pos, n_sids, sid_counter)); return report_gtid_encoding_error(); } Tsid tsid; pos += tsid.decode_tsid(encoded + pos, length - pos, gtid_format); uint64 n_intervals = uint8korr(encoded + pos); pos += 8; rpl_sidno sidno = tsid_map->add_tsid(tsid); if (sidno < 0) { DBUG_PRINT("error", ("sidno=%d", sidno)); RETURN_REPORTED_ERROR; } PROPAGATE_REPORTED_ERROR(ensure_sidno(sidno)); // iterate over intervals if (length - pos < 2 * 8 * n_intervals) { DBUG_PRINT( "error", ("(length=%lu) - (pos=%lu) < 2 * 8 * (n_intervals=%" PRIu64 ")", (ulong)length, (ulong)pos, n_intervals)); return report_gtid_encoding_error(); } Interval_iterator ivit(this, sidno); rpl_gno last = 0; for (uint i = 0; i < n_intervals; i++) { // read one interval rpl_gno start = sint8korr(encoded + pos); pos += 8; rpl_gno end = sint8korr(encoded + pos); pos += 8; if (start <= last || end <= start) { DBUG_PRINT("error", ("last=%" PRId64 " start=%" PRId64 " end=%" PRId64, last, start, end)); return report_gtid_encoding_error(); } last = end; // Add interval. Use the existing iterator position if the // current interval does not begin before it. Otherwise iterate // from the beginning. Interval *current = ivit.get(); if (current == nullptr || start < current->start) ivit.init(this, sidno); DBUG_PRINT("info", ("adding %d:%" PRId64 "-%" PRId64, sidno, start, end - 1)); add_gno_interval(&ivit, start, end, &lock); } } assert(pos <= length); if (actual_length == nullptr) { if (pos != length) { DBUG_PRINT("error", ("(pos=%lu) != (length=%lu)", (ulong)pos, (ulong)length)); return report_gtid_encoding_error(); } } else *actual_length = pos; RETURN_OK; } mysql::gtid::Gtid_format Gtid_set::analyze_encoding_format( bool skip_tagged_gtids) const { if (skip_tagged_gtids == true) { return mysql::gtid::Gtid_format::untagged; } for (const auto &tsid_item : tsid_map->get_sorted_sidno()) { rpl_sidno sidno = tsid_item.second; auto tsid = tsid_map->sidno_to_tsid(sidno); if (tsid.is_tagged()) { return mysql::gtid::Gtid_format::tagged; } } return mysql::gtid::Gtid_format::untagged; } size_t Gtid_set::get_encoded_length(const mysql::gtid::Gtid_format &format, bool skip_tagged_gtids) const { if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); size_t ret = 8; size_t tag_len = 0; rpl_sidno max_sidno = get_max_sidno(); for (rpl_sidno sidno = 1; sidno <= max_sidno; sidno++) if (contains_sidno(sidno)) { auto tsid = tsid_map->sidno_to_tsid(sidno); if (tsid.is_tagged() == false || skip_tagged_gtids == false) { ret += 16 + 8 + 2 * 8 * get_n_intervals(sidno); tag_len += tsid.get_tag().get_encoded_length(format); } } if (format == mysql::gtid::Gtid_format::tagged) { ret += tag_len; } return ret; } size_t Gtid_set::get_encoded_length(bool skip_tagged_gtids) const { if (tsid_lock != nullptr) tsid_lock->assert_some_wrlock(); Gtid_format gtid_format = analyze_encoding_format(skip_tagged_gtids); return get_encoded_length(gtid_format, skip_tagged_gtids); }