mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 19:06:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			420 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			420 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright 2018-2025 Codership Oy <info@codership.com>
 | |
| 
 | |
|    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 */
 | |
| #include "mariadb.h"
 | |
| 
 | |
| #include "mysql/service_wsrep.h"
 | |
| #include "wsrep/key.hpp"
 | |
| #include "wsrep_thd.h"
 | |
| #include "wsrep_trans_observer.h"
 | |
| #include "sql_class.h"
 | |
| #include "debug_sync.h"
 | |
| #include "log.h"
 | |
| 
 | |
| extern "C" my_bool wsrep_on(const THD *thd)
 | |
| {
 | |
|   return my_bool(WSREP(thd));
 | |
| }
 | |
| 
 | |
| extern "C" void wsrep_thd_LOCK(const THD *thd)
 | |
| {
 | |
|   mysql_mutex_lock(&thd->LOCK_thd_data);
 | |
| }
 | |
| 
 | |
| extern "C" int wsrep_thd_TRYLOCK(const THD *thd)
 | |
| {
 | |
|   return mysql_mutex_trylock(&thd->LOCK_thd_data);
 | |
| }
 | |
| 
 | |
| extern "C" void wsrep_thd_UNLOCK(const THD *thd)
 | |
| {
 | |
|   mysql_mutex_unlock(&thd->LOCK_thd_data);
 | |
| }
 | |
| 
 | |
| extern "C" void wsrep_thd_kill_LOCK(const THD *thd)
 | |
| {
 | |
|   mysql_mutex_lock(&thd->LOCK_thd_kill);
 | |
| }
 | |
| 
 | |
| extern "C" void wsrep_thd_kill_UNLOCK(const THD *thd)
 | |
| {
 | |
|   mysql_mutex_unlock(&thd->LOCK_thd_kill);
 | |
| }
 | |
| 
 | |
| extern "C" const char* wsrep_thd_client_state_str(const THD *thd)
 | |
| {
 | |
|   return wsrep::to_c_string(thd->wsrep_cs().state());
 | |
| }
 | |
| 
 | |
| extern "C" const char* wsrep_thd_client_mode_str(const THD *thd)
 | |
| {
 | |
|   return wsrep::to_c_string(thd->wsrep_cs().mode());
 | |
| }
 | |
| 
 | |
| extern "C" const char* wsrep_thd_transaction_state_str(const THD *thd)
 | |
| {
 | |
|   return wsrep::to_c_string(thd->wsrep_cs().transaction().state());
 | |
| }
 | |
| 
 | |
| extern "C" const char *wsrep_thd_query(const THD *thd)
 | |
| {
 | |
|   if (!thd)
 | |
|     return "NULL";
 | |
| 
 | |
|   switch(thd->lex->sql_command)
 | |
|   {
 | |
|     // Mask away some security related details from error log
 | |
|     case SQLCOM_CREATE_USER:
 | |
|       return "CREATE USER";
 | |
|     case SQLCOM_GRANT:
 | |
|       return "GRANT";
 | |
|     case SQLCOM_REVOKE:
 | |
|       return "REVOKE";
 | |
|     case SQLCOM_SET_OPTION:
 | |
|       return "SET OPTION";
 | |
|       /* fallthrough */
 | |
|     default:
 | |
|     {
 | |
|       return (thd->query() ? thd->query() : "NULL");
 | |
|     }
 | |
|   }
 | |
|   return "NULL";
 | |
| }
 | |
| 
 | |
| extern "C" query_id_t wsrep_thd_transaction_id(const THD *thd)
 | |
| {
 | |
|   return thd->wsrep_cs().transaction().id().get();
 | |
| }
 | |
| 
 | |
| extern "C" long long wsrep_thd_trx_seqno(const THD *thd)
 | |
| {
 | |
|   const wsrep::client_state& cs= thd->wsrep_cs();
 | |
|   if (cs.mode() == wsrep::client_state::m_toi)
 | |
|   {
 | |
|     return cs.toi_meta().seqno().get();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     return cs.transaction().ws_meta().seqno().get();
 | |
|   }
 | |
| }
 | |
| 
 | |
| extern "C" void wsrep_thd_self_abort(THD *thd)
 | |
| {
 | |
|   thd->wsrep_cs().bf_abort(wsrep::seqno(0));
 | |
| }
 | |
| 
 | |
| extern "C" const char* wsrep_get_sr_table_name()
 | |
| {
 | |
|   return wsrep_sr_table_name_full;
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_get_debug()
 | |
| {
 | |
|   return wsrep_debug;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Test if this connection is a true local (user) connection and not
 | |
|   a replication or wsrep applier thread.
 | |
| 
 | |
|   Note that this is only usable for galera (as there are other kinds
 | |
|   of system threads, and only if WSREP_NNULL() is tested by the caller.
 | |
|  */
 | |
| extern "C" my_bool wsrep_thd_is_local(const THD *thd)
 | |
| {
 | |
|   /*
 | |
|     async replication IO and background threads have nothing to
 | |
|     replicate in the cluster, marking them as non-local here to
 | |
|     prevent write set population and replication
 | |
| 
 | |
|     async replication SQL thread, applies client transactions from
 | |
|     mariadb master and will be replicated into cluster
 | |
|   */
 | |
|   return (
 | |
|           thd->system_thread != SYSTEM_THREAD_SLAVE_BACKGROUND &&
 | |
|           thd->system_thread != SYSTEM_THREAD_SLAVE_IO &&
 | |
|           thd->wsrep_cs().mode() == wsrep::client_state::m_local);
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_thd_is_applying(const THD *thd)
 | |
| {
 | |
|   return thd->wsrep_cs().mode() == wsrep::client_state::m_high_priority;
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_thd_is_toi(const THD *thd)
 | |
| {
 | |
|   return thd->wsrep_cs().mode() == wsrep::client_state::m_toi;
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_thd_is_local_toi(const THD *thd)
 | |
| {
 | |
|   return thd->wsrep_cs().mode() == wsrep::client_state::m_toi &&
 | |
|          thd->wsrep_cs().toi_mode() == wsrep::client_state::m_local;
 | |
| 
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_thd_is_in_rsu(const THD *thd)
 | |
| {
 | |
|   return thd->wsrep_cs().mode() == wsrep::client_state::m_rsu;
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_thd_is_BF(const THD *thd, my_bool sync)
 | |
| {
 | |
|   my_bool status = FALSE;
 | |
|   if (thd && WSREP(thd))
 | |
|   {
 | |
|     if (sync) mysql_mutex_lock(&thd->LOCK_thd_data);
 | |
|     status = (wsrep_thd_is_applying(thd) || wsrep_thd_is_toi(thd));
 | |
|     if (sync) mysql_mutex_unlock(&thd->LOCK_thd_data);
 | |
|   }
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_thd_is_SR(const THD *thd)
 | |
| {
 | |
|   return thd && thd->wsrep_cs().transaction().is_streaming() &&
 | |
|     thd->wsrep_cs().transaction().state() == wsrep::transaction::s_executing;
 | |
| }
 | |
| 
 | |
| extern "C" void wsrep_handle_SR_rollback(THD *bf_thd __attribute__((unused)),
 | |
|                                          THD *victim_thd)
 | |
| {
 | |
|   /*
 | |
|     We should always be in victim_thd context, either client session is
 | |
|     rolling back or rollbacker thread should be in control.
 | |
|   */
 | |
|   DBUG_ASSERT(victim_thd);
 | |
|   DBUG_ASSERT(current_thd == victim_thd);
 | |
|   DBUG_ASSERT(wsrep_thd_is_SR(victim_thd));
 | |
| 
 | |
|   /* Defensive measure to avoid crash in production. */
 | |
|   if (!victim_thd) return;
 | |
| 
 | |
|   WSREP_DEBUG("Handle SR rollback, for deadlock: thd %llu trx_id %" PRIu64 " frags %zu conf %s",
 | |
|               victim_thd->thread_id,
 | |
|               victim_thd->wsrep_trx_id(),
 | |
|               victim_thd->wsrep_sr().fragments_certified(),
 | |
|               wsrep_thd_transaction_state_str(victim_thd));
 | |
| 
 | |
|   DEBUG_SYNC(victim_thd, "wsrep_before_SR_rollback");
 | |
| 
 | |
|   wsrep_thd_self_abort(victim_thd);
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_thd_bf_abort(THD *bf_thd, THD *victim_thd,
 | |
|                                       my_bool signal)
 | |
| {
 | |
|   mysql_mutex_assert_owner(&victim_thd->LOCK_thd_kill);
 | |
|   mysql_mutex_assert_owner(&victim_thd->LOCK_thd_data);
 | |
|   my_bool ret= wsrep_bf_abort(bf_thd, victim_thd);
 | |
|   /*
 | |
|     Send awake signal if victim was BF aborted or does not
 | |
|     have wsrep on. Note that this should never interrupt RSU
 | |
|     as RSU has paused the provider.
 | |
|    */
 | |
|   if ((ret || !wsrep_on(victim_thd)) && signal)
 | |
|   {
 | |
|     victim_thd->wsrep_aborter= bf_thd->thread_id;
 | |
|     victim_thd->awake_no_mutex(KILL_QUERY_HARD);
 | |
|   } else {
 | |
|     WSREP_DEBUG("wsrep_thd_bf_abort skipped awake, signal %d", signal);
 | |
|   }
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_thd_skip_locking(const THD *thd)
 | |
| {
 | |
|   return thd && thd->wsrep_skip_locking;
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_thd_order_before(const THD *left, const THD *right)
 | |
| {
 | |
|   my_bool before= (wsrep_thd_is_BF(left, false) &&
 | |
|                    wsrep_thd_is_BF(right, false) &&
 | |
|                    wsrep_thd_trx_seqno(left) < wsrep_thd_trx_seqno(right));
 | |
| 
 | |
|   WSREP_DEBUG("wsrep_thd_order_before: %s thread=%llu seqno=%llu query=%s "
 | |
|               "%s %s thread=%llu, seqno=%llu query=%s",
 | |
|               (wsrep_thd_is_BF(left, false) ? "BF" : "def"),
 | |
|               thd_get_thread_id(left),
 | |
|               wsrep_thd_trx_seqno(left),
 | |
|               wsrep_thd_query(left),
 | |
|               (before ? " TRUE " : " FALSE "),
 | |
|               (wsrep_thd_is_BF(right, false) ? "BF" : "def"),
 | |
|               thd_get_thread_id(right),
 | |
|               wsrep_thd_trx_seqno(right),
 | |
|               wsrep_thd_query(right));
 | |
| 
 | |
|   return before;
 | |
| }
 | |
| 
 | |
| /** Check if wsrep transaction is aborting state.
 | |
| 
 | |
| Calling function should make sure that wsrep transaction state
 | |
| can't change during this function.
 | |
| 
 | |
| This function is called from
 | |
| wsrep_abort_thd where we hold THD::LOCK_thd_data
 | |
| wsrep_handle_mdl_conflict we hold THD::LOCK_thd_data
 | |
| wsrep_assert_no_bf_bf_wait we hold lock_sys.latch
 | |
| innobase_kill_query we hold THD::LOCK_thd_data (THD::awake_no_mutex)
 | |
| 
 | |
| @param thd         thread handle
 | |
| 
 | |
| @return true       if wsrep transaction is aborting
 | |
| @return false      if not
 | |
| 
 | |
| */
 | |
| extern "C" my_bool wsrep_thd_is_aborting(const MYSQL_THD thd)
 | |
| {
 | |
|   const wsrep::client_state& cs(thd->wsrep_cs());
 | |
|   const enum wsrep::transaction::state tx_state(cs.transaction().state());
 | |
| 
 | |
|   switch (tx_state)
 | |
|   {
 | |
|     case wsrep::transaction::s_must_abort:
 | |
|       return (cs.state() == wsrep::client_state::s_exec ||
 | |
|               cs.state() == wsrep::client_state::s_result);
 | |
|     case wsrep::transaction::s_aborting:
 | |
|       return true;
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static inline enum wsrep::key::type
 | |
| map_key_type(enum Wsrep_service_key_type type)
 | |
| {
 | |
|   switch (type)
 | |
|   {
 | |
|   case WSREP_SERVICE_KEY_SHARED:    return wsrep::key::shared;
 | |
|   case WSREP_SERVICE_KEY_REFERENCE: return wsrep::key::reference;
 | |
|   case WSREP_SERVICE_KEY_UPDATE:    return wsrep::key::update;
 | |
|   case WSREP_SERVICE_KEY_EXCLUSIVE: return wsrep::key::exclusive;
 | |
|   }
 | |
|   return wsrep::key::exclusive;
 | |
| }
 | |
| 
 | |
| extern "C" int wsrep_thd_append_key(THD *thd,
 | |
|                                     const struct wsrep_key* key,
 | |
|                                     int n_keys,
 | |
|                                     enum Wsrep_service_key_type key_type)
 | |
| {
 | |
|   Wsrep_client_state& client_state(thd->wsrep_cs());
 | |
|   DBUG_ASSERT(client_state.transaction().active());
 | |
|   int ret= 0;
 | |
|   for (int i= 0; i < n_keys && ret == 0; ++i)
 | |
|   {
 | |
|     wsrep::key wsrep_key(map_key_type(key_type));
 | |
|     for (size_t kp= 0; kp < key[i].key_parts_num; ++kp)
 | |
|     {
 | |
|       wsrep_key.append_key_part(key[i].key_parts[kp].ptr, key[i].key_parts[kp].len);
 | |
|     }
 | |
|     ret= client_state.append_key(wsrep_key);
 | |
|   }
 | |
|   /*
 | |
|     In case of `wsrep_gtid_mode` when WS will be replicated, we need to set
 | |
|     `server_id` for events that are going to be written in IO, and in case of
 | |
|     manual SET gtid_seq_no=X we are ignoring value.
 | |
|    */
 | |
|   if (!ret && wsrep_gtid_mode && !thd->slave_thread && !wsrep_thd_is_applying(thd))
 | |
|   {
 | |
|     thd->variables.server_id= wsrep_gtid_server.server_id;
 | |
|     thd->variables.gtid_seq_no= 0;
 | |
|   }
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| extern "C" void wsrep_commit_ordered(THD *thd)
 | |
| {
 | |
|   if (wsrep_is_active(thd) &&
 | |
|       (thd->wsrep_trx().state() == wsrep::transaction::s_committing ||
 | |
|        thd->wsrep_trx().state() == wsrep::transaction::s_ordered_commit))
 | |
|   {
 | |
|     wsrep_gtid_server.signal_waiters(thd->wsrep_current_gtid_seqno, false);
 | |
|     if (wsrep_thd_is_local(thd))
 | |
|     {
 | |
|       thd->wsrep_last_written_gtid_seqno= thd->wsrep_current_gtid_seqno;
 | |
|     }
 | |
|     if (thd->wsrep_trx().state() != wsrep::transaction::s_ordered_commit &&
 | |
|         !wsrep_commit_will_write_binlog(thd))
 | |
|     {
 | |
|       DEBUG_SYNC(thd, "before_wsrep_ordered_commit");
 | |
|       thd->wsrep_cs().ordered_commit();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_thd_has_ignored_error(const THD *thd)
 | |
| {
 | |
|   return thd->wsrep_has_ignored_error;
 | |
| }
 | |
| 
 | |
| extern "C" void wsrep_thd_set_ignored_error(THD *thd, my_bool val)
 | |
| {
 | |
|   thd->wsrep_has_ignored_error= val;
 | |
| }
 | |
| 
 | |
| extern "C" ulong wsrep_OSU_method_get(const MYSQL_THD thd)
 | |
| {
 | |
|   if (thd)
 | |
|     return(thd->variables.wsrep_OSU_method);
 | |
|   else
 | |
|     return(global_system_variables.wsrep_OSU_method);
 | |
| }
 | |
| 
 | |
| extern "C" void wsrep_report_bf_lock_wait(const THD *thd,
 | |
|                                           unsigned long long trx_id)
 | |
| {
 | |
|   if (thd)
 | |
|   {
 | |
|     WSREP_ERROR("Thread %s trx_id: %llu thread: %ld "
 | |
|                 "seqno: %lld client_state: %s client_mode: %s transaction_mode: %s "
 | |
|                 "applier: %d toi: %d local: %d "
 | |
|                 "query: %s",
 | |
|                 wsrep_thd_is_BF(thd, false) ? "BF" : "normal",
 | |
|                 trx_id,
 | |
|                 thd_get_thread_id(thd),
 | |
|                 wsrep_thd_trx_seqno(thd),
 | |
|                 wsrep_thd_client_state_str(thd),
 | |
|                 wsrep_thd_client_mode_str(thd),
 | |
|                 wsrep_thd_transaction_state_str(thd),
 | |
|                 wsrep_thd_is_applying(thd),
 | |
|                 wsrep_thd_is_toi(thd),
 | |
|                 wsrep_thd_is_local(thd),
 | |
|                 wsrep_thd_query(thd));
 | |
|   }
 | |
| }
 | |
| 
 | |
| extern "C" void  wsrep_thd_set_PA_unsafe(THD *thd)
 | |
| {
 | |
|   if (thd && thd->wsrep_cs().mark_transaction_pa_unsafe())
 | |
|   {
 | |
|     WSREP_DEBUG("session does not have active transaction, can not mark as PA unsafe");
 | |
|   }
 | |
| }
 | |
| 
 | |
| extern "C" uint32 wsrep_get_domain_id()
 | |
| {
 | |
|   return wsrep_gtid_domain_id;
 | |
| }
 | |
| 
 | |
| extern "C" my_bool wsrep_thd_is_local_transaction(const THD *thd)
 | |
| {
 | |
|   return (wsrep_thd_is_local(thd) &&
 | |
| 	  thd->wsrep_cs().transaction().active());
 | |
| }
 | 
