3702 lines
120 KiB
C++
3702 lines
120 KiB
C++
/* Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; version 2 of the
|
|
License.
|
|
|
|
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 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 */
|
|
|
|
|
|
#ifndef RPL_GTID_H_INCLUDED
|
|
#define RPL_GTID_H_INCLUDED
|
|
|
|
#include "my_global.h"
|
|
#include "hash.h" // HASH
|
|
#include "my_atomic.h" // my_atomic_add32
|
|
#include "prealloced_array.h" // Prealloced_array
|
|
#include "control_events.h" // binary_log::Uuid
|
|
|
|
#ifdef MYSQL_SERVER
|
|
#include "mysqld.h" // key_rwlock_global_sid_lock
|
|
#include "table.h"
|
|
#endif
|
|
|
|
#include <list>
|
|
#include "atomic_class.h"
|
|
|
|
using binary_log::Uuid;
|
|
/**
|
|
Report an error from code that can be linked into either the server
|
|
or mysqlbinlog. There is no common error reporting mechanism, so we
|
|
have to duplicate the error message (write it out in the source file
|
|
for mysqlbinlog, write it in share/errmsg-utf8.txt for the server).
|
|
|
|
@param MYSQLBINLOG_ERROR arguments to mysqlbinlog's 'error'
|
|
function, including the function call parentheses
|
|
@param SERVER_ERROR arguments to my_error, including the function
|
|
call parentheses.
|
|
*/
|
|
#ifdef MYSQL_CLIENT
|
|
#define BINLOG_ERROR(MYSQLBINLOG_ERROR, SERVER_ERROR) error MYSQLBINLOG_ERROR
|
|
#else
|
|
#define BINLOG_ERROR(MYSQLBINLOG_ERROR, SERVER_ERROR) my_error SERVER_ERROR
|
|
#endif
|
|
|
|
|
|
extern PSI_memory_key key_memory_Gtid_set_to_string;
|
|
extern PSI_memory_key key_memory_Owned_gtids_to_string;
|
|
extern PSI_memory_key key_memory_Gtid_state_to_string;
|
|
extern PSI_memory_key key_memory_Group_cache_to_string;
|
|
extern PSI_memory_key key_memory_Gtid_set_Interval_chunk;
|
|
extern PSI_memory_key key_memory_Gtid_state_group_commit_sidno;
|
|
|
|
/**
|
|
This macro is used to check that the given character, pointed to by the
|
|
character pointer, is a space or not.
|
|
*/
|
|
#define SKIP_WHITESPACE() while (my_isspace(&my_charset_utf8_general_ci, *s)) s++
|
|
/*
|
|
This macro must be used to filter out parts of the code that
|
|
is not used now but may be useful in future. In other words,
|
|
we want to keep such code until we make up our minds on whether
|
|
it should be removed or not.
|
|
*/
|
|
#undef NON_DISABLED_GTID
|
|
|
|
/*
|
|
This macro must be used to filter out parts of the code that
|
|
is not used now but we are not sure if there is a bug around
|
|
them. In other words, we want to keep such code until we have
|
|
time to investigate it.
|
|
*/
|
|
#undef NON_ERROR_GTID
|
|
|
|
#ifndef MYSQL_CLIENT
|
|
class String;
|
|
class THD;
|
|
#endif // ifndef MYSQL_CLIENT
|
|
|
|
|
|
/// Type of SIDNO (source ID number, first component of GTID)
|
|
typedef int rpl_sidno;
|
|
/// Type for GNO (group number, second component of GTID)
|
|
typedef long long int rpl_gno;
|
|
typedef int64 rpl_binlog_pos;
|
|
|
|
|
|
/**
|
|
Generic return type for many functions that can succeed or fail.
|
|
|
|
This is used in conjuction with the macros below for functions where
|
|
the return status either indicates "success" or "failure". It
|
|
provides the following features:
|
|
|
|
- The macros can be used to conveniently propagate errors from
|
|
called functions back to the caller.
|
|
|
|
- If a function is expected to print an error using my_error before
|
|
it returns an error status, then the macros assert that my_error
|
|
has been called.
|
|
|
|
- Does a DBUG_PRINT before returning failure.
|
|
*/
|
|
enum enum_return_status
|
|
{
|
|
/// The function completed successfully.
|
|
RETURN_STATUS_OK= 0,
|
|
/// The function completed with error but did not report it.
|
|
RETURN_STATUS_UNREPORTED_ERROR= 1,
|
|
/// The function completed with error and has called my_error.
|
|
RETURN_STATUS_REPORTED_ERROR= 2
|
|
};
|
|
|
|
/**
|
|
Lowest level macro used in the PROPAGATE_* and RETURN_* macros
|
|
below.
|
|
|
|
If DBUG_OFF is defined, does nothing. Otherwise, if STATUS is
|
|
RETURN_STATUS_OK, does nothing; otherwise, make a dbug printout and
|
|
(if ALLOW_UNREPORTED==0) assert that STATUS !=
|
|
RETURN_STATUS_UNREPORTED.
|
|
|
|
@param STATUS The status to return.
|
|
@param ACTION A text that describes what we are doing: either
|
|
"Returning" or "Propagating" (used in DBUG_PRINT macros)
|
|
@param STATUS_NAME The stringified version of the STATUS (used in
|
|
DBUG_PRINT macros).
|
|
@param ALLOW_UNREPORTED If false, the macro asserts that STATUS is
|
|
not RETURN_STATUS_UNREPORTED_ERROR.
|
|
*/
|
|
#ifdef DBUG_OFF
|
|
#define __CHECK_RETURN_STATUS(STATUS, ACTION, STATUS_NAME, ALLOW_UNREPORTED)
|
|
#else
|
|
extern void check_return_status(enum_return_status status,
|
|
const char *action, const char *status_name,
|
|
int allow_unreported);
|
|
#define __CHECK_RETURN_STATUS(STATUS, ACTION, STATUS_NAME, ALLOW_UNREPORTED) \
|
|
check_return_status(STATUS, ACTION, STATUS_NAME, ALLOW_UNREPORTED);
|
|
#endif
|
|
/**
|
|
Low-level macro that checks if STATUS is RETURN_STATUS_OK; if it is
|
|
not, then RETURN_VALUE is returned.
|
|
@see __DO_RETURN_STATUS
|
|
*/
|
|
#define __PROPAGATE_ERROR(STATUS, RETURN_VALUE, ALLOW_UNREPORTED) \
|
|
do \
|
|
{ \
|
|
enum_return_status __propagate_error_status= STATUS; \
|
|
if (__propagate_error_status != RETURN_STATUS_OK) { \
|
|
__CHECK_RETURN_STATUS(__propagate_error_status, "Propagating", \
|
|
#STATUS, ALLOW_UNREPORTED); \
|
|
DBUG_RETURN(RETURN_VALUE); \
|
|
} \
|
|
} while (0)
|
|
/// Low-level macro that returns STATUS. @see __DO_RETURN_STATUS
|
|
#define __RETURN_STATUS(STATUS, ALLOW_UNREPORTED) \
|
|
do \
|
|
{ \
|
|
enum_return_status __return_status_status= STATUS; \
|
|
__CHECK_RETURN_STATUS(__return_status_status, "Returning", \
|
|
#STATUS, ALLOW_UNREPORTED); \
|
|
DBUG_RETURN(__return_status_status); \
|
|
} while (0)
|
|
/**
|
|
If STATUS (of type enum_return_status) returns RETURN_STATUS_OK,
|
|
does nothing; otherwise, does a DBUG_PRINT and returns STATUS.
|
|
*/
|
|
#define PROPAGATE_ERROR(STATUS) \
|
|
__PROPAGATE_ERROR(STATUS, __propagate_error_status, true)
|
|
/**
|
|
If STATUS (of type enum_return_status) returns RETURN_STATUS_OK,
|
|
does nothing; otherwise asserts that STATUS ==
|
|
RETURN_STATUS_REPORTED_ERROR, does a DBUG_PRINT, and returns STATUS.
|
|
*/
|
|
#define PROPAGATE_REPORTED_ERROR(STATUS) \
|
|
__PROPAGATE_ERROR(STATUS, __propagate_error_status, false)
|
|
/**
|
|
If STATUS (of type enum_return_status) returns RETURN_STATUS_OK,
|
|
does nothing; otherwise asserts that STATUS ==
|
|
RETURN_STATUS_REPORTED_ERROR, does a DBUG_PRINT, and returns 1.
|
|
*/
|
|
#define PROPAGATE_REPORTED_ERROR_INT(STATUS) \
|
|
__PROPAGATE_ERROR(STATUS, 1, false)
|
|
/**
|
|
If STATUS returns something else than RETURN_STATUS_OK, does a
|
|
DBUG_PRINT. Then, returns STATUS.
|
|
*/
|
|
#define RETURN_STATUS(STATUS) __RETURN_STATUS(STATUS, true)
|
|
/**
|
|
Asserts that STATUS is not RETURN_STATUS_UNREPORTED_ERROR. Then, if
|
|
STATUS is RETURN_STATUS_REPORTED_ERROR, does a DBUG_PRINT. Then,
|
|
returns STATUS.
|
|
*/
|
|
#define RETURN_REPORTED_STATUS(STATUS) __RETURN_STATUS(STATUS, false)
|
|
/// Returns RETURN_STATUS_OK.
|
|
#define RETURN_OK DBUG_RETURN(RETURN_STATUS_OK)
|
|
/// Does a DBUG_PRINT and returns RETURN_STATUS_REPORTED_ERROR.
|
|
#define RETURN_REPORTED_ERROR RETURN_STATUS(RETURN_STATUS_REPORTED_ERROR)
|
|
/// Does a DBUG_PRINT and returns RETURN_STATUS_UNREPORTED_ERROR.
|
|
#define RETURN_UNREPORTED_ERROR RETURN_STATUS(RETURN_STATUS_UNREPORTED_ERROR)
|
|
|
|
/**
|
|
enum to map the result of Uuid::parse to the above Macros
|
|
*/
|
|
inline enum_return_status map_macro_enum(int status)
|
|
{
|
|
DBUG_ENTER("map status error with the return value of uuid::parse_method");
|
|
if (status == 0)
|
|
RETURN_OK;
|
|
else
|
|
RETURN_UNREPORTED_ERROR;
|
|
}
|
|
|
|
|
|
/// Possible values for @@GLOBAL.GTID_MODE.
|
|
enum enum_gtid_mode
|
|
{
|
|
/**
|
|
New transactions are anonymous. Replicated transactions must be
|
|
anonymous; replicated GTID-transactions generate an error.
|
|
*/
|
|
GTID_MODE_OFF= 0,
|
|
/**
|
|
New transactions are anonyomus. Replicated transactions can be
|
|
either anonymous or GTID-transactions.
|
|
*/
|
|
GTID_MODE_OFF_PERMISSIVE= 1,
|
|
/**
|
|
New transactions are GTID-transactions. Replicated transactions
|
|
can be either anonymous or GTID-transactions.
|
|
*/
|
|
GTID_MODE_ON_PERMISSIVE= 2,
|
|
/**
|
|
New transactions are GTID-transactions. Replicated transactions
|
|
must be GTID-transactions; replicated anonymous transactions
|
|
generate an error.
|
|
*/
|
|
GTID_MODE_ON= 3
|
|
};
|
|
/**
|
|
The gtid_mode.
|
|
|
|
Please do not access this directly - use the getters and setters
|
|
defined below.
|
|
|
|
It is ulong rather than enum_gtid_mode because of how sys_vars are
|
|
updated.
|
|
*/
|
|
extern ulong _gtid_mode;
|
|
/**
|
|
Strings holding the enumeration values for gtid_mode. Use
|
|
get_gtid_mode_string instead of accessing this directly.
|
|
*/
|
|
extern const char *gtid_mode_names[];
|
|
/**
|
|
'Typelib' for the mode names. Use get_gtid_mode_string instead
|
|
of accessing this directly.
|
|
*/
|
|
extern TYPELIB gtid_mode_typelib;
|
|
|
|
/**
|
|
Return the given string GTID_MODE as an enumeration value.
|
|
|
|
@param string The string to decode.
|
|
|
|
@param[OUT] error If the string does not represent a valid
|
|
GTID_MODE, this is set to true, otherwise it is left untouched.
|
|
|
|
@return The GTID_MODE.
|
|
*/
|
|
inline enum_gtid_mode get_gtid_mode(const char *string, bool *error)
|
|
{
|
|
int ret= find_type(string, >id_mode_typelib, 1);
|
|
if (ret == 0)
|
|
{
|
|
*error= true;
|
|
return GTID_MODE_OFF;
|
|
}
|
|
else
|
|
return (enum_gtid_mode)(ret - 1);
|
|
}
|
|
/// Return the given GTID_MODE as a string.
|
|
inline const char *get_gtid_mode_string(enum_gtid_mode gtid_mode_arg)
|
|
{
|
|
return gtid_mode_names[gtid_mode_arg];
|
|
}
|
|
|
|
/**
|
|
Locks needed to access gtid_mode.
|
|
|
|
When writing, all these locks must be held (for the rwlocks, the
|
|
wrlock must be held).
|
|
|
|
When reading, one of them must be held (for the wrlocks, the rdlock
|
|
suffices).
|
|
*/
|
|
enum enum_gtid_mode_lock
|
|
{
|
|
/// No lock held.
|
|
GTID_MODE_LOCK_NONE,
|
|
/// The specific gtid_mode_lock is held.
|
|
GTID_MODE_LOCK_GTID_MODE,
|
|
/// global_sid_lock held.
|
|
GTID_MODE_LOCK_SID,
|
|
/// read or write lock on channel_map lock is held.
|
|
GTID_MODE_LOCK_CHANNEL_MAP
|
|
/*
|
|
Currently, no function that calls get_gtid_mode needs
|
|
this. Uncomment this, and uncomment the case in get_gtid_mode, if it
|
|
is ever needed.
|
|
|
|
/// mysql_bin_log.get_log_lock() held.
|
|
GTID_MODE_LOCK_LOG
|
|
*/
|
|
};
|
|
/**
|
|
Return the current GTID_MODE as an enumeration value.
|
|
|
|
This variable can be read while holding any one of the locks
|
|
enumerated in enum_gtid_mode_lock (see above).
|
|
|
|
When the variable is updated by a SET GTID_MODE statement, all these
|
|
locks will be taken (the wrlock on global_sid_map).
|
|
|
|
To avoid the mistake of reading the GTID_MODE with no lock, the
|
|
caller has to pass the lock type as a parameter. The function will
|
|
assert that the corresponding lock is held. If no lock is held, it
|
|
will acquire and release global_sid_lock.rdlock.
|
|
|
|
@param have_lock The lock type held by the caller.
|
|
*/
|
|
enum_gtid_mode get_gtid_mode(enum_gtid_mode_lock have_lock);
|
|
|
|
#ifndef DBUG_OFF
|
|
/**
|
|
Return the current GTID_MODE as a string. Used only for debugging.
|
|
|
|
@param need_lock Pass this parameter to get_gtid_mode(bool).
|
|
*/
|
|
inline const char *get_gtid_mode_string(enum_gtid_mode_lock have_lock)
|
|
{
|
|
return get_gtid_mode_string(get_gtid_mode(have_lock));
|
|
}
|
|
#endif // ifndef DBUG_OFF
|
|
|
|
|
|
/**
|
|
Possible values for ENFORCE_GTID_CONSISTENCY.
|
|
*/
|
|
enum enum_gtid_consistency_mode
|
|
{
|
|
GTID_CONSISTENCY_MODE_OFF= 0,
|
|
GTID_CONSISTENCY_MODE_ON= 1,
|
|
GTID_CONSISTENCY_MODE_WARN= 2
|
|
};
|
|
/**
|
|
Strings holding the enumeration values for
|
|
gtid_consistency_mode_names. Use get_gtid_consistency_mode_string
|
|
instead of accessing this directly.
|
|
*/
|
|
extern const char *gtid_consistency_mode_names[];
|
|
/**
|
|
Current value for ENFORCE_GTID_CONSISTENCY.
|
|
Don't use this directly; use get_gtid_consistency_mode.
|
|
*/
|
|
extern ulong _gtid_consistency_mode;
|
|
/**
|
|
Return the current value of ENFORCE_GTID_CONSISTENCY.
|
|
|
|
Caller must hold global_sid_lock.rdlock.
|
|
*/
|
|
enum_gtid_consistency_mode get_gtid_consistency_mode();
|
|
/// Return the given GTID_CONSISTENCY_MODE as a string.
|
|
inline const char *get_gtid_consistency_mode_string(enum_gtid_consistency_mode mode)
|
|
{
|
|
return gtid_consistency_mode_names[(int)mode];
|
|
}
|
|
/**
|
|
Return the current value of ENFORCE_GTID_CONSISTENCY as a string.
|
|
|
|
Caller must hold global_sid_lock.rdlock.
|
|
*/
|
|
inline const char *get_gtid_consistency_mode_string()
|
|
{
|
|
return get_gtid_consistency_mode_string(get_gtid_consistency_mode());
|
|
}
|
|
|
|
|
|
/// The maximum value of GNO
|
|
const rpl_gno MAX_GNO= LLONG_MAX;
|
|
/// The length of MAX_GNO when printed in decimal.
|
|
const int MAX_GNO_TEXT_LENGTH= 19;
|
|
/// The maximal possible length of thread_id when printed in decimal.
|
|
const int MAX_THREAD_ID_TEXT_LENGTH= 19;
|
|
|
|
|
|
/**
|
|
Parse a GNO from a string.
|
|
|
|
@param s Pointer to the string. *s will advance to the end of the
|
|
parsed GNO, if a correct GNO is found.
|
|
@retval GNO if a correct GNO (i.e., 0 or positive number) was found.
|
|
@retval -1 otherwise.
|
|
*/
|
|
rpl_gno parse_gno(const char **s);
|
|
/**
|
|
Formats a GNO as a string.
|
|
|
|
@param s The buffer.
|
|
@param gno The GNO.
|
|
@return Length of the generated string.
|
|
*/
|
|
int format_gno(char *s, rpl_gno gno);
|
|
|
|
typedef Uuid rpl_sid;
|
|
|
|
|
|
/**
|
|
This has the functionality of mysql_rwlock_t, with two differences:
|
|
1. It has additional operations to check if the read and/or write lock
|
|
is held at the moment.
|
|
2. It is wrapped in an object-oriented interface.
|
|
|
|
Note that the assertions do not check whether *this* thread has
|
|
taken the lock (that would be more complicated as it would require a
|
|
dynamic data structure). Luckily, it is still likely that the
|
|
assertions find bugs where a thread forgot to take a lock, because
|
|
most of the time most locks are only used by one thread at a time.
|
|
|
|
The assertions are no-ops when DBUG is off.
|
|
*/
|
|
class Checkable_rwlock
|
|
{
|
|
public:
|
|
/// Initialize this Checkable_rwlock.
|
|
Checkable_rwlock(
|
|
#if defined(HAVE_PSI_INTERFACE)
|
|
PSI_rwlock_key psi_key= 0
|
|
#endif
|
|
)
|
|
{
|
|
#ifndef DBUG_OFF
|
|
my_atomic_store32(&lock_state, 0);
|
|
dbug_trace= true;
|
|
#else
|
|
is_write_lock= false;
|
|
#endif
|
|
#if defined(HAVE_PSI_INTERFACE)
|
|
mysql_rwlock_init(psi_key, &rwlock);
|
|
#else
|
|
mysql_rwlock_init(0, &rwlock);
|
|
#endif
|
|
}
|
|
/// Destroy this Checkable_lock.
|
|
~Checkable_rwlock()
|
|
{
|
|
mysql_rwlock_destroy(&rwlock);
|
|
}
|
|
|
|
/// Acquire the read lock.
|
|
inline void rdlock()
|
|
{
|
|
mysql_rwlock_rdlock(&rwlock);
|
|
assert_no_wrlock();
|
|
#ifndef DBUG_OFF
|
|
if (dbug_trace)
|
|
DBUG_PRINT("info", ("%p.rdlock()", this));
|
|
my_atomic_add32(&lock_state, 1);
|
|
#endif
|
|
}
|
|
/// Acquire the write lock.
|
|
inline void wrlock()
|
|
{
|
|
mysql_rwlock_wrlock(&rwlock);
|
|
assert_no_lock();
|
|
#ifndef DBUG_OFF
|
|
if (dbug_trace)
|
|
DBUG_PRINT("info", ("%p.wrlock()", this));
|
|
my_atomic_store32(&lock_state, -1);
|
|
#else
|
|
is_write_lock= true;
|
|
#endif
|
|
}
|
|
/// Release the lock (whether it is a write or read lock).
|
|
inline void unlock()
|
|
{
|
|
assert_some_lock();
|
|
#ifndef DBUG_OFF
|
|
if (dbug_trace)
|
|
DBUG_PRINT("info", ("%p.unlock()", this));
|
|
int val= my_atomic_load32(&lock_state);
|
|
if (val > 0)
|
|
my_atomic_add32(&lock_state, -1);
|
|
else if (val == -1)
|
|
my_atomic_store32(&lock_state, 0);
|
|
else
|
|
DBUG_ASSERT(0);
|
|
#else
|
|
is_write_lock= false;
|
|
#endif
|
|
mysql_rwlock_unlock(&rwlock);
|
|
}
|
|
/**
|
|
Return true if the write lock is held. Must only be called by
|
|
threads that hold a lock.
|
|
*/
|
|
inline bool is_wrlock()
|
|
{
|
|
assert_some_lock();
|
|
#ifndef DBUG_OFF
|
|
return get_state() == -1;
|
|
#else
|
|
return is_write_lock;
|
|
#endif
|
|
}
|
|
|
|
/// Assert that some thread holds either the read or the write lock.
|
|
inline void assert_some_lock() const
|
|
{ DBUG_ASSERT(get_state() != 0); }
|
|
/// Assert that some thread holds the read lock.
|
|
inline void assert_some_rdlock() const
|
|
{ DBUG_ASSERT(get_state() > 0); }
|
|
/// Assert that some thread holds the write lock.
|
|
inline void assert_some_wrlock() const
|
|
{ DBUG_ASSERT(get_state() == -1); }
|
|
/// Assert that no thread holds the write lock.
|
|
inline void assert_no_wrlock() const
|
|
{ DBUG_ASSERT(get_state() >= 0); }
|
|
/// Assert that no thread holds the read lock.
|
|
inline void assert_no_rdlock() const
|
|
{ DBUG_ASSERT(get_state() <= 0); }
|
|
/// Assert that no thread holds read or write lock.
|
|
inline void assert_no_lock() const
|
|
{ DBUG_ASSERT(get_state() == 0); }
|
|
|
|
#ifndef DBUG_OFF
|
|
|
|
/// If enabled, print any lock/unlock operations to the DBUG trace.
|
|
bool dbug_trace;
|
|
|
|
private:
|
|
/**
|
|
The state of the lock:
|
|
0 - not locked
|
|
-1 - write locked
|
|
>0 - read locked by that many threads
|
|
*/
|
|
int32 lock_state;
|
|
/// Read lock_state atomically and return the value.
|
|
inline int32 get_state() const
|
|
{
|
|
return my_atomic_load32(const_cast<volatile int32*>(&lock_state));
|
|
}
|
|
|
|
#else
|
|
|
|
private:
|
|
bool is_write_lock;
|
|
|
|
#endif
|
|
/// The rwlock.
|
|
mysql_rwlock_t rwlock;
|
|
};
|
|
|
|
|
|
/// Protects Gtid_state. See comment above gtid_state for details.
|
|
extern Checkable_rwlock *global_sid_lock;
|
|
|
|
/// One of the locks that protects GTID_MODE. See
|
|
/// get_gtid_mode(enum_gtid_mode_lock).
|
|
extern Checkable_rwlock *gtid_mode_lock;
|
|
|
|
|
|
/**
|
|
Represents a bidirectional map between SID and SIDNO.
|
|
|
|
SIDNOs are always numbers greater or equal to 1.
|
|
|
|
This data structure OPTIONALLY knows of a read-write lock that
|
|
protects the number of SIDNOs. The lock is provided by the invoker
|
|
of the constructor and it is generally the caller's responsibility
|
|
to acquire the read lock. If the lock is not NULL, access methods
|
|
assert that the caller already holds the read (or write) lock. If
|
|
the lock is not NULL and a method of this class grows the number of
|
|
SIDNOs, then the method temporarily upgrades this lock to a write
|
|
lock and then degrades it to a read lock again; there will be a
|
|
short period when the lock is not held at all.
|
|
*/
|
|
class Sid_map
|
|
{
|
|
public:
|
|
/**
|
|
Create this Sid_map.
|
|
|
|
@param sid_lock Read-write lock that protects updates to the
|
|
number of SIDNOs.
|
|
*/
|
|
Sid_map(Checkable_rwlock *sid_lock);
|
|
/// Destroy this Sid_map.
|
|
~Sid_map();
|
|
#ifdef NON_DISABLED_GTID
|
|
/**
|
|
Clears this Sid_map (for RESET MASTER)
|
|
|
|
@return RETURN_STATUS_OK or RETURN_STAUTS_REPORTED_ERROR
|
|
*/
|
|
enum_return_status clear();
|
|
#endif
|
|
/**
|
|
Add the given SID to this map if it does not already exist.
|
|
|
|
The caller must hold the read lock or write lock on sid_lock
|
|
before invoking this function. If the SID does not exist in this
|
|
map, it will release the read lock, take a write lock, update the
|
|
map, release the write lock, and take the read lock again.
|
|
|
|
@param sid The SID.
|
|
@retval SIDNO The SIDNO for the SID (a new SIDNO if the SID did
|
|
not exist, an existing if it did exist).
|
|
@retval negative Error. This function calls my_error.
|
|
*/
|
|
rpl_sidno add_sid(const rpl_sid &sid);
|
|
/**
|
|
Get the SIDNO for a given SID
|
|
|
|
The caller must hold the read lock on sid_lock before invoking
|
|
this function.
|
|
|
|
@param sid The SID.
|
|
@retval SIDNO if the given SID exists in this map.
|
|
@retval 0 if the given SID does not exist in this map.
|
|
*/
|
|
rpl_sidno sid_to_sidno(const rpl_sid &sid) const
|
|
{
|
|
if (sid_lock != NULL)
|
|
sid_lock->assert_some_lock();
|
|
Node *node= (Node *)my_hash_search(&_sid_to_sidno, sid.bytes,
|
|
binary_log::Uuid::BYTE_LENGTH);
|
|
if (node == NULL)
|
|
return 0;
|
|
return node->sidno;
|
|
}
|
|
/**
|
|
Get the SID for a given SIDNO.
|
|
|
|
Raises an assertion if the SIDNO is not valid.
|
|
|
|
If need_lock is true, acquires sid_lock->rdlock; otherwise asserts
|
|
that it is held already.
|
|
|
|
@param sidno The SIDNO.
|
|
@param need_lock If true, and sid_lock!=NULL, this function will
|
|
acquire sid_lock before looking up the sid, and then release
|
|
it. If false, and sid_lock!=NULL, this function will assert the
|
|
sid_lock is already held. If sid_lock==NULL, nothing is done
|
|
w.r.t. locking.
|
|
@retval NULL The SIDNO does not exist in this map.
|
|
@retval pointer Pointer to the SID. The data is shared with this
|
|
Sid_map, so should not be modified. It is safe to read the data
|
|
even after this Sid_map is modified, but not if this Sid_map is
|
|
destroyed.
|
|
*/
|
|
const rpl_sid &sidno_to_sid(rpl_sidno sidno, bool need_lock= false) const
|
|
{
|
|
if (sid_lock != NULL)
|
|
{
|
|
if (need_lock)
|
|
sid_lock->rdlock();
|
|
else
|
|
sid_lock->assert_some_lock();
|
|
}
|
|
DBUG_ASSERT(sidno >= 1 && sidno <= get_max_sidno());
|
|
const rpl_sid &ret= (_sidno_to_sid[sidno - 1])->sid;
|
|
if (sid_lock != NULL && need_lock)
|
|
sid_lock->unlock();
|
|
return ret;
|
|
}
|
|
/**
|
|
Return the n'th smallest sidno, in the order of the SID's UUID.
|
|
|
|
The caller must hold the read or write lock on sid_lock before
|
|
invoking this function.
|
|
|
|
@param n A number in the interval [0, get_max_sidno()-1], inclusively.
|
|
*/
|
|
rpl_sidno get_sorted_sidno(rpl_sidno n) const
|
|
{
|
|
if (sid_lock != NULL)
|
|
sid_lock->assert_some_lock();
|
|
return _sorted[n];
|
|
}
|
|
/**
|
|
Return the biggest sidno in this Sid_map.
|
|
|
|
The caller must hold the read or write lock on sid_lock before
|
|
invoking this function.
|
|
*/
|
|
rpl_sidno get_max_sidno() const
|
|
{
|
|
if (sid_lock != NULL)
|
|
sid_lock->assert_some_lock();
|
|
return static_cast<rpl_sidno>(_sidno_to_sid.size());
|
|
}
|
|
|
|
/// Return the sid_lock.
|
|
Checkable_rwlock *get_sid_lock() const { return sid_lock; }
|
|
|
|
/**
|
|
Deep copy this Sid_map to dest.
|
|
|
|
The caller must hold:
|
|
* the read lock on this sid_lock
|
|
* the write lock on the dest sid_lock
|
|
before invoking this function.
|
|
|
|
@param[out] dest The Sid_map to which the sids and sidnos will
|
|
be copied.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status copy(Sid_map *dest);
|
|
|
|
private:
|
|
/// Node pointed to by both the hash and the array.
|
|
struct Node
|
|
{
|
|
rpl_sidno sidno;
|
|
rpl_sid sid;
|
|
};
|
|
|
|
/**
|
|
Create a Node from the given SIDNO and SID and add it to
|
|
_sidno_to_sid, _sid_to_sidno, and _sorted.
|
|
|
|
The caller must hold the write lock on sid_lock before invoking
|
|
this function.
|
|
|
|
@param sidno The SIDNO to add.
|
|
@param sid The SID to add.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status add_node(rpl_sidno sidno, const rpl_sid &sid);
|
|
|
|
/// Read-write lock that protects updates to the number of SIDNOs.
|
|
mutable Checkable_rwlock *sid_lock;
|
|
|
|
/**
|
|
Array that maps SIDNO to SID; the element at index N points to a
|
|
Node with SIDNO N-1.
|
|
*/
|
|
Prealloced_array<Node*, 8, true>_sidno_to_sid;
|
|
/**
|
|
Hash that maps SID to SIDNO. The keys in this array are of type
|
|
rpl_sid.
|
|
*/
|
|
HASH _sid_to_sidno;
|
|
/**
|
|
Array that maps numbers in the interval [0, get_max_sidno()-1] to
|
|
SIDNOs, in order of increasing SID.
|
|
|
|
@see Sid_map::get_sorted_sidno.
|
|
*/
|
|
Prealloced_array<rpl_sidno, 8, true> _sorted;
|
|
};
|
|
|
|
|
|
extern Sid_map *global_sid_map;
|
|
|
|
|
|
/**
|
|
Represents a growable array where each element contains a mutex and
|
|
a condition variable.
|
|
|
|
Each element can be locked, unlocked, broadcast, or waited for, and
|
|
it is possible to call "THD::enter_cond" for the condition. The
|
|
allowed indexes range from 0, inclusive, to get_max_index(),
|
|
inclusive. Initially there are zero elements (and get_max_index()
|
|
returns -1); more elements can be allocated by calling
|
|
ensure_index().
|
|
|
|
This data structure has a read-write lock that protects the number
|
|
of elements. The lock is provided by the invoker of the constructor
|
|
and it is generally the caller's responsibility to acquire the read
|
|
lock. Access methods assert that the caller already holds the read
|
|
(or write) lock. If a method of this class grows the number of
|
|
elements, then the method temporarily upgrades this lock to a write
|
|
lock and then degrades it to a read lock again; there will be a
|
|
short period when the lock is not held at all.
|
|
*/
|
|
class Mutex_cond_array
|
|
{
|
|
public:
|
|
/**
|
|
Create a new Mutex_cond_array.
|
|
|
|
@param global_lock Read-write lock that protects updates to the
|
|
number of elements.
|
|
*/
|
|
Mutex_cond_array(Checkable_rwlock *global_lock);
|
|
/// Destroy this object.
|
|
~Mutex_cond_array();
|
|
/// Lock the n'th mutex.
|
|
inline void lock(int n) const
|
|
{
|
|
assert_not_owner(n);
|
|
mysql_mutex_lock(&get_mutex_cond(n)->mutex);
|
|
}
|
|
/// Unlock the n'th mutex.
|
|
inline void unlock(int n) const
|
|
{
|
|
assert_owner(n);
|
|
mysql_mutex_unlock(&get_mutex_cond(n)->mutex);
|
|
}
|
|
/// Broadcast the n'th condition.
|
|
inline void broadcast(int n) const
|
|
{
|
|
mysql_cond_broadcast(&get_mutex_cond(n)->cond);
|
|
}
|
|
/**
|
|
Assert that this thread owns the n'th mutex.
|
|
This is a no-op if DBUG_OFF is on.
|
|
*/
|
|
inline void assert_owner(int n) const
|
|
{
|
|
#ifndef DBUG_OFF
|
|
mysql_mutex_assert_owner(&get_mutex_cond(n)->mutex);
|
|
#endif
|
|
}
|
|
/**
|
|
Assert that this thread does not own the n'th mutex.
|
|
This is a no-op if DBUG_OFF is on.
|
|
*/
|
|
inline void assert_not_owner(int n) const
|
|
{
|
|
#ifndef DBUG_OFF
|
|
mysql_mutex_assert_not_owner(&get_mutex_cond(n)->mutex);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
Wait for signal on the n'th condition variable.
|
|
|
|
The caller must hold the read lock or write lock on sid_lock, as
|
|
well as the nth mutex lock, before invoking this function. The
|
|
sid_lock will be released, whereas the mutex will be released
|
|
during the wait and (atomically) re-acquired when the wait ends
|
|
or the timeout is reached.
|
|
|
|
@param[in] thd THD object for the calling thread.
|
|
@param[in] n Condition variable to wait for.
|
|
@param[in] abstime The absolute point in time when the wait times
|
|
out and stops, or NULL to wait indefinitely.
|
|
|
|
@retval false Success.
|
|
@retval true Failure: either timeout or thread was killed. If
|
|
thread was killed, the error has been generated.
|
|
*/
|
|
inline bool wait(const THD* thd, int sidno, struct timespec* abstime) const
|
|
{
|
|
DBUG_ENTER("Mutex_cond_array::wait");
|
|
int error= 0;
|
|
Mutex_cond *mutex_cond= get_mutex_cond(sidno);
|
|
global_lock->unlock();
|
|
mysql_mutex_assert_owner(&mutex_cond->mutex);
|
|
if (is_thd_killed(thd))
|
|
DBUG_RETURN(true);
|
|
if (abstime != NULL)
|
|
error= mysql_cond_timedwait(&mutex_cond->cond,
|
|
&mutex_cond->mutex, abstime);
|
|
else
|
|
mysql_cond_wait(&mutex_cond->cond, &mutex_cond->mutex);
|
|
mysql_mutex_assert_owner(&mutex_cond->mutex);
|
|
DBUG_RETURN(error == ETIMEDOUT || error == ETIME);
|
|
}
|
|
#ifndef MYSQL_CLIENT
|
|
/// Execute THD::enter_cond for the n'th condition variable.
|
|
void enter_cond(THD *thd, int n, PSI_stage_info *stage,
|
|
PSI_stage_info *old_stage) const;
|
|
#endif // ifndef MYSQL_CLIENT
|
|
/// Return the greatest addressable index in this Mutex_cond_array.
|
|
inline int get_max_index() const
|
|
{
|
|
global_lock->assert_some_lock();
|
|
return static_cast<int>(m_array.size() - 1);
|
|
}
|
|
/**
|
|
Grows the array so that the given index fits.
|
|
|
|
If the array is grown, the global_lock is temporarily upgraded to
|
|
a write lock and then degraded again; there will be a
|
|
short period when the lock is not held at all.
|
|
|
|
@param n The index.
|
|
@return RETURN_OK or RETURN_REPORTED_ERROR
|
|
*/
|
|
enum_return_status ensure_index(int n);
|
|
private:
|
|
/**
|
|
Return true if the given THD is killed.
|
|
|
|
@param[in] thd - The thread object
|
|
@retval true - thread is killed
|
|
false - thread not killed
|
|
*/
|
|
bool is_thd_killed(const THD* thd) const;
|
|
/// A mutex/cond pair.
|
|
struct Mutex_cond
|
|
{
|
|
mysql_mutex_t mutex;
|
|
mysql_cond_t cond;
|
|
};
|
|
/// Return the Nth Mutex_cond object
|
|
inline Mutex_cond *get_mutex_cond(int n) const
|
|
{
|
|
global_lock->assert_some_lock();
|
|
DBUG_ASSERT(n <= get_max_index());
|
|
Mutex_cond *ret= m_array[n];
|
|
DBUG_ASSERT(ret);
|
|
return ret;
|
|
}
|
|
/// Read-write lock that protects updates to the number of elements.
|
|
mutable Checkable_rwlock *global_lock;
|
|
Prealloced_array<Mutex_cond*, 8, true> m_array;
|
|
};
|
|
|
|
|
|
/**
|
|
Holds information about a GTID interval: the sidno, the first gno
|
|
and the last gno of this interval.
|
|
*/
|
|
struct Gtid_interval
|
|
{
|
|
/* SIDNO of this Gtid interval. */
|
|
rpl_sidno sidno;
|
|
/* The first GNO of this Gtid interval. */
|
|
rpl_gno gno_start;
|
|
/* The last GNO of this Gtid interval. */
|
|
rpl_gno gno_end;
|
|
void set(rpl_sidno sid_no, rpl_gno start, rpl_gno end)
|
|
{
|
|
sidno= sid_no;
|
|
gno_start= start;
|
|
gno_end= end;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
TODO: Move this structure to libbinlogevents/include/control_events.h
|
|
when we start using C++11.
|
|
Holds information about a GTID: the sidno and the gno.
|
|
|
|
This is a POD. It has to be a POD because it is part of
|
|
Gtid_specification, which has to be a POD because it is used in
|
|
THD::variables.
|
|
*/
|
|
struct Gtid
|
|
{
|
|
/// SIDNO of this Gtid.
|
|
rpl_sidno sidno;
|
|
/// GNO of this Gtid.
|
|
rpl_gno gno;
|
|
|
|
/// Set both components to 0.
|
|
void clear() { sidno= 0; gno= 0; }
|
|
/// Set both components to the given, positive values.
|
|
void set(rpl_sidno sidno_arg, rpl_gno gno_arg)
|
|
{
|
|
DBUG_ASSERT(sidno_arg > 0);
|
|
DBUG_ASSERT(gno_arg > 0);
|
|
sidno= sidno_arg;
|
|
gno= gno_arg;
|
|
}
|
|
/**
|
|
Return true if sidno is zero (and assert that gno is zero too in
|
|
this case).
|
|
*/
|
|
bool is_empty() const
|
|
{
|
|
// check that gno is not set inconsistently
|
|
if (sidno <= 0)
|
|
DBUG_ASSERT(gno == 0);
|
|
else
|
|
DBUG_ASSERT(gno > 0);
|
|
return sidno == 0;
|
|
}
|
|
/**
|
|
The maximal length of the textual representation of a SID, not
|
|
including the terminating '\0'.
|
|
*/
|
|
static const int MAX_TEXT_LENGTH= binary_log::Uuid::TEXT_LENGTH + 1 + MAX_GNO_TEXT_LENGTH;
|
|
/**
|
|
Return true if parse() would succeed, but don't store the
|
|
result anywhere.
|
|
*/
|
|
static bool is_valid(const char *text);
|
|
/**
|
|
Convert a Gtid to a string.
|
|
@param sid the sid to use. This overrides the sidno of this Gtid.
|
|
@param[out] buf Buffer to store the Gtid in (normally
|
|
MAX_TEXT_LENGTH+1 bytes long).
|
|
@return Length of the string, not counting '\0'.
|
|
*/
|
|
int to_string(const rpl_sid &sid, char *buf) const;
|
|
/**
|
|
Convert this Gtid to a string.
|
|
@param sid_map sid_map to use when converting sidno to a SID.
|
|
@param[out] buf Buffer to store the Gtid in (normally
|
|
MAX_TEXT_LENGTH+1 bytes long).
|
|
@param need_lock If true, the function will acquire sid_map->sid_lock; otherwise it will assert that the lock is held.
|
|
@return Length of the string, not counting '\0'.
|
|
*/
|
|
int to_string(const Sid_map *sid_map, char *buf, bool need_lock= false) const;
|
|
/// Returns true if this Gtid has the same sid and gno as 'other'.
|
|
bool equals(const Gtid &other) const
|
|
{ return sidno == other.sidno && gno == other.gno; }
|
|
/**
|
|
Parses the given string and stores in this Gtid.
|
|
|
|
@param text The text to parse
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status parse(Sid_map *sid_map, const char *text);
|
|
|
|
#ifndef DBUG_OFF
|
|
/// Debug only: print this Gtid to stdout.
|
|
void print(const Sid_map *sid_map) const
|
|
{
|
|
char buf[MAX_TEXT_LENGTH + 1];
|
|
to_string(sid_map, buf);
|
|
printf("%s\n", buf);
|
|
}
|
|
#endif
|
|
/// Print this Gtid to the trace file if debug is enabled; no-op otherwise.
|
|
void dbug_print(const Sid_map *sid_map, const char *text= "",
|
|
bool need_lock= false) const
|
|
{
|
|
#ifndef DBUG_OFF
|
|
char buf[MAX_TEXT_LENGTH + 1];
|
|
to_string(sid_map, buf, need_lock);
|
|
DBUG_PRINT("info", ("%s%s%s", text, *text ? ": " : "", buf));
|
|
#endif
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
Represents a set of GTIDs.
|
|
|
|
This is structured as an array, indexed by SIDNO, where each element
|
|
contains a linked list of intervals.
|
|
|
|
This data structure OPTIONALLY knows of a Sid_map that gives a
|
|
correspondence between SIDNO and SID. If the Sid_map is NULL, then
|
|
operations that require a Sid_map - printing and parsing - raise an
|
|
assertion.
|
|
|
|
This data structure OPTIONALLY knows of a read-write lock that
|
|
protects the number of SIDNOs. The lock is provided by the invoker
|
|
of the constructor and it is generally the caller's responsibility
|
|
to acquire the read lock. If the lock is not NULL, access methods
|
|
assert that the caller already holds the read (or write) lock. If
|
|
the lock is not NULL and a method of this class grows the number of
|
|
SIDNOs, then the method temporarily upgrades this lock to a write
|
|
lock and then degrades it to a read lock again; there will be a
|
|
short period when the lock is not held at all.
|
|
*/
|
|
class Gtid_set
|
|
{
|
|
public:
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
static PSI_mutex_key key_gtid_executed_free_intervals_mutex;
|
|
#endif
|
|
/**
|
|
Constructs a new, empty Gtid_set.
|
|
|
|
@param sid_map The Sid_map to use, or NULL if this Gtid_set
|
|
should not have a Sid_map.
|
|
@param sid_lock Read-write lock that protects updates to the
|
|
number of SIDs. This may be NULL if such changes do not need to be
|
|
protected.
|
|
@param free_intervals_mutex_key Performance_schema instrumentation
|
|
key to use for the free_intervals mutex.
|
|
*/
|
|
Gtid_set(Sid_map *sid_map, Checkable_rwlock *sid_lock= NULL);
|
|
/**
|
|
Constructs a new Gtid_set that contains the groups in the given string, in the same format as add_gtid_text(char *).
|
|
|
|
@param sid_map The Sid_map to use for SIDs.
|
|
@param text The text to parse.
|
|
@param status Will be set to RETURN_STATUS_OK on success or
|
|
RETURN_STATUS_REPORTED_ERROR on error.
|
|
@param sid_lock Read/write lock to protect changes in the number
|
|
of SIDs with. This may be NULL if such changes do not need to be
|
|
protected.
|
|
@param free_intervals_mutex_key Performance_schema instrumentation
|
|
key to use for the free_intervals mutex.
|
|
|
|
If sid_lock != NULL, then the read lock on sid_lock must be held
|
|
before calling this function. If the array is grown, sid_lock is
|
|
temporarily upgraded to a write lock and then degraded again;
|
|
there will be a short period when the lock is not held at all.
|
|
*/
|
|
Gtid_set(Sid_map *sid_map, const char *text, enum_return_status *status,
|
|
Checkable_rwlock *sid_lock= NULL);
|
|
private:
|
|
/// Worker for the constructor.
|
|
void init();
|
|
public:
|
|
/// Destroy this Gtid_set.
|
|
~Gtid_set();
|
|
/**
|
|
Removes all groups from this Gtid_set.
|
|
|
|
This does not deallocate anything: if groups are added later,
|
|
existing allocated memory will be re-used.
|
|
*/
|
|
void clear();
|
|
/**
|
|
Adds the given GTID to this Gtid_set.
|
|
|
|
The SIDNO must exist in the Gtid_set before this function is called.
|
|
|
|
@param sidno SIDNO of the group to add.
|
|
@param gno GNO of the group to add.
|
|
*/
|
|
void _add_gtid(rpl_sidno sidno, rpl_gno gno)
|
|
{
|
|
DBUG_ENTER("Gtid_set::_add_gtid(sidno, gno)");
|
|
Interval_iterator ivit(this, sidno);
|
|
Free_intervals_lock lock(this);
|
|
add_gno_interval(&ivit, gno, gno + 1, &lock);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
/**
|
|
Removes the given GTID from this Gtid_set.
|
|
|
|
@param sidno SIDNO of the group to remove.
|
|
@param gno GNO of the group to remove.
|
|
*/
|
|
void _remove_gtid(rpl_sidno sidno, rpl_gno gno)
|
|
{
|
|
DBUG_ENTER("Gtid_set::_remove_gtid(rpl_sidno, rpl_gno)");
|
|
if (sidno <= get_max_sidno())
|
|
{
|
|
Interval_iterator ivit(this, sidno);
|
|
Free_intervals_lock lock(this);
|
|
remove_gno_interval(&ivit, gno, gno + 1, &lock);
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
/**
|
|
Adds the given GTID to this Gtid_set.
|
|
|
|
The SIDNO must exist in the Gtid_set before this function is called.
|
|
|
|
@param gtid Gtid to add.
|
|
*/
|
|
void _add_gtid(const Gtid >id)
|
|
{ _add_gtid(gtid.sidno, gtid.gno); }
|
|
/**
|
|
Removes the given GTID from this Gtid_set.
|
|
|
|
@param gtid Gtid to remove.
|
|
*/
|
|
void _remove_gtid(const Gtid >id)
|
|
{
|
|
_remove_gtid(gtid.sidno, gtid.gno);
|
|
}
|
|
/**
|
|
Adds all groups from the given Gtid_set to this Gtid_set.
|
|
|
|
If sid_lock != NULL, then the read lock must be held before
|
|
calling this function. If a new sidno is added so that the array
|
|
of lists of intervals is grown, sid_lock is temporarily upgraded
|
|
to a write lock and then degraded again; there will be a short
|
|
period when the lock is not held at all.
|
|
|
|
@param other The Gtid_set to add.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status add_gtid_set(const Gtid_set *other);
|
|
/**
|
|
Removes all groups in the given Gtid_set from this Gtid_set.
|
|
|
|
@param other The Gtid_set to remove.
|
|
*/
|
|
void remove_gtid_set(const Gtid_set *other);
|
|
/**
|
|
Removes all intervals of 'other' for a given SIDNO, from 'this'.
|
|
|
|
Example:
|
|
this = A:1-100, B:1-100
|
|
other = A:1-100, B:1-50, C:1-100
|
|
this.remove_intervals_for_sidno(other, B) = A:1-100, B:51-100
|
|
|
|
It is not required that the intervals exist in this Gtid_set.
|
|
|
|
@param other The set to remove.
|
|
@param sidno The sidno to remove.
|
|
*/
|
|
void remove_intervals_for_sidno(Gtid_set *other, rpl_sidno sidno);
|
|
/**
|
|
Adds the set of GTIDs represented by the given string to this Gtid_set.
|
|
|
|
The string must have the format of a comma-separated list of zero
|
|
or more of the following:
|
|
|
|
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX(:NUMBER+(-NUMBER)?)*
|
|
| ANONYMOUS
|
|
|
|
Each X is a hexadecimal digit (upper- or lowercase).
|
|
NUMBER is a decimal, 0xhex, or 0oct number.
|
|
|
|
The start of an interval must be greater than 0. The end of an
|
|
interval may be 0, but any interval that has an endpoint that
|
|
is smaller than the start is discarded.
|
|
|
|
If sid_lock != NULL, then the read lock on sid_lock must be held
|
|
before calling this function. If a new sidno is added so that the
|
|
array of lists of intervals is grown, sid_lock is temporarily
|
|
upgraded to a write lock and then degraded again; there will be a
|
|
short period when the lock is not held at all.
|
|
|
|
@param text The string to parse.
|
|
@param anonymous[in,out] If this is NULL, ANONYMOUS is not
|
|
allowed. If this is not NULL, it will be set to true if the
|
|
anonymous group was found; false otherwise.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status add_gtid_text(const char *text, bool *anonymous= NULL);
|
|
/**
|
|
Decodes a Gtid_set from the given string.
|
|
|
|
@param string The string to parse.
|
|
@param length The number of bytes.
|
|
@param actual_length If this is not NULL, it is set to the number
|
|
of bytes used by the encoding (which may be less than 'length').
|
|
If this is NULL, an error is generated if the encoding is shorter
|
|
than the given 'length'.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status add_gtid_encoding(const uchar *encoded, size_t length,
|
|
size_t *actual_length= NULL);
|
|
/// Return true iff the given GTID exists in this set.
|
|
bool contains_gtid(rpl_sidno sidno, rpl_gno gno) const;
|
|
/// Return true iff the given GTID exists in this set.
|
|
bool contains_gtid(const Gtid >id) const
|
|
{ return contains_gtid(gtid.sidno, gtid.gno); }
|
|
// Get last gno or 0 if this set is empty.
|
|
rpl_gno get_last_gno(rpl_sidno sidno) const;
|
|
/// Returns the maximal sidno that this Gtid_set currently has space for.
|
|
rpl_sidno get_max_sidno() const
|
|
{
|
|
if (sid_lock)
|
|
sid_lock->assert_some_lock();
|
|
return static_cast<rpl_sidno>(m_intervals.size());
|
|
}
|
|
/**
|
|
Allocates space for all sidnos up to the given sidno in the array of intervals.
|
|
The sidno must exist in the Sid_map associated with this Gtid_set.
|
|
|
|
If sid_lock != NULL, then the read lock on sid_lock must be held
|
|
before calling this function. If the array is grown, sid_lock is
|
|
temporarily upgraded to a write lock and then degraded again;
|
|
there will be a short period when the lock is not held at all.
|
|
|
|
@param sidno The SIDNO.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status ensure_sidno(rpl_sidno sidno);
|
|
/// Returns true if this Gtid_set is a subset of the other Gtid_set.
|
|
bool is_subset(const Gtid_set *super) const;
|
|
/// Returns true if this Gtid_set is a non equal subset of the other Gtid_set.
|
|
bool is_subset_not_equals(const Gtid_set *super) const
|
|
{
|
|
return (is_subset(super) && !equals(super));
|
|
}
|
|
|
|
/**
|
|
Returns true if this Gtid_set is a subset of the given gtid_set
|
|
on the given superset_sidno and subset_sidno.
|
|
|
|
@param super Gtid_set with which 'this'::gtid_set needs to be
|
|
compared
|
|
@param superset_sidno The sidno that will be compared, relative to
|
|
super->sid_map.
|
|
@param subset_sidno The sidno that will be compared, relative to
|
|
this->sid_map.
|
|
@return true If 'this' Gtid_set is subset of given
|
|
'super' Gtid_set.
|
|
false If 'this' Gtid_set is *not* subset of given
|
|
'super' Gtid_set.
|
|
*/
|
|
bool is_subset_for_sid(const Gtid_set *super, rpl_sidno superset_sidno,
|
|
rpl_sidno subset_sidno) const;
|
|
/// Returns true if there is a least one element of this Gtid_set in
|
|
/// the other Gtid_set.
|
|
bool is_intersection_nonempty(const Gtid_set *other) const;
|
|
/**
|
|
Add the intersection of this Gtid_set and the other Gtid_set to result.
|
|
|
|
@param other The Gtid_set to intersect with this Gtid_set
|
|
@param result Gtid_set where the result will be stored.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status intersection(const Gtid_set *other, Gtid_set *result);
|
|
/// Returns true if this Gtid_set is empty.
|
|
bool is_empty() const
|
|
{
|
|
Gtid_iterator git(this);
|
|
return git.get().sidno == 0;
|
|
}
|
|
/**
|
|
Returns true if this Gtid_set contains at least one GTID with
|
|
the given SIDNO.
|
|
|
|
@param sidno The SIDNO to test.
|
|
@retval true The SIDNO is less than or equal to the max SIDNO, and
|
|
there is at least one group with this SIDNO.
|
|
@retval false The SIDNO is greater than the max SIDNO, or there is
|
|
no group with this SIDNO.
|
|
*/
|
|
bool contains_sidno(rpl_sidno sidno) const
|
|
{
|
|
DBUG_ASSERT(sidno >= 1);
|
|
if (sidno > get_max_sidno())
|
|
return false;
|
|
Const_interval_iterator ivit(this, sidno);
|
|
return ivit.get() != NULL;
|
|
}
|
|
/**
|
|
Returns true if the given string is a valid specification of a
|
|
Gtid_set, false otherwise.
|
|
*/
|
|
static bool is_valid(const char *text);
|
|
|
|
/**
|
|
Class Gtid_set::String_format defines the separators used by
|
|
Gtid_set::to_string.
|
|
*/
|
|
struct String_format
|
|
{
|
|
/// The generated string begins with this.
|
|
const char *begin;
|
|
/// The generated string begins with this.
|
|
const char *end;
|
|
/// In 'SID:GNO', this is the ':'
|
|
const char *sid_gno_separator;
|
|
/// In 'SID:GNO-GNO', this is the '-'
|
|
const char *gno_start_end_separator;
|
|
/// In 'SID:GNO:GNO', this is the second ':'
|
|
const char *gno_gno_separator;
|
|
/// In 'SID:GNO,SID:GNO', this is the ','
|
|
const char *gno_sid_separator;
|
|
/// If the set is empty and this is not NULL, then this string is generated.
|
|
const char *empty_set_string;
|
|
/// The following fields are the lengths of each field above.
|
|
const int begin_length;
|
|
const int end_length;
|
|
const int sid_gno_separator_length;
|
|
const int gno_start_end_separator_length;
|
|
const int gno_gno_separator_length;
|
|
const int gno_sid_separator_length;
|
|
const int empty_set_string_length;
|
|
};
|
|
/**
|
|
Returns the length of the output from to_string.
|
|
|
|
@warning This does not include the trailing '\0', so your buffer
|
|
needs space for get_string_length() + 1 characters.
|
|
|
|
@param string_format String_format object that specifies
|
|
separators in the resulting text.
|
|
@return The length.
|
|
*/
|
|
size_t get_string_length(const String_format *string_format= NULL) const;
|
|
/**
|
|
Formats this Gtid_set as a string and saves in a given buffer.
|
|
|
|
@param[out] buf Pointer to the buffer where the string should be
|
|
stored. This should have size at least get_string_length()+1.
|
|
@param need_lock If this Gtid_set has a sid_lock, then the write
|
|
lock must be held while generating the string. If this parameter
|
|
is true, then this function acquires and releases the lock;
|
|
otherwise it asserts that the caller holds the lock.
|
|
@param string_format String_format object that specifies
|
|
separators in the resulting text.
|
|
@return Length of the generated string.
|
|
*/
|
|
size_t to_string(char *buf, bool need_lock= false,
|
|
const String_format *string_format= NULL) const;
|
|
|
|
/**
|
|
Formats a Gtid_set as a string and saves in a newly allocated buffer.
|
|
@param[out] buf Pointer to pointer to string. The function will
|
|
set it to point to the newly allocated buffer, or NULL on out of memory.
|
|
@param need_lock If this Gtid_set has a sid_lock, then the write
|
|
lock must be held while generating the string. If this parameter
|
|
is true, then this function acquires and releases the lock;
|
|
otherwise it asserts that the caller holds the lock.
|
|
@param string_format Specifies how to format the string.
|
|
@retval Length of the generated string, or -1 on out of memory.
|
|
*/
|
|
long to_string(char **buf, bool need_lock= false,
|
|
const String_format *string_format= NULL) const;
|
|
#ifndef DBUG_OFF
|
|
/// Debug only: Print this Gtid_set to stdout.
|
|
void print(bool need_lock= false,
|
|
const Gtid_set::String_format *sf= NULL) const
|
|
{
|
|
char *str;
|
|
to_string(&str, need_lock, sf);
|
|
printf("%s\n", str ? str : "out of memory in Gtid_set::print");
|
|
my_free(str);
|
|
}
|
|
#endif
|
|
/**
|
|
Print this Gtid_set to the trace file if debug is enabled; no-op
|
|
otherwise.
|
|
*/
|
|
void dbug_print(const char *text= "", bool need_lock= false,
|
|
const Gtid_set::String_format *sf= NULL) const
|
|
{
|
|
#ifndef DBUG_OFF
|
|
char *str;
|
|
to_string(&str, need_lock, sf);
|
|
DBUG_PRINT("info", ("%s%s'%s'",
|
|
text,
|
|
*text ? ": " : "",
|
|
str ? str : "out of memory in Gtid_set::dbug_print"));
|
|
my_free(str);
|
|
#endif
|
|
}
|
|
/**
|
|
Gets all gtid intervals from this Gtid_set.
|
|
|
|
@param[out] gtid_intervals Store all gtid intervals from this Gtid_set.
|
|
*/
|
|
void get_gtid_intervals(std::list<Gtid_interval> *gtid_intervals) const;
|
|
/**
|
|
The default String_format: the format understood by
|
|
add_gtid_text(const char *).
|
|
*/
|
|
static const String_format default_string_format;
|
|
/**
|
|
String_format useful to generate an SQL string: the string is
|
|
wrapped in single quotes and there is a newline between SIDs.
|
|
*/
|
|
static const String_format sql_string_format;
|
|
/**
|
|
String_format for printing the Gtid_set commented: the string is
|
|
not quote-wrapped, and every SID is on a new line with a leading '# '.
|
|
*/
|
|
static const String_format commented_string_format;
|
|
|
|
/// Return the Sid_map associated with this Gtid_set.
|
|
Sid_map *get_sid_map() const { return sid_map; }
|
|
|
|
/**
|
|
Represents one element in the linked list of intervals associated
|
|
with a SIDNO.
|
|
*/
|
|
struct Interval
|
|
{
|
|
public:
|
|
/// The first GNO of this interval.
|
|
rpl_gno start;
|
|
/// The first GNO after this interval.
|
|
rpl_gno end;
|
|
/// Return true iff this interval is equal to the given interval.
|
|
bool equals(const Interval &other) const
|
|
{
|
|
return start == other.start && end == other.end;
|
|
}
|
|
/// Pointer to next interval in list.
|
|
Interval *next;
|
|
};
|
|
|
|
/**
|
|
Provides an array of Intervals that this Gtid_set can use when
|
|
groups are subsequently added. This can be used as an
|
|
optimization, to reduce allocation for sets that have a known
|
|
number of intervals.
|
|
|
|
@param n_intervals The number of intervals to add.
|
|
@param intervals Array of n_intervals intervals.
|
|
*/
|
|
void add_interval_memory(int n_intervals, Interval *intervals_param)
|
|
{
|
|
if (sid_lock != NULL)
|
|
mysql_mutex_lock(&free_intervals_mutex);
|
|
add_interval_memory_lock_taken(n_intervals, intervals_param);
|
|
if (sid_lock != NULL)
|
|
mysql_mutex_unlock(&free_intervals_mutex);
|
|
}
|
|
|
|
|
|
/**
|
|
Iterator over intervals for a given SIDNO.
|
|
|
|
This is an abstract template class, used as a common base class
|
|
for Const_interval_iterator and Interval_iterator.
|
|
|
|
The iterator always points to an interval pointer. The interval
|
|
pointer is either the initial pointer into the list, or the next
|
|
pointer of one of the intervals in the list.
|
|
*/
|
|
template<typename Gtid_set_p, typename Interval_p> class Interval_iterator_base
|
|
{
|
|
public:
|
|
/**
|
|
Construct a new iterator over the GNO intervals for a given Gtid_set.
|
|
|
|
@param gtid_set The Gtid_set.
|
|
@param sidno The SIDNO.
|
|
*/
|
|
Interval_iterator_base(Gtid_set_p gtid_set, rpl_sidno sidno)
|
|
{
|
|
DBUG_ASSERT(sidno >= 1 && sidno <= gtid_set->get_max_sidno());
|
|
init(gtid_set, sidno);
|
|
}
|
|
/// Construct a new iterator over the free intervals of a Gtid_set.
|
|
Interval_iterator_base(Gtid_set_p gtid_set)
|
|
{ p= const_cast<Interval_p *>(>id_set->free_intervals); }
|
|
/// Reset this iterator.
|
|
inline void init(Gtid_set_p gtid_set, rpl_sidno sidno)
|
|
{ p= const_cast<Interval_p *>(>id_set->m_intervals[sidno - 1]); }
|
|
/// Advance current_elem one step.
|
|
inline void next()
|
|
{
|
|
DBUG_ASSERT(*p != NULL);
|
|
p= const_cast<Interval_p *>(&(*p)->next);
|
|
}
|
|
/// Return current_elem.
|
|
inline Interval_p get() const { return *p; }
|
|
protected:
|
|
/**
|
|
Holds the address of the 'next' pointer of the previous element,
|
|
or the address of the initial pointer into the list, if the
|
|
current element is the first element.
|
|
*/
|
|
Interval_p *p;
|
|
};
|
|
|
|
/**
|
|
Iterator over intervals of a const Gtid_set.
|
|
*/
|
|
class Const_interval_iterator
|
|
: public Interval_iterator_base<const Gtid_set *, const Interval *>
|
|
{
|
|
public:
|
|
/// Create this Const_interval_iterator.
|
|
Const_interval_iterator(const Gtid_set *gtid_set, rpl_sidno sidno)
|
|
: Interval_iterator_base<const Gtid_set *, const Interval *>(gtid_set, sidno) {}
|
|
/// Create this Const_interval_iterator.
|
|
Const_interval_iterator(const Gtid_set *gtid_set)
|
|
: Interval_iterator_base<const Gtid_set *, const Interval *>(gtid_set) {}
|
|
};
|
|
|
|
/**
|
|
Iterator over intervals of a non-const Gtid_set, with additional
|
|
methods to modify the Gtid_set.
|
|
*/
|
|
class Interval_iterator
|
|
: public Interval_iterator_base<Gtid_set *, Interval *>
|
|
{
|
|
public:
|
|
/// Create this Interval_iterator.
|
|
Interval_iterator(Gtid_set *gtid_set, rpl_sidno sidno)
|
|
: Interval_iterator_base<Gtid_set *, Interval *>(gtid_set, sidno) {}
|
|
/// Destroy this Interval_iterator.
|
|
Interval_iterator(Gtid_set *gtid_set)
|
|
: Interval_iterator_base<Gtid_set *, Interval *>(gtid_set) {}
|
|
private:
|
|
/**
|
|
Set current_elem to the given Interval but do not touch the
|
|
next pointer of the given Interval.
|
|
*/
|
|
inline void set(Interval *iv) { *p= iv; }
|
|
/// Insert the given element before current_elem.
|
|
inline void insert(Interval *iv) { iv->next= *p; set(iv); }
|
|
/// Remove current_elem.
|
|
inline void remove(Gtid_set *gtid_set)
|
|
{
|
|
DBUG_ASSERT(get() != NULL);
|
|
Interval *next= (*p)->next;
|
|
gtid_set->put_free_interval(*p);
|
|
set(next);
|
|
}
|
|
/**
|
|
Only Gtid_set is allowed to use set/insert/remove.
|
|
|
|
They are not safe to use from other code because: (1) very easy
|
|
to make a mistakes (2) they don't clear cached_string_format or
|
|
cached_string_length.
|
|
*/
|
|
friend class Gtid_set;
|
|
};
|
|
|
|
|
|
/**
|
|
Iterator over all groups in a Gtid_set. This is a const
|
|
iterator; it does not allow modification of the Gtid_set.
|
|
*/
|
|
class Gtid_iterator
|
|
{
|
|
public:
|
|
Gtid_iterator(const Gtid_set *gs)
|
|
: gtid_set(gs), sidno(0), ivit(gs)
|
|
{
|
|
if (gs->sid_lock != NULL)
|
|
gs->sid_lock->assert_some_wrlock();
|
|
next_sidno();
|
|
}
|
|
/// Advance to next group.
|
|
inline void next()
|
|
{
|
|
DBUG_ASSERT(gno > 0 && sidno > 0);
|
|
// go to next group in current interval
|
|
gno++;
|
|
// end of interval? then go to next interval for this sidno
|
|
if (gno == ivit.get()->end)
|
|
{
|
|
ivit.next();
|
|
const Interval *iv= ivit.get();
|
|
// last interval for this sidno? then go to next sidno
|
|
if (iv == NULL)
|
|
{
|
|
next_sidno();
|
|
// last sidno? then don't try more
|
|
if (sidno == 0)
|
|
return;
|
|
iv= ivit.get();
|
|
}
|
|
gno= iv->start;
|
|
}
|
|
}
|
|
/// Return next group, or {0,0} if we reached the end.
|
|
inline Gtid get() const
|
|
{
|
|
Gtid ret= { sidno, gno };
|
|
return ret;
|
|
}
|
|
private:
|
|
/// Find the next sidno that has one or more intervals.
|
|
inline void next_sidno()
|
|
{
|
|
const Interval *iv;
|
|
do
|
|
{
|
|
sidno++;
|
|
if (sidno > gtid_set->get_max_sidno())
|
|
{
|
|
sidno= 0;
|
|
gno= 0;
|
|
return;
|
|
}
|
|
ivit.init(gtid_set, sidno);
|
|
iv= ivit.get();
|
|
} while (iv == NULL);
|
|
gno= iv->start;
|
|
}
|
|
/// The Gtid_set we iterate over.
|
|
const Gtid_set *gtid_set;
|
|
/**
|
|
The SIDNO of the current element, or 0 if the iterator is past
|
|
the last element.
|
|
*/
|
|
rpl_sidno sidno;
|
|
/**
|
|
The GNO of the current element, or 0 if the iterator is past the
|
|
last element.
|
|
*/
|
|
rpl_gno gno;
|
|
/// Iterator over the intervals for the current SIDNO.
|
|
Const_interval_iterator ivit;
|
|
};
|
|
|
|
public:
|
|
|
|
/**
|
|
Encodes this Gtid_set as a binary string.
|
|
*/
|
|
void encode(uchar *buf) const;
|
|
/**
|
|
Returns the length of this Gtid_set when encoded using the
|
|
encode() function.
|
|
*/
|
|
size_t get_encoded_length() const;
|
|
|
|
private:
|
|
/**
|
|
Contains a list of intervals allocated by this Gtid_set. When a
|
|
method of this class needs a new interval and there are no more
|
|
free intervals, a new Interval_chunk is allocated and the
|
|
intervals of it are added to the list of free intervals.
|
|
*/
|
|
struct Interval_chunk
|
|
{
|
|
Interval_chunk *next;
|
|
Interval intervals[1];
|
|
};
|
|
/// The default number of intervals in an Interval_chunk.
|
|
static const int CHUNK_GROW_SIZE= 8;
|
|
|
|
/**
|
|
Return true if the given sidno of this Gtid_set contains the same
|
|
intervals as the given sidno of the other Gtid_set.
|
|
|
|
@param sidno SIDNO to check for this Gtid_set.
|
|
@param other Other Gtid_set
|
|
@param other_sidno SIDNO to check in other.
|
|
@return true if equal, false is not equal.
|
|
*/
|
|
bool sidno_equals(rpl_sidno sidno,
|
|
const Gtid_set *other, rpl_sidno other_sidno) const;
|
|
/// Returns true if this Gtid_set is equal to the other Gtid_set.
|
|
bool equals(const Gtid_set *other) const;
|
|
|
|
/// Return the number of intervals for the given sidno.
|
|
int get_n_intervals(rpl_sidno sidno) const
|
|
{
|
|
Const_interval_iterator ivit(this, sidno);
|
|
int ret= 0;
|
|
while (ivit.get() != NULL)
|
|
{
|
|
ret++;
|
|
ivit.next();
|
|
}
|
|
return ret;
|
|
}
|
|
/// Return the number of intervals in this Gtid_set.
|
|
int get_n_intervals() const
|
|
{
|
|
if (sid_lock != NULL)
|
|
sid_lock->assert_some_wrlock();
|
|
rpl_sidno max_sidno= get_max_sidno();
|
|
int ret= 0;
|
|
for (rpl_sidno sidno= 1; sidno < max_sidno; sidno++)
|
|
ret+= get_n_intervals(sidno);
|
|
return ret;
|
|
}
|
|
/**
|
|
Allocates a new chunk of Intervals and adds them to the list of
|
|
unused intervals.
|
|
|
|
@param size The number of intervals in this chunk
|
|
*/
|
|
void create_new_chunk(int size);
|
|
/**
|
|
Returns a fresh new Interval object.
|
|
|
|
This usually does not require any real allocation, it only pops
|
|
the first interval from the list of free intervals. If there are
|
|
no free intervals, it calls create_new_chunk.
|
|
|
|
@param out The resulting Interval* will be stored here.
|
|
*/
|
|
void get_free_interval(Interval **out);
|
|
/**
|
|
Puts the given interval in the list of free intervals. Does not
|
|
unlink it from its place in any other list.
|
|
*/
|
|
void put_free_interval(Interval *iv);
|
|
/**
|
|
Like add_interval_memory, but does not acquire
|
|
free_intervals_mutex.
|
|
@see Gtid_set::add_interval_memory
|
|
*/
|
|
void add_interval_memory_lock_taken(int n_ivs, Interval *ivs);
|
|
|
|
/// Read-write lock that protects updates to the number of SIDs.
|
|
mutable Checkable_rwlock *sid_lock;
|
|
/**
|
|
Lock protecting the list of free intervals. This lock is only
|
|
used if sid_lock is not NULL.
|
|
*/
|
|
mysql_mutex_t free_intervals_mutex;
|
|
/**
|
|
Class representing a lock on free_intervals_mutex.
|
|
|
|
This is used by the add_* and remove_* functions. The lock is
|
|
declared by the top-level function and a pointer to the lock is
|
|
passed down to low-level functions. If the low-level function
|
|
decides to access the free intervals list, then it acquires the
|
|
lock. The lock is then automatically released by the destructor
|
|
when the top-level function returns.
|
|
|
|
The lock is not taken if Gtid_set->sid_lock == NULL; such
|
|
Gtid_sets are assumed to be thread-local.
|
|
*/
|
|
class Free_intervals_lock
|
|
{
|
|
public:
|
|
/// Create a new lock, but do not acquire it.
|
|
Free_intervals_lock(Gtid_set *_gtid_set)
|
|
: gtid_set(_gtid_set), locked(false) {}
|
|
/// Lock the lock if it is not already locked.
|
|
void lock_if_not_locked()
|
|
{
|
|
if (gtid_set->sid_lock && !locked)
|
|
{
|
|
mysql_mutex_lock(>id_set->free_intervals_mutex);
|
|
locked= true;
|
|
}
|
|
}
|
|
/// Lock the lock if it is locked.
|
|
void unlock_if_locked()
|
|
{
|
|
if (gtid_set->sid_lock && locked)
|
|
{
|
|
mysql_mutex_unlock(>id_set->free_intervals_mutex);
|
|
locked= false;
|
|
}
|
|
}
|
|
/// Destroy this object and unlock the lock if it is locked.
|
|
~Free_intervals_lock()
|
|
{
|
|
unlock_if_locked();
|
|
}
|
|
private:
|
|
Gtid_set *gtid_set;
|
|
bool locked;
|
|
};
|
|
void assert_free_intervals_locked()
|
|
{
|
|
if (sid_lock != NULL)
|
|
mysql_mutex_assert_owner(&free_intervals_mutex);
|
|
}
|
|
|
|
/**
|
|
Adds the interval (start, end) to the given Interval_iterator.
|
|
|
|
This is the lowest-level function that adds groups; this is where
|
|
Interval objects are added, grown, or merged.
|
|
|
|
@param ivitp Pointer to iterator. After this function returns,
|
|
the current_element of the iterator will be the interval that
|
|
contains start and end.
|
|
@param start The first GNO in the interval.
|
|
@param end The first GNO after the interval.
|
|
@param lock If this function has to add or remove an interval,
|
|
then this lock will be taken unless it is already taken. This
|
|
mechanism means that the lock will be taken lazily by
|
|
e.g. add_gtid_set() the first time that the list of free intervals
|
|
is accessed, and automatically released when add_gtid_set()
|
|
returns.
|
|
*/
|
|
void add_gno_interval(Interval_iterator *ivitp,
|
|
rpl_gno start, rpl_gno end,
|
|
Free_intervals_lock *lock);
|
|
/**
|
|
Removes the interval (start, end) from the given
|
|
Interval_iterator. This is the lowest-level function that removes
|
|
groups; this is where Interval objects are removed, truncated, or
|
|
split.
|
|
|
|
It is not required that the groups in the interval exist in this
|
|
Gtid_set.
|
|
|
|
@param ivitp Pointer to iterator. After this function returns,
|
|
the current_element of the iterator will be the next interval
|
|
after end.
|
|
@param start The first GNO in the interval.
|
|
@param end The first GNO after the interval.
|
|
@param lock If this function has to add or remove an interval,
|
|
then this lock will be taken unless it is already taken. This
|
|
mechanism means that the lock will be taken lazily by
|
|
e.g. add_gtid_set() the first time that the list of free intervals
|
|
is accessed, and automatically released when add_gtid_set()
|
|
returns.
|
|
*/
|
|
void remove_gno_interval(Interval_iterator *ivitp,
|
|
rpl_gno start, rpl_gno end,
|
|
Free_intervals_lock *lock);
|
|
/**
|
|
Adds a list of intervals to the given SIDNO.
|
|
|
|
The SIDNO must exist in the Gtid_set before this function is called.
|
|
|
|
@param sidno The SIDNO to which intervals will be added.
|
|
@param ivit Iterator over the intervals to add. This is typically
|
|
an iterator over some other Gtid_set.
|
|
@param lock If this function has to add or remove an interval,
|
|
then this lock will be taken unless it is already taken. This
|
|
mechanism means that the lock will be taken lazily by
|
|
e.g. add_gtid_set() the first time that the list of free intervals
|
|
is accessed, and automatically released when add_gtid_set()
|
|
returns.
|
|
*/
|
|
void add_gno_intervals(rpl_sidno sidno,
|
|
Const_interval_iterator ivit,
|
|
Free_intervals_lock *lock);
|
|
/**
|
|
Removes a list of intervals from the given SIDNO.
|
|
|
|
It is not required that the intervals exist in this Gtid_set.
|
|
|
|
@param sidno The SIDNO from which intervals will be removed.
|
|
@param ivit Iterator over the intervals to remove. This is typically
|
|
an iterator over some other Gtid_set.
|
|
@param lock If this function has to add or remove an interval,
|
|
then this lock will be taken unless it is already taken. This
|
|
mechanism means that the lock will be taken lazily by
|
|
e.g. add_gtid_set() the first time that the list of free intervals
|
|
is accessed, and automatically released when add_gtid_set()
|
|
returns.
|
|
*/
|
|
void remove_gno_intervals(rpl_sidno sidno,
|
|
Const_interval_iterator ivit,
|
|
Free_intervals_lock *lock);
|
|
|
|
/// Returns true if every interval of sub is a subset of some
|
|
/// interval of super.
|
|
static bool is_interval_subset(Const_interval_iterator *sub,
|
|
Const_interval_iterator *super);
|
|
/// Returns true if at least one sidno in ivit1 is also in ivit2.
|
|
static bool is_interval_intersection_nonempty(Const_interval_iterator *ivit1,
|
|
Const_interval_iterator *ivit2);
|
|
|
|
/// Sid_map associated with this Gtid_set.
|
|
Sid_map *sid_map;
|
|
/**
|
|
Array where the N'th element contains the head pointer to the
|
|
intervals of SIDNO N+1.
|
|
*/
|
|
Prealloced_array<Interval*, 8, true> m_intervals;
|
|
/// Linked list of free intervals.
|
|
Interval *free_intervals;
|
|
/// Linked list of chunks.
|
|
Interval_chunk *chunks;
|
|
/// If the string is cached.
|
|
mutable bool has_cached_string_length;
|
|
/// The string length.
|
|
mutable size_t cached_string_length;
|
|
/// The String_format that was used when cached_string_length was computed.
|
|
mutable const String_format *cached_string_format;
|
|
#ifndef DBUG_OFF
|
|
/**
|
|
The number of chunks. Used only to check some invariants when
|
|
DBUG is on.
|
|
*/
|
|
int n_chunks;
|
|
#endif
|
|
|
|
/// Used by unit tests that need to access private members.
|
|
#ifdef FRIEND_OF_GTID_SET
|
|
friend FRIEND_OF_GTID_SET;
|
|
#endif
|
|
/// Only Free_intervals_lock is allowed to access free_intervals_mutex.
|
|
friend class Gtid_set::Free_intervals_lock;
|
|
};
|
|
|
|
|
|
/**
|
|
Holds information about a Gtid_set. Can also be NULL.
|
|
|
|
This is used as backend storage for @@session.gtid_next_list. The
|
|
idea is that we allow the user to set this to NULL, but we keep the
|
|
Gtid_set object so that we can re-use the allocated memory and
|
|
avoid costly allocations later.
|
|
|
|
This is stored in struct system_variables (defined in sql_class.h),
|
|
which is cleared using memset(0); hence the negated form of
|
|
is_non_null.
|
|
|
|
The convention is: if is_non_null is false, then the value of the
|
|
session variable is NULL, and the field gtid_set may be NULL or
|
|
non-NULL. If is_non_null is true, then the value of the session
|
|
variable is not NULL, and the field gtid_set has to be non-NULL.
|
|
|
|
This is a POD. It has to be a POD because it is stored in
|
|
THD::variables.
|
|
*/
|
|
struct Gtid_set_or_null
|
|
{
|
|
/// Pointer to the Gtid_set.
|
|
Gtid_set *gtid_set;
|
|
/// True if this Gtid_set is NULL.
|
|
bool is_non_null;
|
|
/// Return NULL if this is NULL, otherwise return the Gtid_set.
|
|
inline Gtid_set *get_gtid_set() const
|
|
{
|
|
DBUG_ASSERT(!(is_non_null && gtid_set == NULL));
|
|
return is_non_null ? gtid_set : NULL;
|
|
}
|
|
/**
|
|
Do nothing if this object is non-null; set to empty set otherwise.
|
|
|
|
@return NULL if out of memory; Gtid_set otherwise.
|
|
*/
|
|
Gtid_set *set_non_null(Sid_map *sm)
|
|
{
|
|
if (!is_non_null)
|
|
{
|
|
if (gtid_set == NULL)
|
|
gtid_set= new Gtid_set(sm);
|
|
else
|
|
gtid_set->clear();
|
|
}
|
|
is_non_null= (gtid_set != NULL);
|
|
return gtid_set;
|
|
}
|
|
/// Set this Gtid_set to NULL.
|
|
inline void set_null() { is_non_null= false; }
|
|
};
|
|
|
|
|
|
/**
|
|
Represents the set of GTIDs that are owned by some thread.
|
|
|
|
This data structure has a read-write lock that protects the number
|
|
of SIDNOs. The lock is provided by the invoker of the constructor
|
|
and it is generally the caller's responsibility to acquire the read
|
|
lock. Access methods assert that the caller already holds the read
|
|
(or write) lock. If a method of this class grows the number of
|
|
SIDNOs, then the method temporarily upgrades this lock to a write
|
|
lock and then degrades it to a read lock again; there will be a
|
|
short period when the lock is not held at all.
|
|
|
|
The internal representation is a multi-valued map from GTIDs to
|
|
threads, mapping GTIDs to one or more threads that owns it.
|
|
|
|
In Group Replication multiple threads can own a GTID whereas if GR
|
|
is disabeld there is at most one owner per GTID.
|
|
*/
|
|
class Owned_gtids
|
|
{
|
|
public:
|
|
/**
|
|
Constructs a new, empty Owned_gtids object.
|
|
|
|
@param sid_lock Read-write lock that protects updates to the
|
|
number of SIDs.
|
|
*/
|
|
Owned_gtids(Checkable_rwlock *sid_lock);
|
|
/// Destroys this Owned_gtids.
|
|
~Owned_gtids();
|
|
/**
|
|
Add a GTID to this Owned_gtids.
|
|
|
|
@param gtid The Gtid to add.
|
|
@param owner The my_thread_id of the group to add.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status add_gtid_owner(const Gtid >id, my_thread_id owner);
|
|
|
|
/*
|
|
Fill all gtids into the given Gtid_set object. It doesn't clear the given
|
|
gtid set before filling its owned gtids into it.
|
|
*/
|
|
void get_gtids(Gtid_set >id_set) const;
|
|
/**
|
|
Removes the given GTID.
|
|
|
|
If the group does not exist in this Owned_gtids object, does
|
|
nothing.
|
|
|
|
@param gtid The Gtid.
|
|
@param owner thread_id of the owner thread
|
|
*/
|
|
void remove_gtid(const Gtid >id, const my_thread_id owner);
|
|
/**
|
|
Ensures that this Owned_gtids object can accomodate SIDNOs up to
|
|
the given SIDNO.
|
|
|
|
If this Owned_gtids object needs to be resized, then the lock
|
|
will be temporarily upgraded to a write lock and then degraded to
|
|
a read lock again; there will be a short period when the lock is
|
|
not held at all.
|
|
|
|
@param sidno The SIDNO.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status ensure_sidno(rpl_sidno sidno);
|
|
/// Returns true if there is a least one element of this Owned_gtids
|
|
/// set in the other Gtid_set.
|
|
bool is_intersection_nonempty(const Gtid_set *other) const;
|
|
/// Returns true if this Owned_gtids is empty.
|
|
bool is_empty() const
|
|
{
|
|
Gtid_iterator git(this);
|
|
return git.get().sidno == 0;
|
|
}
|
|
/// Returns the maximal sidno that this Owned_gtids currently has space for.
|
|
rpl_sidno get_max_sidno() const
|
|
{
|
|
sid_lock->assert_some_lock();
|
|
return static_cast<rpl_sidno>(sidno_to_hash.size());
|
|
}
|
|
|
|
/**
|
|
Write a string representation of this Owned_groups to the given buffer.
|
|
|
|
@param out Buffer to write to.
|
|
@return Number of characters written.
|
|
*/
|
|
int to_string(char *out) const
|
|
{
|
|
char *p= out;
|
|
rpl_sidno max_sidno= get_max_sidno();
|
|
rpl_sidno sid_map_max_sidno= global_sid_map->get_max_sidno();
|
|
for (rpl_sidno sid_i= 0; sid_i < sid_map_max_sidno; sid_i++)
|
|
{
|
|
rpl_sidno sidno= global_sid_map->get_sorted_sidno(sid_i);
|
|
if (sidno > max_sidno)
|
|
continue;
|
|
HASH *hash= get_hash(sidno);
|
|
bool printed_sid= false;
|
|
for (uint i= 0; i < hash->records; i++)
|
|
{
|
|
Node *node= (Node *)my_hash_element(hash, i);
|
|
DBUG_ASSERT(node != NULL);
|
|
if (!printed_sid)
|
|
{
|
|
p+= global_sid_map->sidno_to_sid(sidno).to_string(p);
|
|
printed_sid= true;
|
|
}
|
|
p+= sprintf(p, ":%lld#%u", node->gno, node->owner);
|
|
}
|
|
}
|
|
*p= 0;
|
|
return (int)(p - out);
|
|
}
|
|
|
|
/**
|
|
Return an upper bound on the length of the string representation
|
|
of this Owned_groups. The actual length may be smaller. This
|
|
includes the trailing '\0'.
|
|
*/
|
|
size_t get_max_string_length() const
|
|
{
|
|
rpl_sidno max_sidno= get_max_sidno();
|
|
size_t ret= 0;
|
|
for (rpl_sidno sidno= 1; sidno <= max_sidno; sidno++)
|
|
{
|
|
HASH *hash= get_hash(sidno);
|
|
if (hash->records > 0)
|
|
ret+= binary_log::Uuid::TEXT_LENGTH +
|
|
hash->records * (1 + MAX_GNO_TEXT_LENGTH +
|
|
1 + MAX_THREAD_ID_TEXT_LENGTH);
|
|
}
|
|
return 1 + ret;
|
|
}
|
|
|
|
/**
|
|
Return true if the given thread is the owner of any groups.
|
|
*/
|
|
bool thread_owns_anything(my_thread_id thd_id) const
|
|
{
|
|
Gtid_iterator git(this);
|
|
Node *node= git.get_node();
|
|
while (node != NULL)
|
|
{
|
|
if (node->owner == thd_id)
|
|
return true;
|
|
git.next();
|
|
node= git.get_node();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifndef DBUG_OFF
|
|
/**
|
|
Debug only: return a newly allocated string representation of
|
|
this Owned_gtids.
|
|
*/
|
|
char *to_string() const
|
|
{
|
|
char *str= (char *)my_malloc(key_memory_Owned_gtids_to_string,
|
|
get_max_string_length(), MYF(MY_WME));
|
|
DBUG_ASSERT(str != NULL);
|
|
to_string(str);
|
|
return str;
|
|
}
|
|
/// Debug only: print this Owned_gtids to stdout.
|
|
void print() const
|
|
{
|
|
char *str= to_string();
|
|
printf("%s\n", str);
|
|
my_free(str);
|
|
}
|
|
#endif
|
|
/**
|
|
Print this Owned_gtids to the trace file if debug is enabled; no-op
|
|
otherwise.
|
|
*/
|
|
void dbug_print(const char *text= "") const
|
|
{
|
|
#ifndef DBUG_OFF
|
|
char *str= to_string();
|
|
DBUG_PRINT("info", ("%s%s%s", text, *text ? ": " : "", str));
|
|
my_free(str);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
If thd_id==0, returns true when gtid is not owned by any thread.
|
|
If thd_id!=0, returns true when gtid is owned by that thread.
|
|
*/
|
|
bool is_owned_by(const Gtid >id, const my_thread_id thd_id) const;
|
|
|
|
private:
|
|
/// Represents one owned group.
|
|
struct Node
|
|
{
|
|
/// GNO of the group.
|
|
rpl_gno gno;
|
|
/// Owner of the group.
|
|
my_thread_id owner;
|
|
};
|
|
/// Read-write lock that protects updates to the number of SIDs.
|
|
mutable Checkable_rwlock *sid_lock;
|
|
/// Returns the HASH for the given SIDNO.
|
|
HASH *get_hash(rpl_sidno sidno) const
|
|
{
|
|
DBUG_ASSERT(sidno >= 1 && sidno <= get_max_sidno());
|
|
sid_lock->assert_some_lock();
|
|
return sidno_to_hash[sidno - 1];
|
|
}
|
|
/// Return true iff this Owned_gtids object contains the given group.
|
|
bool contains_gtid(const Gtid >id) const;
|
|
|
|
/// Growable array of hashes.
|
|
Prealloced_array<HASH*, 8, true> sidno_to_hash;
|
|
|
|
public:
|
|
/**
|
|
Iterator over all groups in a Owned_gtids set. This is a const
|
|
iterator; it does not allow modification of the set.
|
|
*/
|
|
class Gtid_iterator
|
|
{
|
|
public:
|
|
Gtid_iterator(const Owned_gtids* og)
|
|
: owned_gtids(og), sidno(1), hash(NULL), node_index(0), node(NULL)
|
|
{
|
|
max_sidno= owned_gtids->get_max_sidno();
|
|
if (sidno <= max_sidno)
|
|
hash= owned_gtids->get_hash(sidno);
|
|
next();
|
|
}
|
|
/// Advance to next group.
|
|
inline void next()
|
|
{
|
|
#ifndef DBUG_OFF
|
|
if (owned_gtids->sid_lock)
|
|
owned_gtids->sid_lock->assert_some_wrlock();
|
|
#endif
|
|
|
|
while (sidno <= max_sidno)
|
|
{
|
|
DBUG_ASSERT(hash != NULL);
|
|
if (node_index < hash->records)
|
|
{
|
|
node= (Node *)my_hash_element(hash, node_index);
|
|
DBUG_ASSERT(node != NULL);
|
|
// Jump to next node on next iteration.
|
|
node_index++;
|
|
return;
|
|
}
|
|
|
|
node_index= 0;
|
|
// hash is initialized on constructor or in previous iteration
|
|
// for current SIDNO, so we must increment for next iteration.
|
|
sidno++;
|
|
if (sidno <= max_sidno)
|
|
hash= owned_gtids->get_hash(sidno);
|
|
}
|
|
node= NULL;
|
|
}
|
|
/// Return next group, or {0,0} if we reached the end.
|
|
inline Gtid get() const
|
|
{
|
|
Gtid ret= { 0, 0 };
|
|
if (node)
|
|
{
|
|
ret.sidno= sidno;
|
|
ret.gno= node->gno;
|
|
}
|
|
return ret;
|
|
}
|
|
/// Return next group Node, or NULL if we reached the end.
|
|
inline Node* get_node() const
|
|
{
|
|
return node;
|
|
}
|
|
private:
|
|
/// The Owned_gtids set we iterate over.
|
|
const Owned_gtids *owned_gtids;
|
|
/// The SIDNO of the current element, or 1 in the initial iteration.
|
|
rpl_sidno sidno;
|
|
/// Max SIDNO of the current iterator.
|
|
rpl_sidno max_sidno;
|
|
/// Current SIDNO hash.
|
|
HASH *hash;
|
|
/// Current node index on current SIDNO hash.
|
|
uint node_index;
|
|
/// Current node on current SIDNO hash.
|
|
Node *node;
|
|
};
|
|
};
|
|
|
|
|
|
/**
|
|
Represents the state of the group log: the set of logged groups, the
|
|
set of lost groups, the set of owned groups, the owner of each owned
|
|
group, and a Mutex_cond_array that protects updates to groups of
|
|
each SIDNO.
|
|
|
|
Locking:
|
|
|
|
This data structure has a read-write lock that protects the number
|
|
of SIDNOs, and a Mutex_cond_array that contains one mutex per SIDNO.
|
|
The rwlock is always the global_sid_lock.
|
|
|
|
Access methods generally assert that the caller already holds the
|
|
appropriate lock:
|
|
|
|
- before accessing any global data, hold at least the rdlock.
|
|
|
|
- before accessing a specific SIDNO in a Gtid_set or Owned_gtids
|
|
(e.g., calling Gtid_set::_add_gtid(Gtid)), hold either the rdlock
|
|
and the SIDNO's mutex lock; or the wrlock. If you need to hold
|
|
multiple mutexes, they must be acquired in order of increasing
|
|
SIDNO.
|
|
|
|
- before starting an operation that needs to access all SIDs
|
|
(e.g. Gtid_set::to_string()), hold the wrlock.
|
|
|
|
The access type (read/write) does not matter; the write lock only
|
|
implies that the entire data structure is locked whereas the read
|
|
lock implies that everything except SID-specific data is locked.
|
|
*/
|
|
class Gtid_state
|
|
{
|
|
public:
|
|
/**
|
|
Constructs a new Gtid_state object.
|
|
|
|
@param _sid_lock Read-write lock that protects updates to the
|
|
number of SIDs.
|
|
@param _sid_map Sid_map used by this group log.
|
|
*/
|
|
Gtid_state(Checkable_rwlock *_sid_lock, Sid_map *_sid_map)
|
|
: sid_lock(_sid_lock),
|
|
sid_map(_sid_map),
|
|
sid_locks(sid_lock),
|
|
lost_gtids(sid_map, sid_lock),
|
|
executed_gtids(sid_map, sid_lock),
|
|
gtids_only_in_table(sid_map, sid_lock),
|
|
previous_gtids_logged(sid_map, sid_lock),
|
|
owned_gtids(sid_lock),
|
|
commit_group_sidnos(key_memory_Gtid_state_group_commit_sidno) {}
|
|
/**
|
|
Add @@GLOBAL.SERVER_UUID to this binlog's Sid_map.
|
|
|
|
This can't be done in the constructor because the constructor is
|
|
invoked at server startup before SERVER_UUID is initialized.
|
|
|
|
The caller must hold the read lock or write lock on sid_locks
|
|
before invoking this function.
|
|
|
|
@retval 0 Success
|
|
@retval 1 Error (out of memory or IO error).
|
|
*/
|
|
int init();
|
|
/**
|
|
Reset the state and persistor after RESET MASTER: remove all logged
|
|
and lost groups, but keep owned groups as they are.
|
|
|
|
The caller must hold the write lock on sid_lock before calling
|
|
this function.
|
|
|
|
@param thd Thread requesting to reset the persistor
|
|
|
|
@retval 0 Success
|
|
@retval -1 Error
|
|
*/
|
|
int clear(THD *thd);
|
|
/**
|
|
Returns true if the given GTID is logged.
|
|
|
|
@param gtid The Gtid to check.
|
|
|
|
@retval true The group is logged in the binary log.
|
|
@retval false The group is not logged in the binary log.
|
|
*/
|
|
bool is_executed(const Gtid >id) const
|
|
{
|
|
DBUG_ENTER("Gtid_state::is_executed");
|
|
bool ret= executed_gtids.contains_gtid(gtid);
|
|
DBUG_RETURN(ret);
|
|
}
|
|
/**
|
|
Returns true if GTID is owned, otherwise returns 0.
|
|
|
|
@param gtid The Gtid to check.
|
|
@return true if some thread owns the group, false if the group is
|
|
not owned
|
|
*/
|
|
bool is_owned(const Gtid >id) const
|
|
{ return !owned_gtids.is_owned_by(gtid, 0); }
|
|
#ifndef MYSQL_CLIENT
|
|
/**
|
|
Acquires ownership of the given GTID, on behalf of the given thread.
|
|
|
|
The caller must lock the SIDNO before invoking this function.
|
|
|
|
@param thd The thread that will own the GTID.
|
|
@param gtid The Gtid to acquire ownership of.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status acquire_ownership(THD *thd, const Gtid >id);
|
|
/**
|
|
This function updates both the THD and the Gtid_state to reflect that
|
|
the transaction set of transactions has ended, and it does this for the
|
|
whole commit group (by following the thd->next_to_commit pointer).
|
|
|
|
It will:
|
|
|
|
- Clean up the thread state when a thread owned GTIDs is empty.
|
|
- Release ownership of all GTIDs owned by the THDs. This removes
|
|
the GTIDs from Owned_gtids and clears the ownership status in the
|
|
THDs object.
|
|
- Add the owned GTIDs to executed_gtids when the thread is committing.
|
|
- Decrease counters of GTID-violating transactions.
|
|
- Send a broadcast on the condition variable for every sidno for
|
|
which we released ownership.
|
|
|
|
@param first_thd The first thread of the group commit that needs GTIDs to
|
|
be updated.
|
|
*/
|
|
void update_commit_group(THD *first_thd);
|
|
/**
|
|
Remove the GTID owned by thread from owned GTIDs, stating that
|
|
thd->owned_gtid was committed.
|
|
|
|
This will:
|
|
- remove owned GTID from owned_gtids;
|
|
- remove all owned GTIDS from thd->owned_gtid and thd->owned_gtid_set;
|
|
|
|
@param thd Thread for which owned groups are updated.
|
|
*/
|
|
void update_on_commit(THD *thd);
|
|
/**
|
|
Update the state after the given thread has rollbacked.
|
|
|
|
This will:
|
|
- release ownership of all GTIDs owned by the THD;
|
|
- remove owned GTID from owned_gtids;
|
|
- remove all owned GTIDS from thd->owned_gtid and thd->owned_gtid_set;
|
|
- send a broadcast on the condition variable for every sidno for
|
|
which we released ownership.
|
|
|
|
@param thd Thread for which owned groups are updated.
|
|
*/
|
|
void update_on_rollback(THD *thd);
|
|
|
|
/**
|
|
Acquire anonymous ownership.
|
|
|
|
The caller must hold either sid_lock.rdlock or
|
|
sid_lock.wrlock. (The caller must have taken the lock and checked
|
|
that gtid_mode!=ON before calling this function, or else the
|
|
gtid_mode could have changed to ON by a concurrent SET GTID_MODE.)
|
|
*/
|
|
void acquire_anonymous_ownership()
|
|
{
|
|
DBUG_ENTER("Gtid_state::acquire_anonymous_ownership");
|
|
sid_lock->assert_some_lock();
|
|
DBUG_ASSERT(get_gtid_mode(GTID_MODE_LOCK_SID) != GTID_MODE_ON);
|
|
#ifndef DBUG_OFF
|
|
int32 old_value=
|
|
#endif
|
|
anonymous_gtid_count.atomic_add(1);
|
|
DBUG_PRINT("info", ("anonymous_gtid_count increased to %d", old_value + 1));
|
|
DBUG_ASSERT(old_value >= 0);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/// Release anonymous ownership.
|
|
void release_anonymous_ownership()
|
|
{
|
|
DBUG_ENTER("Gtid_state::release_anonymous_ownership");
|
|
sid_lock->assert_some_lock();
|
|
DBUG_ASSERT(get_gtid_mode(GTID_MODE_LOCK_SID) != GTID_MODE_ON);
|
|
#ifndef DBUG_OFF
|
|
int32 old_value=
|
|
#endif
|
|
anonymous_gtid_count.atomic_add(-1);
|
|
DBUG_PRINT("info", ("anonymous_gtid_count decreased to %d", old_value - 1));
|
|
DBUG_ASSERT(old_value >= 1);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/// Return the number of clients that hold anonymous ownership.
|
|
int32 get_anonymous_ownership_count()
|
|
{
|
|
return anonymous_gtid_count.atomic_get();
|
|
}
|
|
|
|
/**
|
|
Increase the global counter when starting a GTID-violating
|
|
transaction having GTID_NEXT=AUTOMATIC.
|
|
*/
|
|
void begin_automatic_gtid_violating_transaction()
|
|
{
|
|
DBUG_ENTER("Gtid_state::begin_automatic_gtid_violating_transaction");
|
|
DBUG_ASSERT(get_gtid_mode(GTID_MODE_LOCK_SID) <= GTID_MODE_OFF_PERMISSIVE);
|
|
DBUG_ASSERT(get_gtid_consistency_mode() != GTID_CONSISTENCY_MODE_ON);
|
|
#ifndef DBUG_OFF
|
|
int32 old_value=
|
|
#endif
|
|
automatic_gtid_violation_count.atomic_add(1);
|
|
DBUG_PRINT("info", ("ongoing_automatic_gtid_violating_transaction_count increased to %d", old_value + 1));
|
|
DBUG_ASSERT(old_value >= 0);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/**
|
|
Decrease the global counter when ending a GTID-violating
|
|
transaction having GTID_NEXT=AUTOMATIC.
|
|
*/
|
|
void end_automatic_gtid_violating_transaction()
|
|
{
|
|
DBUG_ENTER("Gtid_state::end_automatic_gtid_violating_transaction");
|
|
#ifndef DBUG_OFF
|
|
global_sid_lock->rdlock();
|
|
DBUG_ASSERT(get_gtid_mode(GTID_MODE_LOCK_SID) <= GTID_MODE_OFF_PERMISSIVE);
|
|
DBUG_ASSERT(get_gtid_consistency_mode() != GTID_CONSISTENCY_MODE_ON);
|
|
global_sid_lock->unlock();
|
|
int32 old_value=
|
|
#endif
|
|
automatic_gtid_violation_count.atomic_add(-1);
|
|
DBUG_PRINT("info", ("ongoing_automatic_gtid_violating_transaction_count decreased to %d", old_value - 1));
|
|
DBUG_ASSERT(old_value >= 1);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/**
|
|
Return the number of ongoing GTID-violating transactions having
|
|
GTID_NEXT=AUTOMATIC.
|
|
*/
|
|
int32 get_automatic_gtid_violating_transaction_count()
|
|
{
|
|
return automatic_gtid_violation_count.atomic_get();
|
|
}
|
|
|
|
/**
|
|
Increase the global counter when starting a GTID-violating
|
|
transaction having GTID_NEXT=ANONYMOUS.
|
|
*/
|
|
void begin_anonymous_gtid_violating_transaction()
|
|
{
|
|
DBUG_ENTER("Gtid_state::begin_anonymous_gtid_violating_transaction");
|
|
DBUG_ASSERT(get_gtid_mode(GTID_MODE_LOCK_SID) != GTID_MODE_ON);
|
|
DBUG_ASSERT(get_gtid_consistency_mode() != GTID_CONSISTENCY_MODE_ON);
|
|
#ifndef DBUG_OFF
|
|
int32 old_value=
|
|
#endif
|
|
anonymous_gtid_violation_count.atomic_add(1);
|
|
DBUG_PRINT("info", ("ongoing_anonymous_gtid_violating_transaction_count increased to %d", old_value + 1));
|
|
DBUG_ASSERT(old_value >= 0);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/**
|
|
Decrease the global counter when ending a GTID-violating
|
|
transaction having GTID_NEXT=ANONYMOUS.
|
|
*/
|
|
void end_anonymous_gtid_violating_transaction()
|
|
{
|
|
DBUG_ENTER("Gtid_state::end_anonymous_gtid_violating_transaction");
|
|
#ifndef DBUG_OFF
|
|
global_sid_lock->rdlock();
|
|
DBUG_ASSERT(get_gtid_mode(GTID_MODE_LOCK_SID) != GTID_MODE_ON);
|
|
DBUG_ASSERT(get_gtid_consistency_mode() != GTID_CONSISTENCY_MODE_ON);
|
|
global_sid_lock->unlock();
|
|
int32 old_value=
|
|
#endif
|
|
anonymous_gtid_violation_count.atomic_add(-1);
|
|
DBUG_PRINT("info", ("ongoing_anonymous_gtid_violating_transaction_count decreased to %d", old_value - 1));
|
|
DBUG_ASSERT(old_value >= 1);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
void end_gtid_violating_transaction(THD *thd);
|
|
|
|
/**
|
|
Return the number of ongoing GTID-violating transactions having
|
|
GTID_NEXT=AUTOMATIC.
|
|
*/
|
|
int32 get_anonymous_gtid_violating_transaction_count()
|
|
{
|
|
return anonymous_gtid_violation_count.atomic_get();
|
|
}
|
|
|
|
/**
|
|
Increase the global counter when starting a call to
|
|
WAIT_FOR_EXECUTED_GTID_SET or WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS.
|
|
*/
|
|
void begin_gtid_wait(enum_gtid_mode_lock gtid_mode_lock)
|
|
{
|
|
DBUG_ENTER("Gtid_state::begin_gtid_wait");
|
|
DBUG_ASSERT(get_gtid_mode(gtid_mode_lock) != GTID_MODE_OFF);
|
|
#ifndef DBUG_OFF
|
|
int32 old_value=
|
|
#endif
|
|
gtid_wait_count.atomic_add(1);
|
|
DBUG_PRINT("info",
|
|
("gtid_wait_count changed from %d to %d",
|
|
old_value, old_value + 1));
|
|
DBUG_ASSERT(old_value >= 0);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/**
|
|
Decrease the global counter when ending a call to
|
|
WAIT_FOR_EXECUTED_GTID_SET or WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS.
|
|
*/
|
|
void end_gtid_wait()
|
|
{
|
|
DBUG_ENTER("Gtid_state::end_gtid_wait");
|
|
DBUG_ASSERT(get_gtid_mode(GTID_MODE_LOCK_NONE) != GTID_MODE_OFF);
|
|
#ifndef DBUG_OFF
|
|
int32 old_value=
|
|
#endif
|
|
gtid_wait_count.atomic_add(-1);
|
|
DBUG_PRINT("info",
|
|
("gtid_wait_count changed from %d to %d",
|
|
old_value, old_value - 1));
|
|
DBUG_ASSERT(old_value >= 1);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/**
|
|
Return the number of clients that have an ongoing call to
|
|
WAIT_FOR_EXECUTED_GTID_SET or WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS.
|
|
*/
|
|
int32 get_gtid_wait_count()
|
|
{
|
|
return gtid_wait_count.atomic_get();
|
|
}
|
|
|
|
#endif // ifndef MYSQL_CLIENT
|
|
private:
|
|
/**
|
|
Computes the next available GNO.
|
|
|
|
@param sidno The GTID's SIDNO.
|
|
|
|
@retval -1 The range of GNOs was exhausted (i.e., more than 1<<63-1
|
|
GTIDs with the same UUID have been generated).
|
|
@retval >0 The GNO for the GTID.
|
|
*/
|
|
rpl_gno get_automatic_gno(rpl_sidno sidno) const;
|
|
/**
|
|
The next_free_gno variable will be set with the supposed next free GNO
|
|
every time a new GNO is delivered automatically or when a transaction is
|
|
rolled back, releasing a GNO smaller than the last one delivered.
|
|
It was introduced in an optimization of Gtid_state::get_automatic_gno and
|
|
Gtid_state::generate_automatic_gtid functions.
|
|
|
|
Locking scheme
|
|
|
|
This variable can be read and modified in four places:
|
|
- During server startup, holding global_sid_lock.wrlock;
|
|
- By a client thread holding global_sid_lock.wrlock (doing a RESET MASTER);
|
|
- By a client thread calling MYSQL_BIN_LOG::write_gtid function (often the
|
|
group commit FLUSH stage leader). It will call
|
|
Gtid_state::generate_automatic_gtid, that will acquire
|
|
global_sid_lock.rdlock and lock_sidno(get_server_sidno()) when getting a
|
|
new automatically generated GTID;
|
|
- By a client thread rolling back, holding global_sid_lock.rdlock
|
|
and lock_sidno(get_server_sidno()).
|
|
*/
|
|
rpl_gno next_free_gno;
|
|
public:
|
|
/**
|
|
Return the last executed GNO for a given SIDNO, e.g.
|
|
for the following set: UUID:1-10, UUID:12, UUID:15-20
|
|
20 will be returned.
|
|
|
|
@param sidno The group's SIDNO.
|
|
|
|
@retval The GNO or 0 if set is empty.
|
|
*/
|
|
rpl_gno get_last_executed_gno(rpl_sidno sidno) const;
|
|
/**
|
|
Generates the GTID (or ANONYMOUS, if GTID_MODE = OFF or
|
|
OFF_PERMISSIVE) for the THD, and acquires ownership.
|
|
|
|
@param THD The thread.
|
|
@param specified_sidno Externaly generated sidno.
|
|
@param specified_gno Externaly generated gno.
|
|
@param[in,out] locked_sidno This parameter should be used when there is
|
|
a need of generating many GTIDs without having
|
|
to acquire/release a sidno_lock many times.
|
|
The caller must hold global_sid_lock and unlock
|
|
the locked_sidno after invocation when
|
|
locked_sidno > 0 if locked_sidno!=NULL.
|
|
The caller must not hold global_sid_lock when
|
|
locked_sidno==NULL.
|
|
See comments on function code to more details.
|
|
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_ERROR. Error can happen
|
|
in case of out of memory or if the range of GNOs was exhausted.
|
|
*/
|
|
enum_return_status generate_automatic_gtid(THD *thd,
|
|
rpl_sidno specified_sidno= 0,
|
|
rpl_gno specified_gno= 0,
|
|
rpl_sidno *locked_sidno= NULL);
|
|
|
|
/// Locks a mutex for the given SIDNO.
|
|
void lock_sidno(rpl_sidno sidno) { sid_locks.lock(sidno); }
|
|
/// Unlocks a mutex for the given SIDNO.
|
|
void unlock_sidno(rpl_sidno sidno) { sid_locks.unlock(sidno); }
|
|
/// Broadcasts updates for the given SIDNO.
|
|
void broadcast_sidno(rpl_sidno sidno) { sid_locks.broadcast(sidno); }
|
|
/// Assert that we own the given SIDNO.
|
|
void assert_sidno_lock_owner(rpl_sidno sidno)
|
|
{ sid_locks.assert_owner(sidno); }
|
|
#ifndef MYSQL_CLIENT
|
|
/**
|
|
Wait for a signal on the given SIDNO.
|
|
|
|
NOTE: This releases a lock!
|
|
|
|
This requires that the caller holds a read lock on sid_lock. It
|
|
will release the lock before waiting; neither global_sid_lock nor
|
|
the mutex lock on SIDNO will not be held when this function
|
|
returns.
|
|
|
|
@param thd THD object of the caller.
|
|
@param sidno Sidno to wait for.
|
|
@param[in] abstime The absolute point in time when the wait times
|
|
out and stops, or NULL to wait indefinitely.
|
|
|
|
@retval false Success.
|
|
@retval true Failure: either timeout or thread was killed. If
|
|
thread was killed, the error has been generated.
|
|
*/
|
|
bool wait_for_sidno(THD *thd, rpl_sidno sidno, struct timespec *abstime);
|
|
/**
|
|
This is only a shorthand for wait_for_sidno, which contains
|
|
additional debug printouts and assertions for the case when the
|
|
caller waits for one specific GTID.
|
|
*/
|
|
bool wait_for_gtid(THD *thd, const Gtid >id, struct timespec* abstime= NULL);
|
|
/**
|
|
Wait until the given Gtid_set is included in @@GLOBAL.GTID_EXECUTED.
|
|
|
|
@param thd The calling thread.
|
|
@param gtid_set Gtid_set to wait for.
|
|
@param[in] timeout The maximum number of milliseconds that the
|
|
function should wait, or 0 to wait indefinitely.
|
|
|
|
@retval false Success.
|
|
@retval true Failure: either timeout or thread was killed. If
|
|
thread was killed, the error has been generated.
|
|
*/
|
|
bool wait_for_gtid_set(THD *thd, Gtid_set *gtid_set, double timeout);
|
|
#endif // ifndef MYSQL_CLIENT
|
|
/**
|
|
Locks one mutex for each SIDNO where the given Gtid_set has at
|
|
least one GTID. Locks are acquired in order of increasing SIDNO.
|
|
*/
|
|
void lock_sidnos(const Gtid_set *set);
|
|
/**
|
|
Unlocks the mutex for each SIDNO where the given Gtid_set has at
|
|
least one GTID.
|
|
*/
|
|
void unlock_sidnos(const Gtid_set *set);
|
|
/**
|
|
Broadcasts the condition variable for each SIDNO where the given
|
|
Gtid_set has at least one GTID.
|
|
*/
|
|
void broadcast_sidnos(const Gtid_set *set);
|
|
/**
|
|
Ensure that owned_gtids, executed_gtids, lost_gtids, gtids_only_in_table,
|
|
previous_gtids_logged and sid_locks have room for at least as many SIDNOs
|
|
as sid_map.
|
|
|
|
This function must only be called in one place:
|
|
Sid_map::add_sid().
|
|
|
|
Requires that the write lock on sid_locks is held. If any object
|
|
needs to be resized, then the lock will be temporarily upgraded to
|
|
a write lock and then degraded to a read lock again; there will be
|
|
a short period when the lock is not held at all.
|
|
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status ensure_sidno();
|
|
|
|
/**
|
|
Adds the given Gtid_set to lost_gtids and executed_gtids.
|
|
lost_gtids must be a subset of executed_gtids.
|
|
|
|
Requires that the caller holds global_sid_lock.wrlock.
|
|
|
|
@param gtid_set The Gtid_set to add.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status add_lost_gtids(const Gtid_set *gtid_set);
|
|
/// Return a pointer to the Gtid_set that contains the lost groups.
|
|
const Gtid_set *get_lost_gtids() const { return &lost_gtids; }
|
|
/*
|
|
Return a pointer to the Gtid_set that contains the stored groups
|
|
in gtid_executed table.
|
|
*/
|
|
const Gtid_set *get_executed_gtids() const { return &executed_gtids; }
|
|
/*
|
|
Return a pointer to the Gtid_set that contains the stored groups
|
|
only in gtid_executed table, not in binlog files.
|
|
*/
|
|
const Gtid_set *get_gtids_only_in_table() const
|
|
{
|
|
return >ids_only_in_table;
|
|
}
|
|
/*
|
|
Return a pointer to the Gtid_set that contains the previous stored
|
|
groups in the last binlog file.
|
|
*/
|
|
const Gtid_set *get_previous_gtids_logged() const
|
|
{
|
|
return &previous_gtids_logged;
|
|
}
|
|
/// Return a pointer to the Owned_gtids that contains the owned groups.
|
|
const Owned_gtids *get_owned_gtids() const { return &owned_gtids; }
|
|
/// Return the server's SID's SIDNO
|
|
rpl_sidno get_server_sidno() const { return server_sidno; }
|
|
/// Return the server's SID
|
|
const rpl_sid &get_server_sid() const
|
|
{
|
|
return global_sid_map->sidno_to_sid(server_sidno);
|
|
}
|
|
#ifndef DBUG_OFF
|
|
/**
|
|
Debug only: Returns an upper bound on the length of the string
|
|
generated by to_string(), not counting '\0'. The actual length
|
|
may be shorter.
|
|
*/
|
|
size_t get_max_string_length() const
|
|
{
|
|
return owned_gtids.get_max_string_length() +
|
|
executed_gtids.get_string_length() +
|
|
lost_gtids.get_string_length() +
|
|
gtids_only_in_table.get_string_length() +
|
|
previous_gtids_logged.get_string_length() +
|
|
150;
|
|
}
|
|
/// Debug only: Generate a string in the given buffer and return the length.
|
|
int to_string(char *buf) const
|
|
{
|
|
char *p= buf;
|
|
p+= sprintf(p, "Executed GTIDs:\n");
|
|
p+= executed_gtids.to_string(p);
|
|
p+= sprintf(p, "\nOwned GTIDs:\n");
|
|
p+= owned_gtids.to_string(p);
|
|
p+= sprintf(p, "\nLost GTIDs:\n");
|
|
p+= lost_gtids.to_string(p);
|
|
p+= sprintf(p, "\nGTIDs only_in_table:\n");
|
|
p+= lost_gtids.to_string(p);
|
|
return (int)(p - buf);
|
|
}
|
|
/// Debug only: return a newly allocated string, or NULL on out-of-memory.
|
|
char *to_string() const
|
|
{
|
|
char *str= (char *)my_malloc(key_memory_Gtid_state_to_string,
|
|
get_max_string_length(), MYF(MY_WME));
|
|
to_string(str);
|
|
return str;
|
|
}
|
|
/// Debug only: print this Gtid_state to stdout.
|
|
void print() const
|
|
{
|
|
char *str= to_string();
|
|
printf("%s", str);
|
|
my_free(str);
|
|
}
|
|
#endif
|
|
/**
|
|
Print this Gtid_state to the trace file if debug is enabled; no-op
|
|
otherwise.
|
|
*/
|
|
void dbug_print(const char *text= "") const
|
|
{
|
|
#ifndef DBUG_OFF
|
|
sid_lock->assert_some_wrlock();
|
|
char *str= to_string();
|
|
DBUG_PRINT("info", ("%s%s%s", text, *text ? ": " : "", str));
|
|
my_free(str);
|
|
#endif
|
|
}
|
|
/**
|
|
Save gtid owned by the thd into executed_gtids variable
|
|
and gtid_executed table.
|
|
|
|
@param thd Session to commit
|
|
@retval
|
|
0 OK
|
|
@retval
|
|
-1 Error
|
|
*/
|
|
int save(THD *thd);
|
|
/**
|
|
Insert the gtid set into table.
|
|
|
|
@param gtid_set contains a set of gtid, which holds
|
|
the sidno and the gno.
|
|
|
|
@retval
|
|
0 OK
|
|
@retval
|
|
-1 Error
|
|
*/
|
|
int save(const Gtid_set *gtid_set);
|
|
/**
|
|
Save the set of gtids logged in the last binlog into gtid_executed table.
|
|
|
|
@param on_rotation true if it is on binlog rotation.
|
|
|
|
@retval
|
|
0 OK
|
|
@retval
|
|
-1 Error
|
|
*/
|
|
int save_gtids_of_last_binlog_into_table(bool on_rotation);
|
|
/**
|
|
Fetch gtids from gtid_executed table and store them into
|
|
gtid_executed set.
|
|
|
|
@retval
|
|
0 OK
|
|
@retval
|
|
1 The table was not found.
|
|
@retval
|
|
-1 Error
|
|
*/
|
|
int read_gtid_executed_from_table();
|
|
/**
|
|
Compress the gtid_executed table, read each row by the PK(sid, gno_start)
|
|
in increasing order, compress the first consecutive gtids range
|
|
(delete consecutive gtids from the second consecutive gtid, then
|
|
update the first gtid) within a single transaction.
|
|
|
|
@param thd Thread requesting to compress the table
|
|
|
|
@retval
|
|
0 OK
|
|
@retval
|
|
1 The table was not found.
|
|
@retval
|
|
-1 Error
|
|
*/
|
|
int compress(THD *thd);
|
|
#ifdef MYSQL_SERVER
|
|
/**
|
|
Push a warning to client if user is modifying the gtid_executed
|
|
table explicitly by a non-XA transaction. Push an error to client
|
|
if user is modifying it explicitly by a XA transaction.
|
|
|
|
@param thd Thread requesting to access the table
|
|
@param table The table is being accessed.
|
|
|
|
@retval 0 No warning or error was pushed to the client.
|
|
@retval 1 Push a warning to client.
|
|
@retval 2 Push an error to client.
|
|
*/
|
|
int warn_or_err_on_modify_gtid_table(THD *thd, TABLE_LIST *table);
|
|
#endif
|
|
|
|
private:
|
|
/**
|
|
Remove the GTID owned by thread from owned GTIDs.
|
|
|
|
This will:
|
|
|
|
- Clean up the thread state if the thread owned GTIDs is empty.
|
|
- Release ownership of all GTIDs owned by the THD. This removes
|
|
the GTID from Owned_gtids and clears the ownership status in the
|
|
THD object.
|
|
- Add the owned GTID to executed_gtids if the is_commit flag is
|
|
set.
|
|
- Decrease counters of GTID-violating transactions.
|
|
- Send a broadcast on the condition variable for every sidno for
|
|
which we released ownership.
|
|
|
|
@param[in] thd - Thread for which owned groups are updated.
|
|
*/
|
|
void update_gtids_impl(THD *thd, bool is_commit);
|
|
#ifdef HAVE_GTID_NEXT_LIST
|
|
/// Lock all SIDNOs owned by the given THD.
|
|
void lock_owned_sidnos(const THD *thd);
|
|
#endif
|
|
/// Unlock all SIDNOs owned by the given THD.
|
|
void unlock_owned_sidnos(const THD *thd);
|
|
/// Broadcast the condition for all SIDNOs owned by the given THD.
|
|
void broadcast_owned_sidnos(const THD *thd);
|
|
/// Read-write lock that protects updates to the number of SIDs.
|
|
mutable Checkable_rwlock *sid_lock;
|
|
/// The Sid_map used by this Gtid_state.
|
|
mutable Sid_map *sid_map;
|
|
/// Contains one mutex/cond pair for every SIDNO.
|
|
Mutex_cond_array sid_locks;
|
|
/**
|
|
The set of GTIDs that existed in some previously purged binary log.
|
|
This is always a subset of executed_gtids.
|
|
*/
|
|
Gtid_set lost_gtids;
|
|
/*
|
|
The set of GTIDs that has been executed and
|
|
stored into gtid_executed table.
|
|
*/
|
|
Gtid_set executed_gtids;
|
|
/*
|
|
The set of GTIDs that exists only in gtid_executed table, not in
|
|
binlog files.
|
|
*/
|
|
Gtid_set gtids_only_in_table;
|
|
/* The previous GTIDs in the last binlog. */
|
|
Gtid_set previous_gtids_logged;
|
|
/// The set of GTIDs that are owned by some thread.
|
|
Owned_gtids owned_gtids;
|
|
/// The SIDNO for this server.
|
|
rpl_sidno server_sidno;
|
|
|
|
/// The number of anonymous transactions owned by any client.
|
|
Atomic_int32 anonymous_gtid_count;
|
|
/// The number of GTID-violating transactions that use GTID_NEXT=AUTOMATIC.
|
|
Atomic_int32 automatic_gtid_violation_count;
|
|
/// The number of GTID-violating transactions that use GTID_NEXT=AUTOMATIC.
|
|
Atomic_int32 anonymous_gtid_violation_count;
|
|
/// The number of clients that are executing
|
|
/// WAIT_FOR_EXECUTED_GTID_SET or WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS.
|
|
Atomic_int32 gtid_wait_count;
|
|
|
|
/// Used by unit tests that need to access private members.
|
|
#ifdef FRIEND_OF_GTID_STATE
|
|
friend FRIEND_OF_GTID_STATE;
|
|
#endif
|
|
|
|
/**
|
|
This is a sub task of update_on_rollback responsible only to handle
|
|
the case of a thread that needs to skip GTID operations when it has
|
|
"failed to commit".
|
|
|
|
Administrative commands [CHECK|REPAIR|OPTIMIZE|ANALYZE] TABLE
|
|
are written to the binary log even when they fail. When the
|
|
commands fail, they will call update_on_rollback; later they will
|
|
write the binary log. But we must not do any of the things in
|
|
update_gtids_impl if we are going to write the binary log. So
|
|
these statements set the skip_gtid_rollback flag, which tells
|
|
update_on_rollback to return early. When the statements are
|
|
written to the binary log they will call update_on_commit as
|
|
usual.
|
|
|
|
@param[in] thd - Thread to be evaluated.
|
|
|
|
@retval true The transaction should skip the rollback, false otherwise.
|
|
*/
|
|
bool update_gtids_impl_check_skip_gtid_rollback(THD *thd);
|
|
/**
|
|
This is a sub task of update_gtids_impl responsible only to handle
|
|
the case of a thread that owns nothing and does not violate GTID
|
|
consistency.
|
|
|
|
If the THD does not own anything, there is nothing to do, so we can do an
|
|
early return of the update process. Except if there is a GTID consistency
|
|
violation; then we need to decrease the counter, so then we can continue
|
|
executing inside update_gtids_impl.
|
|
|
|
@param[in] thd - Thread to be evaluated.
|
|
@retval true The transaction can be skipped because it owns nothing and
|
|
does not violate GTID consistency, false otherwise.
|
|
*/
|
|
bool update_gtids_impl_do_nothing(THD *thd);
|
|
/**
|
|
This is a sub task of update_gtids_impl responsible only to evaluate
|
|
if the thread is committing in the middle of a statement by checking
|
|
THD's is_commit_in_middle_of_statement flag.
|
|
|
|
This flag is true for anonymous transactions, when the
|
|
'transaction' has been split into multiple transactions in the
|
|
binlog, and the present transaction is not the last one.
|
|
|
|
This means two things:
|
|
|
|
- We should not release anonymous ownership in case
|
|
gtid_next=anonymous. If we did, it would be possible for user
|
|
to set GTID_MODE=ON from a concurrent transaction, making it
|
|
impossible to commit the current transaction.
|
|
|
|
- We should not decrease the counters for GTID-violating
|
|
statements. If we did, it would be possible for a concurrent
|
|
client to set ENFORCE_GTID_CONSISTENCY=ON despite there is an
|
|
ongoing transaction that violates GTID consistency.
|
|
|
|
The flag is set in two cases:
|
|
|
|
1. We are committing the statement cache when there are more
|
|
changes in the transaction cache.
|
|
|
|
This happens either because a single statement in the
|
|
beginning of a transaction updates both transactional and
|
|
non-transactional tables, or because we are committing a
|
|
non-transactional update in the middle of a transaction when
|
|
binlog_direct_non_transactional_updates=1.
|
|
|
|
In this case, the flag is set further down in this function.
|
|
|
|
2. The statement is one of the special statements that may
|
|
generate multiple transactions: CREATE...SELECT, DROP TABLE,
|
|
DROP DATABASE. See comment for THD::owned_gtid in
|
|
sql/sql_class.h.
|
|
|
|
In this case, the THD::is_commit_in_middle_of_statement flag
|
|
is set by the caller and the flag becomes true here.
|
|
|
|
@param[in] thd - Thread to be evaluated.
|
|
@return The value of thread's is_commit_in_middle_of_statement flag.
|
|
*/
|
|
bool update_gtids_impl_begin(THD *thd);
|
|
/**
|
|
Handle the case that the thread own a set of GTIDs.
|
|
|
|
This is a sub task of update_gtids_impl responsible only to handle
|
|
the case of a thread with a set of GTIDs being updated.
|
|
|
|
- Release ownership of the GTIDs owned by the THD. This removes
|
|
the GTID from Owned_gtids and clears the ownership status in the
|
|
THD object.
|
|
- Add the owned GTIDs to executed_gtids if the is_commit flag is set.
|
|
- Send a broadcast on the condition variable for the sidno which we
|
|
released ownership.
|
|
|
|
@param[in] thd - Thread for which owned GTID set should be updated.
|
|
@param[in] is_commit - If the thread is being updated by a commit.
|
|
*/
|
|
void update_gtids_impl_own_gtid_set(THD *thd, bool is_commit);
|
|
/**
|
|
Lock a given sidno of a transaction being updated.
|
|
|
|
This is a sub task of update_gtids_impl responsible only to lock the
|
|
sidno of the GTID being updated.
|
|
|
|
@param[in] sidno - The sidno to be locked.
|
|
*/
|
|
void update_gtids_impl_lock_sidno(rpl_sidno sidno);
|
|
/**
|
|
|
|
Locks the sidnos of all the GTIDs of the commit group starting on the
|
|
transaction passed as parameter.
|
|
|
|
This is a sub task of update_commit_group responsible only to lock the
|
|
sidno(s) of the GTID(s) being updated.
|
|
|
|
The function should follow thd->next_to_commit to lock all sidnos of all
|
|
transactions being updated in a group.
|
|
|
|
@param[in] thd - Thread that owns the GTID(s) to be updated or leader
|
|
of the commit group in the case of a commit group
|
|
update.
|
|
*/
|
|
void update_gtids_impl_lock_sidnos(THD *thd);
|
|
/**
|
|
Handle the case that the thread own a single non-anonymous GTID.
|
|
|
|
This is a sub task of update_gtids_impl responsible only to handle
|
|
the case of a thread with a single non-anonymous GTID being updated
|
|
either for commit or rollback.
|
|
|
|
- Release ownership of the GTID owned by the THD. This removes
|
|
the GTID from Owned_gtids and clears the ownership status in the
|
|
THD object.
|
|
- Add the owned GTID to executed_gtids if the is_commit flag is set.
|
|
- Send a broadcast on the condition variable for the sidno which we
|
|
released ownership.
|
|
|
|
@param[in] thd - Thread to be updated that owns single non-anonymous GTID.
|
|
@param[in] is_commit - If the thread is being updated by a commit.
|
|
*/
|
|
void update_gtids_impl_own_gtid(THD *thd, bool is_commit);
|
|
/**
|
|
Unlock a given sidno after broadcasting its changes.
|
|
|
|
This is a sub task of update_gtids_impl responsible only to
|
|
unlock the sidno of the GTID being updated after broadcasting
|
|
its changes.
|
|
|
|
@param[in] sidno - The sidno to be broadcasted and unlocked.
|
|
*/
|
|
void update_gtids_impl_broadcast_and_unlock_sidno(rpl_sidno sidno);
|
|
/**
|
|
Unlocks all locked sidnos after broadcasting their changes.
|
|
|
|
This is a sub task of update_commit_group responsible only to
|
|
unlock the sidno(s) of the GTID(s) being updated after broadcasting
|
|
their changes.
|
|
*/
|
|
void update_gtids_impl_broadcast_and_unlock_sidnos();
|
|
/**
|
|
Handle the case that the thread owns ANONYMOUS GTID.
|
|
|
|
This is a sub task of update_gtids_impl responsible only to handle
|
|
the case of a thread with an ANONYMOUS GTID being updated.
|
|
|
|
- Release ownership of the anonymous GTID owned by the THD and clears
|
|
the ownership status in the THD object.
|
|
- Decrease counters of GTID-violating transactions.
|
|
|
|
@param[in] thd - Thread to be updated that owns anonymous GTID.
|
|
@param[in,out] more_trx - If the 'transaction' has been split into
|
|
multiple transactions in the binlog.
|
|
This is firstly assigned with the return of
|
|
Gtid_state::update_gtids_impl_begin function, and
|
|
its value can be set to true when
|
|
Gtid_state::update_gtids_impl_anonymous_gtid
|
|
detects more content on the transaction cache.
|
|
*/
|
|
void update_gtids_impl_own_anonymous(THD* thd, bool *more_trx);
|
|
/**
|
|
Handle the case that the thread owns nothing.
|
|
|
|
This is a sub task of update_gtids_impl responsible only to handle
|
|
the case of a thread that owns nothing being updated.
|
|
|
|
There are two cases when this happens:
|
|
- Normally, it is a rollback of an automatic transaction, so
|
|
the is_commit is false and gtid_next=automatic.
|
|
- There is also a corner case. This case may happen for a transaction
|
|
that uses GTID_NEXT=AUTOMATIC, and violates GTID_CONSISTENCY, and
|
|
commits changes to the database, but does not write to the binary log,
|
|
so that no GTID is generated. An example is CREATE TEMPORARY TABLE
|
|
inside a transaction when binlog_format=row. Despite the thread does
|
|
not own anything, the GTID consistency violation makes it necessary to
|
|
call end_gtid_violating_transaction. Therefore
|
|
MYSQL_BIN_LOG::gtid_end_transaction will call
|
|
gtid_state->update_on_commit in this case, and subsequently we will
|
|
reach this case.
|
|
|
|
@param[in] thd - Thread to be updated that owns anonymous GTID.
|
|
*/
|
|
void update_gtids_impl_own_nothing(THD *thd);
|
|
/**
|
|
Handle the final part of update_gtids_impl.
|
|
|
|
This is a sub task of update_gtids_impl responsible only to handle
|
|
the call to end_gtid_violating_transaction function when there is no
|
|
more transactions split after the current transaction.
|
|
|
|
@param[in] thd - Thread for which owned group is updated.
|
|
@param[in] more_trx - This is the value returned from
|
|
Gtid_state::update_gtids_impl_begin and can be
|
|
changed for transactions owning anonymous GTID at
|
|
Gtid_state::update_gtids_impl_own_anonymous.
|
|
*/
|
|
void update_gtids_impl_end(THD *thd, bool more_trx);
|
|
/**
|
|
This array is used by Gtid_state_update_gtids_impl* functions.
|
|
|
|
The array items (one per sidno of the sid_map) will be set as true for
|
|
each sidno that requires to be locked when updating a set of GTIDs
|
|
(at Gtid_set::update_gtids_impl_lock_sidnos).
|
|
|
|
The array items will be set false at
|
|
Gtid_set::update_gtids_impl_broadcast_and_unlock_sidnos.
|
|
|
|
It is used to so that lock, unlock, and broadcast operations are only
|
|
called once per sidno per commit group, instead of once per transaction.
|
|
|
|
Its access is protected by:
|
|
- global_sid_lock->wrlock when growing and cleaning up;
|
|
- MYSQL_BIN_LOG::LOCK_commit when setting true/false on array items.
|
|
*/
|
|
Prealloced_array<bool, 8, true> commit_group_sidnos;
|
|
/**
|
|
Ensure that commit_group_sidnos have room for the SIDNO passed as
|
|
parameter.
|
|
|
|
This function must only be called in one place:
|
|
Gtid_state::ensure_sidno().
|
|
|
|
@param sidno The SIDNO.
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status ensure_commit_group_sidnos(rpl_sidno sidno);
|
|
};
|
|
|
|
|
|
/**
|
|
Enumeration of group types formed inside a transactions.
|
|
The structure of a group is as follows:
|
|
@verbatim
|
|
Group {
|
|
SID (16 byte UUID): The source identifier for the group
|
|
GNO (8 byte unsigned int): The group number for the group
|
|
COMMIT_FLAG (boolean): True if this is the last group of the
|
|
transaction
|
|
}
|
|
@endverbatim
|
|
*/
|
|
enum enum_group_type
|
|
{
|
|
/**
|
|
Specifies that the GTID has not been generated yet; it will be
|
|
generated on commit. It will depend on the GTID_MODE: if
|
|
GTID_MODE<=OFF_PERMISSIVE, then the transaction will be anonymous;
|
|
if GTID_MODE>=ON_PERMISSIVE, then the transaction will be assigned
|
|
a new GTID.
|
|
|
|
This is the default value: thd->variables.gtid_next has this state
|
|
when GTID_NEXT="AUTOMATIC".
|
|
|
|
It is important that AUTOMATIC_GROUP==0 so that the default value
|
|
for thd->variables->gtid_next.type is AUTOMATIC_GROUP.
|
|
*/
|
|
AUTOMATIC_GROUP= 0,
|
|
/**
|
|
Specifies that the transaction has been assigned a GTID (UUID:NUMBER).
|
|
|
|
thd->variables.gtid_next has this state when GTID_NEXT="UUID:NUMBER".
|
|
|
|
This is the state of GTID-transactions replicated to the slave.
|
|
*/
|
|
GTID_GROUP,
|
|
/**
|
|
Specifies that the transaction is anonymous, i.e., it does not
|
|
have a GTID and will never be assigned one.
|
|
|
|
thd->variables.gtid_next has this state when GTID_NEXT="ANONYMOUS".
|
|
|
|
This is the state of any transaction generated on a pre-GTID
|
|
server, or on a server with GTID_MODE==OFF.
|
|
*/
|
|
ANONYMOUS_GROUP,
|
|
/**
|
|
GTID_NEXT is set to this state after a transaction with
|
|
GTID_NEXT=='UUID:NUMBER' is committed.
|
|
|
|
This is used to protect against a special case of unsafe
|
|
non-transactional updates.
|
|
|
|
Background: Non-transactional updates are allowed as long as they
|
|
are sane. Non-transactional updates must be single-statement
|
|
transactions; they must not be mixed with transactional updates in
|
|
the same statement or in the same transaction. Since
|
|
non-transactional updates must be logged separately from
|
|
transactional updates, a single mixed statement would generate two
|
|
different transactions.
|
|
|
|
Problematic case: Consider a transaction, Tx1, that updates two
|
|
transactional tables on the master, t1 and t2. Then slave (s1) later
|
|
replays Tx1. However, t2 is a non-transactional table at s1. As such, s1
|
|
will report an error because it cannot split Tx1 into two different
|
|
transactions. Had no error been reported, then Tx1 would be split into Tx1
|
|
and Tx2, potentially causing severe harm in case some form of fail-over
|
|
procedure is later engaged by s1.
|
|
|
|
To detect this case on the slave and generate an appropriate error
|
|
message rather than causing an inconsistency in the GTID state, we
|
|
do as follows. When committing a transaction that has
|
|
GTID_NEXT==UUID:NUMBER, we set GTID_NEXT to UNDEFINED_GROUP. When
|
|
the next part of the transaction is being processed, an error is
|
|
generated, because it is not allowed to execute a transaction when
|
|
GTID_NEXT==UNDEFINED. In the normal case, the error is not
|
|
generated, because there will always be a Gtid_log_event after the
|
|
next transaction.
|
|
*/
|
|
UNDEFINED_GROUP,
|
|
/*
|
|
GTID_NEXT is set to this state by the slave applier thread when it
|
|
reads a Format_description_log_event that does not originate from
|
|
this server.
|
|
|
|
Background: when the slave applier thread reads a relay log that
|
|
comes from a pre-GTID master, it must preserve the transactions as
|
|
anonymous transactions, even if GTID_MODE>=ON_PERMISSIVE. This
|
|
may happen, e.g., if the relay log was received when master and
|
|
slave had GTID_MODE=OFF or when master and slave were old, and the
|
|
relay log is applied when slave has GTID_MODE>=ON_PERMISSIVE.
|
|
|
|
So the slave thread should set GTID_NEXT=ANONYMOUS for the next
|
|
transaction when it starts to process an old binary log. However,
|
|
there is no way for the slave to tell if the binary log is old,
|
|
until it sees the first transaction. If the first transaction
|
|
begins with a Gtid_log_event, we have the GTID there; if it begins
|
|
with query_log_event, row events, etc, then this is an old binary
|
|
log. So at the time the binary log begins, we just set
|
|
GTID_NEXT=NOT_YET_DETERMINED_GROUP. If it remains
|
|
NOT_YET_DETERMINED when the next transaction begins,
|
|
gtid_pre_statement_checks will automatically turn it into an
|
|
anonymous transaction. If a Gtid_log_event comes across before
|
|
the next transaction starts, then the Gtid_log_event will just set
|
|
GTID_NEXT='UUID:NUMBER' accordingly.
|
|
*/
|
|
NOT_YET_DETERMINED_GROUP
|
|
};
|
|
/// Global state of GTIDs.
|
|
extern Gtid_state *gtid_state;
|
|
|
|
|
|
/**
|
|
This struct represents a specification of a GTID for a statement to
|
|
be executed: either "AUTOMATIC", "ANONYMOUS", or "SID:GNO".
|
|
|
|
This is a POD. It has to be a POD because it is used in THD::variables.
|
|
*/
|
|
struct Gtid_specification
|
|
{
|
|
/// The type of this GTID
|
|
enum_group_type type;
|
|
/**
|
|
The GTID:
|
|
{ SIDNO, GNO } if type == GTID;
|
|
{ 0, 0 } if type == AUTOMATIC or ANONYMOUS.
|
|
*/
|
|
Gtid gtid;
|
|
/// Set the type to GTID_GROUP and SID, GNO to the given values.
|
|
void set(rpl_sidno sidno, rpl_gno gno)
|
|
{
|
|
gtid.set(sidno, gno);
|
|
type= GTID_GROUP;
|
|
}
|
|
/// Set the type to GTID_GROUP and SID, GNO to the given Gtid.
|
|
void set(const Gtid >id_param) { set(gtid_param.sidno, gtid_param.gno); }
|
|
/// Set the type to AUTOMATIC_GROUP.
|
|
void set_automatic()
|
|
{
|
|
type= AUTOMATIC_GROUP;
|
|
}
|
|
/// Set the type to ANONYMOUS_GROUP.
|
|
void set_anonymous()
|
|
{
|
|
type= ANONYMOUS_GROUP;
|
|
}
|
|
/// Set the type to NOT_YET_DETERMINED_GROUP.
|
|
void set_not_yet_determined()
|
|
{
|
|
type= NOT_YET_DETERMINED_GROUP;
|
|
}
|
|
/// Set to undefined. Must only be called if the type is GTID_GROUP.
|
|
void set_undefined()
|
|
{
|
|
DBUG_ASSERT(type == GTID_GROUP);
|
|
type= UNDEFINED_GROUP;
|
|
}
|
|
/// Return true if this Gtid_specification is equal to 'other'.
|
|
bool equals(const Gtid_specification &other) const
|
|
{
|
|
return (type == other.type &&
|
|
(type != GTID_GROUP || gtid.equals(other.gtid)));
|
|
}
|
|
/**
|
|
Return true if this Gtid_specification is a GTID_GROUP with the
|
|
same SID, GNO as 'other_gtid'.
|
|
*/
|
|
bool equals(const Gtid &other_gtid) const
|
|
{ return type == GTID_GROUP && gtid.equals(other_gtid); }
|
|
#ifndef MYSQL_CLIENT
|
|
/**
|
|
Parses the given string and stores in this Gtid_specification.
|
|
|
|
@param text The text to parse
|
|
@return RETURN_STATUS_OK or RETURN_STATUS_REPORTED_ERROR.
|
|
*/
|
|
enum_return_status parse(Sid_map *sid_map, const char *text);
|
|
/// Returns true if the given string is a valid Gtid_specification.
|
|
static bool is_valid(const char *text);
|
|
#endif
|
|
static const int MAX_TEXT_LENGTH= Gtid::MAX_TEXT_LENGTH;
|
|
/**
|
|
Writes this Gtid_specification to the given string buffer.
|
|
|
|
@param sid_map Sid_map to use if the type of this
|
|
Gtid_specification is GTID_GROUP.
|
|
@param buf[out] The buffer
|
|
@param need_lock If true, this function acquires global_sid_lock
|
|
before looking up the sidno in sid_map, and then releases it. If
|
|
false, this function asserts that the lock is held by the caller.
|
|
@retval The number of characters written.
|
|
*/
|
|
int to_string(const Sid_map *sid_map, char *buf, bool need_lock= false) const;
|
|
/**
|
|
Writes this Gtid_specification to the given string buffer.
|
|
|
|
@param sid SID to use if the type of this Gtid_specification is
|
|
GTID_GROUP. Can be NULL if this Gtid_specification is
|
|
ANONYMOUS_GROUP or AUTOMATIC_GROUP.
|
|
@param buf[out] The buffer
|
|
@retval The number of characters written.
|
|
@buf[out]
|
|
*/
|
|
int to_string(const rpl_sid *sid, char *buf) const;
|
|
#ifndef DBUG_OFF
|
|
/// Debug only: print this Gtid_specification to stdout.
|
|
void print() const
|
|
{
|
|
char buf[MAX_TEXT_LENGTH + 1];
|
|
to_string(global_sid_map, buf);
|
|
printf("%s\n", buf);
|
|
}
|
|
#endif
|
|
/**
|
|
Print this Gtid_specificatoin to the trace file if debug is
|
|
enabled; no-op otherwise.
|
|
*/
|
|
void dbug_print(const char *text= "", bool need_lock= false) const
|
|
{
|
|
#ifndef DBUG_OFF
|
|
char buf[MAX_TEXT_LENGTH + 1];
|
|
to_string(global_sid_map, buf, need_lock);
|
|
DBUG_PRINT("info", ("%s%s%s", text, *text ? ": " : "", buf));
|
|
#endif
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
Represents a group in the group cache.
|
|
|
|
Groups in the group cache are slightly different from other groups,
|
|
because not all information about them is known.
|
|
|
|
Automatic groups are marked as such by setting gno<=0.
|
|
*/
|
|
struct Cached_group
|
|
{
|
|
/// The gtid for this group.
|
|
Gtid_specification spec;
|
|
/**
|
|
The position of this GTID in the cache, i.e., the total size of
|
|
all previous groups.
|
|
*/
|
|
rpl_binlog_pos binlog_offset;
|
|
};
|
|
|
|
|
|
/**
|
|
Indicates if a statement should be skipped or not. Used as return
|
|
value from gtid_before_statement.
|
|
*/
|
|
enum enum_gtid_statement_status
|
|
{
|
|
/// Statement can execute.
|
|
GTID_STATEMENT_EXECUTE,
|
|
/// Statement should be cancelled.
|
|
GTID_STATEMENT_CANCEL,
|
|
/**
|
|
Statement should be skipped, but there may be an implicit commit
|
|
after the statement if gtid_commit is set.
|
|
*/
|
|
GTID_STATEMENT_SKIP
|
|
};
|
|
|
|
|
|
#ifndef MYSQL_CLIENT
|
|
/**
|
|
Perform GTID-related checks before executing a statement:
|
|
|
|
- Check that the current statement does not contradict
|
|
enforce_gtid_consistency.
|
|
|
|
- Check that there is no implicit commit in a transaction when
|
|
GTID_NEXT==UUID:NUMBER.
|
|
|
|
- Change thd->variables.gtid_next.type to ANONYMOUS_GROUP if it is
|
|
currently NOT_YET_DETERMINED_GROUP.
|
|
|
|
- Check whether the statement should be cancelled.
|
|
|
|
@param thd THD object for the session.
|
|
|
|
@retval GTID_STATEMENT_EXECUTE The normal case: the checks
|
|
succeeded, and statement can execute.
|
|
|
|
@retval GTID_STATEMENT_CANCEL The checks failed; an
|
|
error has be generated and the statement must stop.
|
|
|
|
@retval GTID_STATEMENT_SKIP The checks succeeded, but the GTID has
|
|
already been executed (exists in GTID_EXECUTED). So the statement
|
|
must not execute; however, if there are implicit commits, then the
|
|
implicit commits must execute.
|
|
*/
|
|
enum_gtid_statement_status gtid_pre_statement_checks(THD *thd);
|
|
|
|
/**
|
|
Perform GTID-related checks before executing a statement, but after
|
|
executing an implicit commit before the statement, if any:
|
|
|
|
If gtid_next=anonymous, but the thread does not hold anonymous
|
|
ownership, then acquire anonymous ownership. (Do this only if this
|
|
is not an 'innocent' statement, i.e., SET/SHOW/DO/SELECT that does
|
|
not invoke a stored function.)
|
|
|
|
It is important that this is done after the implicit commit, because
|
|
the implicit commit may release anonymous ownership.
|
|
|
|
@param thd THD object for the session
|
|
|
|
@retval false Success.
|
|
|
|
@retval true Error. Error can happen if GTID_MODE=ON. The error has
|
|
been reported by (a function called by) this function.
|
|
*/
|
|
bool gtid_pre_statement_post_implicit_commit_checks(THD *thd);
|
|
|
|
/**
|
|
Acquire ownership of the given Gtid_specification.
|
|
|
|
The Gtid_specification must be of type GTID_GROUP or ANONYMOUS_GROUP.
|
|
|
|
The caller must hold global_sid_lock (normally the rdlock). The
|
|
lock may be termporarily released and acquired again. In the end,
|
|
the lock will be released, so the caller should *not* release the
|
|
lock.
|
|
|
|
The function will try to acquire ownership of the GTID and update
|
|
both THD::gtid_next, Gtid_state::owned_gtids, and
|
|
THD::owned_gtid/THD::owned_sid.
|
|
|
|
@param thd The thread that acquires ownership.
|
|
|
|
@param spec The Gtid_specification.
|
|
|
|
@retval false Success: either we have acquired ownership of the
|
|
GTID, or it is already included in GTID_EXECUTED and will be
|
|
skipped.
|
|
|
|
@retval true Failure; the thread was killed or an error occurred.
|
|
The error has been reported using my_error.
|
|
*/
|
|
bool set_gtid_next(THD *thd, const Gtid_specification &spec);
|
|
#ifdef HAVE_GTID_NEXT_LIST
|
|
int gtid_acquire_ownership_multiple(THD *thd);
|
|
#endif
|
|
|
|
/**
|
|
Return sidno for a given sid, see Sid_map::add_sid() for details.
|
|
*/
|
|
rpl_sidno get_sidno_from_global_sid_map(rpl_sid sid);
|
|
|
|
/**
|
|
Return last gno for a given sidno, see
|
|
Gtid_state::get_last_executed_gno() for details.
|
|
*/
|
|
rpl_gno get_last_executed_gno(rpl_sidno sidno);
|
|
|
|
void gtid_set_performance_schema_values(const THD *thd);
|
|
|
|
/**
|
|
If gtid_next=ANONYMOUS or NOT_YET_DETERMINED, but the thread does
|
|
not hold anonymous ownership, acquire anonymous ownership.
|
|
|
|
@param thd Thread.
|
|
|
|
@retval true Error (can happen if gtid_mode=ON and
|
|
gtid_next=anonymous). The error has already been reported using
|
|
my_error.
|
|
|
|
@retval false Success.
|
|
*/
|
|
bool gtid_reacquire_ownership_if_anonymous(THD *thd);
|
|
|
|
|
|
/**
|
|
The function commits or rolls back the gtid state if it needs to.
|
|
It's supposed to be invoked at the end of transaction commit or
|
|
rollback, as well as as at the end of XA prepare.
|
|
|
|
@param thd Thread context
|
|
@param needs_to The actual work will be done when the parameter is true
|
|
@param do_commit When true the gtid state changes are committed, otherwise
|
|
they are rolled back.
|
|
*/
|
|
|
|
inline void gtid_state_commit_or_rollback(THD *thd, bool needs_to,
|
|
bool do_commit)
|
|
{
|
|
if (needs_to)
|
|
{
|
|
if (do_commit)
|
|
gtid_state->update_on_commit(thd);
|
|
else
|
|
gtid_state->update_on_rollback(thd);
|
|
}
|
|
}
|
|
|
|
#endif // ifndef MYSQL_CLIENT
|
|
|
|
#endif /* RPL_GTID_H_INCLUDED */
|