diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
commit | a175314c3e5827eb193872241446f2f8f5c9d33c (patch) | |
tree | cd3d60ca99ae00829c52a6ca79150a5b6e62528b /sql/mdl.h | |
parent | Initial commit. (diff) | |
download | mariadb-10.5-upstream.tar.xz mariadb-10.5-upstream.zip |
Adding upstream version 1:10.5.12.upstream/1%10.5.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sql/mdl.h')
-rw-r--r-- | sql/mdl.h | 1133 |
1 files changed, 1133 insertions, 0 deletions
diff --git a/sql/mdl.h b/sql/mdl.h new file mode 100644 index 00000000..31ac4e81 --- /dev/null +++ b/sql/mdl.h @@ -0,0 +1,1133 @@ +#ifndef MDL_H +#define MDL_H +/* Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2020, 2021, MariaDB + + 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, + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ + +#include "sql_plist.h" +#include "ilist.h" +#include <my_sys.h> +#include <m_string.h> +#include <mysql_com.h> +#include <lf.h> + +class THD; + +class MDL_context; +class MDL_lock; +class MDL_ticket; +bool ok_for_lower_case_names(const char *name); + +typedef unsigned short mdl_bitmap_t; +#define MDL_BIT(A) static_cast<mdl_bitmap_t>(1U << A) + + +/** + @def ENTER_COND(C, M, S, O) + Start a wait on a condition. + @param C the condition to wait on + @param M the associated mutex + @param S the new stage to enter + @param O the previous stage + @sa EXIT_COND(). +*/ +#define ENTER_COND(C, M, S, O) enter_cond(C, M, S, O, __func__, __FILE__, __LINE__) + +/** + @def EXIT_COND(S) + End a wait on a condition + @param S the new stage to enter +*/ +#define EXIT_COND(S) exit_cond(S, __func__, __FILE__, __LINE__) + +/** + An interface to separate the MDL module from the THD, and the rest of the + server code. + */ + +class MDL_context_owner +{ +public: + virtual ~MDL_context_owner() {} + + /** + Enter a condition wait. + For @c enter_cond() / @c exit_cond() to work the mutex must be held before + @c enter_cond(); this mutex is then released by @c exit_cond(). + Usage must be: lock mutex; enter_cond(); your code; exit_cond(). + @param cond the condition to wait on + @param mutex the associated mutex + @param [in] stage the stage to enter, or NULL + @param [out] old_stage the previous stage, or NULL + @param src_function function name of the caller + @param src_file file name of the caller + @param src_line line number of the caller + @sa ENTER_COND(), THD::enter_cond() + @sa EXIT_COND(), THD::exit_cond() + */ + virtual void enter_cond(mysql_cond_t *cond, mysql_mutex_t *mutex, + const PSI_stage_info *stage, PSI_stage_info *old_stage, + const char *src_function, const char *src_file, + int src_line) = 0; + + /** + @def EXIT_COND(S) + End a wait on a condition + @param [in] stage the new stage to enter + @param src_function function name of the caller + @param src_file file name of the caller + @param src_line line number of the caller + @sa ENTER_COND(), THD::enter_cond() + @sa EXIT_COND(), THD::exit_cond() + */ + virtual void exit_cond(const PSI_stage_info *stage, + const char *src_function, const char *src_file, + int src_line) = 0; + /** + Has the owner thread been killed? + */ + virtual int is_killed() = 0; + + /** + This one is only used for DEBUG_SYNC. + (Do not use it to peek/poke into other parts of THD.) + */ + virtual THD* get_thd() = 0; + + /** + @see THD::notify_shared_lock() + */ + virtual bool notify_shared_lock(MDL_context_owner *in_use, + bool needs_thr_lock_abort) = 0; +}; + +/** + Type of metadata lock request. + + @sa Comments for MDL_object_lock::can_grant_lock() and + MDL_scoped_lock::can_grant_lock() for details. + + Scoped locks are database (or schema) locks. + The object locks are for tables, triggers etc. +*/ + +enum enum_mdl_type { + /* This means that the MDL_request is not initialized */ + MDL_NOT_INITIALIZED= -1, + /* + An intention exclusive metadata lock (IX). Used only for scoped locks. + Owner of this type of lock can acquire upgradable exclusive locks on + individual objects. + Compatible with other IX locks, but is incompatible with scoped S and + X locks. + IX lock is taken in SCHEMA namespace when we intend to modify + object metadata. Object may refer table, stored procedure, trigger, + view/etc. + */ + MDL_INTENTION_EXCLUSIVE= 0, + /* + A shared metadata lock (S). + To be used in cases when we are interested in object metadata only + and there is no intention to access object data (e.g. for stored + routines or during preparing prepared statements). + We also mis-use this type of lock for open HANDLERs, since lock + acquired by this statement has to be compatible with lock acquired + by LOCK TABLES ... WRITE statement, i.e. SNRW (We can't get by by + acquiring S lock at HANDLER ... OPEN time and upgrading it to SR + lock for HANDLER ... READ as it doesn't solve problem with need + to abort DML statements which wait on table level lock while having + open HANDLER in the same connection). + To avoid deadlock which may occur when SNRW lock is being upgraded to + X lock for table on which there is an active S lock which is owned by + thread which waits in its turn for table-level lock owned by thread + performing upgrade we have to use thr_abort_locks_for_thread() + facility in such situation. + This problem does not arise for locks on stored routines as we don't + use SNRW locks for them. It also does not arise when S locks are used + during PREPARE calls as table-level locks are not acquired in this + case. + This lock is taken for global read lock, when caching a stored + procedure in memory for the duration of the transaction and for + tables used by prepared statements. + */ + MDL_SHARED, + /* + A high priority shared metadata lock. + Used for cases when there is no intention to access object data (i.e. + data in the table). + "High priority" means that, unlike other shared locks, it is granted + ignoring pending requests for exclusive locks. Intended for use in + cases when we only need to access metadata and not data, e.g. when + filling an INFORMATION_SCHEMA table. + Since SH lock is compatible with SNRW lock, the connection that + holds SH lock lock should not try to acquire any kind of table-level + or row-level lock, as this can lead to a deadlock. Moreover, after + acquiring SH lock, the connection should not wait for any other + resource, as it might cause starvation for X locks and a potential + deadlock during upgrade of SNW or SNRW to X lock (e.g. if the + upgrading connection holds the resource that is being waited for). + */ + MDL_SHARED_HIGH_PRIO, + /* + A shared metadata lock (SR) for cases when there is an intention to read + data from table. + A connection holding this kind of lock can read table metadata and read + table data (after acquiring appropriate table and row-level locks). + This means that one can only acquire TL_READ, TL_READ_NO_INSERT, and + similar table-level locks on table if one holds SR MDL lock on it. + To be used for tables in SELECTs, subqueries, and LOCK TABLE ... READ + statements. + */ + MDL_SHARED_READ, + /* + A shared metadata lock (SW) for cases when there is an intention to modify + (and not just read) data in the table. + A connection holding SW lock can read table metadata and modify or read + table data (after acquiring appropriate table and row-level locks). + To be used for tables to be modified by INSERT, UPDATE, DELETE + statements, but not LOCK TABLE ... WRITE or DDL). Also taken by + SELECT ... FOR UPDATE. + */ + MDL_SHARED_WRITE, + /* + An upgradable shared metadata lock for cases when there is an + intention to modify (and not just read) data in the table. + Can be upgraded to MDL_SHARED_NO_WRITE and MDL_EXCLUSIVE. + A connection holding SU lock can read table metadata and modify or read + table data (after acquiring appropriate table and row-level locks). + To be used for the first phase of ALTER TABLE. + */ + MDL_SHARED_UPGRADABLE, + /* + A shared metadata lock for cases when we need to read data from table + and block all concurrent modifications to it (for both data and metadata). + Used by LOCK TABLES READ statement. + */ + MDL_SHARED_READ_ONLY, + /* + An upgradable shared metadata lock which blocks all attempts to update + table data, allowing reads. + A connection holding this kind of lock can read table metadata and read + table data. + Can be upgraded to X metadata lock. + Note, that since this type of lock is not compatible with SNRW or SW + lock types, acquiring appropriate engine-level locks for reading + (TL_READ* for MyISAM, shared row locks in InnoDB) should be + contention-free. + To be used for the first phase of ALTER TABLE, when copying data between + tables, to allow concurrent SELECTs from the table, but not UPDATEs. + */ + MDL_SHARED_NO_WRITE, + /* + An upgradable shared metadata lock which allows other connections + to access table metadata, but not data. + It blocks all attempts to read or update table data, while allowing + INFORMATION_SCHEMA and SHOW queries. + A connection holding this kind of lock can read table metadata modify and + read table data. + Can be upgraded to X metadata lock. + To be used for LOCK TABLES WRITE statement. + Not compatible with any other lock type except S and SH. + */ + MDL_SHARED_NO_READ_WRITE, + /* + An exclusive metadata lock (X). + A connection holding this lock can modify both table's metadata and data. + No other type of metadata lock can be granted while this lock is held. + To be used for CREATE/DROP/RENAME TABLE statements and for execution of + certain phases of other DDL statements. + */ + MDL_EXCLUSIVE, + /* This should be the last !!! */ + MDL_TYPE_END +}; + + +/** Backup locks */ + +/** + Block concurrent backup +*/ +#define MDL_BACKUP_START enum_mdl_type(0) +/** + Block new write requests to non transactional tables +*/ +#define MDL_BACKUP_FLUSH enum_mdl_type(1) +/** + In addition to previous locks, blocks running requests to non trans tables + Used to wait until all DML usage of on trans tables are finished +*/ +#define MDL_BACKUP_WAIT_FLUSH enum_mdl_type(2) +/** + In addition to previous locks, blocks new DDL's from starting +*/ +#define MDL_BACKUP_WAIT_DDL enum_mdl_type(3) +/** + In addition to previous locks, blocks commits +*/ +#define MDL_BACKUP_WAIT_COMMIT enum_mdl_type(4) + +/** + Blocks (or is blocked by) statements that intend to modify data. Acquired + before commit lock by FLUSH TABLES WITH READ LOCK. +*/ +#define MDL_BACKUP_FTWRL1 enum_mdl_type(5) + +/** + Blocks (or is blocked by) commits. Acquired after global read lock by + FLUSH TABLES WITH READ LOCK. +*/ +#define MDL_BACKUP_FTWRL2 enum_mdl_type(6) + +#define MDL_BACKUP_DML enum_mdl_type(7) +#define MDL_BACKUP_TRANS_DML enum_mdl_type(8) +#define MDL_BACKUP_SYS_DML enum_mdl_type(9) + +/** + Must be acquired by DDL statements that intend to modify data. + Currently it's also used for LOCK TABLES. +*/ +#define MDL_BACKUP_DDL enum_mdl_type(10) + +/** + Blocks new DDL's. Used by backup code to enable DDL logging +*/ +#define MDL_BACKUP_BLOCK_DDL enum_mdl_type(11) + +/* + Statement is modifying data, but will not block MDL_BACKUP_DDL or earlier + BACKUP stages. + ALTER TABLE is started with MDL_BACKUP_DDL, but changed to + MDL_BACKUP_ALTER_COPY while alter table is copying or modifing data. +*/ + +#define MDL_BACKUP_ALTER_COPY enum_mdl_type(12) + +/** + Must be acquired during commit. +*/ +#define MDL_BACKUP_COMMIT enum_mdl_type(13) +#define MDL_BACKUP_END enum_mdl_type(14) + + +/** Duration of metadata lock. */ + +enum enum_mdl_duration { + /** + Locks with statement duration are automatically released at the end + of statement or transaction. + */ + MDL_STATEMENT= 0, + /** + Locks with transaction duration are automatically released at the end + of transaction. + */ + MDL_TRANSACTION, + /** + Locks with explicit duration survive the end of statement and transaction. + They have to be released explicitly by calling MDL_context::release_lock(). + */ + MDL_EXPLICIT, + /* This should be the last ! */ + MDL_DURATION_END }; + + +/** Maximal length of key for metadata locking subsystem. */ +#define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1) + + +/** + Metadata lock object key. + + A lock is requested or granted based on a fully qualified name and type. + E.g. They key for a table consists of <0 (=table)>+<database>+<table name>. + Elsewhere in the comments this triple will be referred to simply as "key" + or "name". +*/ + +struct MDL_key +{ +public: +#ifdef HAVE_PSI_INTERFACE + static void init_psi_keys(); +#endif + + /** + Object namespaces. + Sic: when adding a new member to this enum make sure to + update m_namespace_to_wait_state_name array in mdl.cc and + metadata_lock_info_lock_name in metadata_lock_info.cc! + + Different types of objects exist in different namespaces + - SCHEMA is for databases (to protect against DROP DATABASE) + - TABLE is for tables and views. + - BACKUP is for locking DML, DDL and COMMIT's during BACKUP STAGES + - FUNCTION is for stored functions. + - PROCEDURE is for stored procedures. + - TRIGGER is for triggers. + - EVENT is for event scheduler events + Note that although there isn't metadata locking on triggers, + it's necessary to have a separate namespace for them since + MDL_key is also used outside of the MDL subsystem. + */ + enum enum_mdl_namespace { BACKUP=0, + SCHEMA, + TABLE, + FUNCTION, + PROCEDURE, + PACKAGE_BODY, + TRIGGER, + EVENT, + USER_LOCK, /* user level locks. */ + /* This should be the last ! */ + NAMESPACE_END }; + + const uchar *ptr() const { return (uchar*) m_ptr; } + uint length() const { return m_length; } + + const char *db_name() const { return m_ptr + 1; } + uint db_name_length() const { return m_db_name_length; } + + const char *name() const { return m_ptr + m_db_name_length + 2; } + uint name_length() const { return m_length - m_db_name_length - 3; } + + enum_mdl_namespace mdl_namespace() const + { return (enum_mdl_namespace)(m_ptr[0]); } + + /** + Construct a metadata lock key from a triplet (mdl_namespace, + database and name). + + @remark The key for a table is <mdl_namespace>+<database name>+<table name> + + @param mdl_namespace Id of namespace of object to be locked + @param db Name of database to which the object belongs + @param name Name of of the object + @param key Where to store the the MDL key. + */ + void mdl_key_init(enum_mdl_namespace mdl_namespace_arg, + const char *db, const char *name_arg) + { + m_ptr[0]= (char) mdl_namespace_arg; + /* + It is responsibility of caller to ensure that db and object names + are not longer than NAME_LEN. Still we play safe and try to avoid + buffer overruns. + */ + DBUG_ASSERT(strlen(db) <= NAME_LEN); + DBUG_ASSERT(strlen(name_arg) <= NAME_LEN); + m_db_name_length= static_cast<uint16>(strmake(m_ptr + 1, db, NAME_LEN) - + m_ptr - 1); + m_length= static_cast<uint16>(strmake(m_ptr + m_db_name_length + 2, + name_arg, + NAME_LEN) - m_ptr + 1); + m_hash_value= my_hash_sort(&my_charset_bin, (uchar*) m_ptr + 1, + m_length - 1); + DBUG_SLOW_ASSERT(mdl_namespace_arg == USER_LOCK || ok_for_lower_case_names(db)); + } + void mdl_key_init(const MDL_key *rhs) + { + memcpy(m_ptr, rhs->m_ptr, rhs->m_length); + m_length= rhs->m_length; + m_db_name_length= rhs->m_db_name_length; + m_hash_value= rhs->m_hash_value; + } + bool is_equal(const MDL_key *rhs) const + { + return (m_length == rhs->m_length && + memcmp(m_ptr, rhs->m_ptr, m_length) == 0); + } + /** + Compare two MDL keys lexicographically. + */ + int cmp(const MDL_key *rhs) const + { + /* + The key buffer is always '\0'-terminated. Since key + character set is utf-8, we can safely assume that no + character starts with a zero byte. + */ + return memcmp(m_ptr, rhs->m_ptr, MY_MIN(m_length, rhs->m_length)); + } + + MDL_key(const MDL_key *rhs) + { + mdl_key_init(rhs); + } + MDL_key(enum_mdl_namespace namespace_arg, + const char *db_arg, const char *name_arg) + { + mdl_key_init(namespace_arg, db_arg, name_arg); + } + MDL_key() {} /* To use when part of MDL_request. */ + + /** + Get thread state name to be used in case when we have to + wait on resource identified by key. + */ + const PSI_stage_info * get_wait_state_name() const + { + return & m_namespace_to_wait_state_name[(int)mdl_namespace()]; + } + my_hash_value_type hash_value() const + { + return m_hash_value + mdl_namespace(); + } + my_hash_value_type tc_hash_value() const + { + return m_hash_value; + } + +private: + uint16 m_length; + uint16 m_db_name_length; + my_hash_value_type m_hash_value; + char m_ptr[MAX_MDLKEY_LENGTH]; + static PSI_stage_info m_namespace_to_wait_state_name[NAMESPACE_END]; +private: + MDL_key(const MDL_key &); /* not implemented */ + MDL_key &operator=(const MDL_key &); /* not implemented */ + friend my_hash_value_type mdl_hash_function(CHARSET_INFO *, + const uchar *, size_t); +}; + + +/** + A pending metadata lock request. + + A lock request and a granted metadata lock are represented by + different classes because they have different allocation + sites and hence different lifetimes. The allocation of lock requests is + controlled from outside of the MDL subsystem, while allocation of granted + locks (tickets) is controlled within the MDL subsystem. + + MDL_request is a C structure, you don't need to call a constructor + or destructor for it. +*/ + +class MDL_request +{ +public: + /** Type of metadata lock. */ + enum enum_mdl_type type; + /** Duration for requested lock. */ + enum enum_mdl_duration duration; + + /** + Pointers for participating in the list of lock requests for this context. + */ + MDL_request *next_in_list; + MDL_request **prev_in_list; + /** + Pointer to the lock ticket object for this lock request. + Valid only if this lock request is satisfied. + */ + MDL_ticket *ticket; + + /** A lock is requested based on a fully qualified name and type. */ + MDL_key key; + + const char *m_src_file; + uint m_src_line; + +public: + + static void *operator new(size_t size, MEM_ROOT *mem_root) throw () + { return alloc_root(mem_root, size); } + static void operator delete(void *, MEM_ROOT *) {} + + void init_with_source(MDL_key::enum_mdl_namespace namespace_arg, + const char *db_arg, const char *name_arg, + enum_mdl_type mdl_type_arg, + enum_mdl_duration mdl_duration_arg, + const char *src_file, uint src_line); + void init_by_key_with_source(const MDL_key *key_arg, enum_mdl_type mdl_type_arg, + enum_mdl_duration mdl_duration_arg, + const char *src_file, uint src_line); + /** Set type of lock request. Can be only applied to pending locks. */ + inline void set_type(enum_mdl_type type_arg) + { + DBUG_ASSERT(ticket == NULL); + type= type_arg; + } + void move_from(MDL_request &from) + { + type= from.type; + duration= from.duration; + ticket= from.ticket; + next_in_list= from.next_in_list; + prev_in_list= from.prev_in_list; + key.mdl_key_init(&from.key); + from.ticket= NULL; // that's what "move" means + } + + /** + Is this a request for a lock which allow data to be updated? + + @note This method returns true for MDL_SHARED_UPGRADABLE type of + lock. Even though this type of lock doesn't allow updates + it will always be upgraded to one that does. + */ + bool is_write_lock_request() const + { + return (type >= MDL_SHARED_WRITE && + type != MDL_SHARED_READ_ONLY); + } + + /* + This is to work around the ugliness of TABLE_LIST + compiler-generated assignment operator. It is currently used + in several places to quickly copy "most" of the members of the + table list. These places currently never assume that the mdl + request is carried over to the new TABLE_LIST, or shared + between lists. + + This method does not initialize the instance being assigned! + Use of init() for initialization after this assignment operator + is mandatory. Can only be used before the request has been + granted. + */ + MDL_request& operator=(const MDL_request &) + { + type= MDL_NOT_INITIALIZED; + ticket= NULL; + /* Do nothing, in particular, don't try to copy the key. */ + return *this; + } + /* Another piece of ugliness for TABLE_LIST constructor */ + MDL_request(): type(MDL_NOT_INITIALIZED), ticket(NULL) {} + + MDL_request(const MDL_request *rhs) + :type(rhs->type), + duration(rhs->duration), + ticket(NULL), + key(&rhs->key) + {} +}; + + +typedef void (*mdl_cached_object_release_hook)(void *); + +#define MDL_REQUEST_INIT(R, P1, P2, P3, P4, P5) \ + (*R).init_with_source(P1, P2, P3, P4, P5, __FILE__, __LINE__) + +#define MDL_REQUEST_INIT_BY_KEY(R, P1, P2, P3) \ + (*R).init_by_key_with_source(P1, P2, P3, __FILE__, __LINE__) + + +/** + An abstract class for inspection of a connected + subgraph of the wait-for graph. +*/ + +class MDL_wait_for_graph_visitor +{ +public: + virtual bool enter_node(MDL_context *node) = 0; + virtual void leave_node(MDL_context *node) = 0; + + virtual bool inspect_edge(MDL_context *dest) = 0; + virtual ~MDL_wait_for_graph_visitor(); + MDL_wait_for_graph_visitor() {} +}; + +/** + Abstract class representing an edge in the waiters graph + to be traversed by deadlock detection algorithm. +*/ + +class MDL_wait_for_subgraph +{ +public: + virtual ~MDL_wait_for_subgraph(); + + /** + Accept a wait-for graph visitor to inspect the node + this edge is leading to. + */ + virtual bool accept_visitor(MDL_wait_for_graph_visitor *gvisitor) = 0; + + enum enum_deadlock_weight + { + DEADLOCK_WEIGHT_FTWRL1= 0, + DEADLOCK_WEIGHT_DML= 1, + DEADLOCK_WEIGHT_DDL= 100 + }; + /* A helper used to determine which lock request should be aborted. */ + virtual uint get_deadlock_weight() const = 0; +}; + + +/** + A granted metadata lock. + + @warning MDL_ticket members are private to the MDL subsystem. + + @note Multiple shared locks on a same object are represented by a + single ticket. The same does not apply for other lock types. + + @note There are two groups of MDL_ticket members: + - "Externally accessible". These members can be accessed from + threads/contexts different than ticket owner in cases when + ticket participates in some list of granted or waiting tickets + for a lock. Therefore one should change these members before + including then to waiting/granted lists or while holding lock + protecting those lists. + - "Context private". Such members are private to thread/context + owning this ticket. I.e. they should not be accessed from other + threads/contexts. +*/ + +class MDL_ticket : public MDL_wait_for_subgraph, public ilist_node<> +{ +public: + /** + Pointers for participating in the list of lock requests for this context. + Context private. + */ + MDL_ticket *next_in_context; + MDL_ticket **prev_in_context; +public: +#ifdef WITH_WSREP + void wsrep_report(bool debug) const; +#endif /* WITH_WSREP */ + bool has_pending_conflicting_lock() const; + + MDL_context *get_ctx() const { return m_ctx; } + bool is_upgradable_or_exclusive() const + { + return m_type == MDL_SHARED_UPGRADABLE || + m_type == MDL_SHARED_NO_WRITE || + m_type == MDL_SHARED_NO_READ_WRITE || + m_type == MDL_EXCLUSIVE; + } + enum_mdl_type get_type() const { return m_type; } + const LEX_STRING *get_type_name() const; + const LEX_STRING *get_type_name(enum_mdl_type type) const; + MDL_lock *get_lock() const { return m_lock; } + MDL_key *get_key() const; + void downgrade_lock(enum_mdl_type type); + + bool has_stronger_or_equal_type(enum_mdl_type type) const; + + bool is_incompatible_when_granted(enum_mdl_type type) const; + bool is_incompatible_when_waiting(enum_mdl_type type) const; + + /** Implement MDL_wait_for_subgraph interface. */ + virtual bool accept_visitor(MDL_wait_for_graph_visitor *dvisitor); + virtual uint get_deadlock_weight() const; + /** + Status of lock request represented by the ticket as reflected in P_S. + */ + enum enum_psi_status { PENDING = 0, GRANTED, + PRE_ACQUIRE_NOTIFY, POST_RELEASE_NOTIFY }; +private: + friend class MDL_context; + + MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg +#ifndef DBUG_OFF + , enum_mdl_duration duration_arg +#endif + ) + : m_type(type_arg), +#ifndef DBUG_OFF + m_duration(duration_arg), +#endif + m_ctx(ctx_arg), + m_lock(NULL), + m_psi(NULL) + {} + + virtual ~MDL_ticket() + { + DBUG_ASSERT(m_psi == NULL); + } + + static MDL_ticket *create(MDL_context *ctx_arg, enum_mdl_type type_arg +#ifndef DBUG_OFF + , enum_mdl_duration duration_arg +#endif + ); + static void destroy(MDL_ticket *ticket); +private: + /** Type of metadata lock. Externally accessible. */ + enum enum_mdl_type m_type; +#ifndef DBUG_OFF + /** + Duration of lock represented by this ticket. + Context private. Debug-only. + */ + enum_mdl_duration m_duration; +#endif + /** + Context of the owner of the metadata lock ticket. Externally accessible. + */ + MDL_context *m_ctx; + + /** + Pointer to the lock object for this lock ticket. Externally accessible. + */ + MDL_lock *m_lock; + + PSI_metadata_lock *m_psi; + +private: + MDL_ticket(const MDL_ticket &); /* not implemented */ + MDL_ticket &operator=(const MDL_ticket &); /* not implemented */ +}; + + +/** + Savepoint for MDL context. + + Doesn't include metadata locks with explicit duration as + they are not released during rollback to savepoint. +*/ + +class MDL_savepoint +{ +public: + MDL_savepoint() {}; + +private: + MDL_savepoint(MDL_ticket *stmt_ticket, MDL_ticket *trans_ticket) + : m_stmt_ticket(stmt_ticket), m_trans_ticket(trans_ticket) + {} + + friend class MDL_context; + +private: + /** + Pointer to last lock with statement duration which was taken + before creation of savepoint. + */ + MDL_ticket *m_stmt_ticket; + /** + Pointer to last lock with transaction duration which was taken + before creation of savepoint. + */ + MDL_ticket *m_trans_ticket; +}; + + +/** + A reliable way to wait on an MDL lock. +*/ + +class MDL_wait +{ +public: + MDL_wait(); + ~MDL_wait(); + + enum enum_wait_status { EMPTY = 0, GRANTED, VICTIM, TIMEOUT, KILLED }; + + bool set_status(enum_wait_status result_arg); + enum_wait_status get_status(); + void reset_status(); + enum_wait_status timed_wait(MDL_context_owner *owner, + struct timespec *abs_timeout, + bool signal_timeout, + const PSI_stage_info *wait_state_name); +private: + /** + Condvar which is used for waiting until this context's pending + request can be satisfied or this thread has to perform actions + to resolve a potential deadlock (we subscribe to such + notification by adding a ticket corresponding to the request + to an appropriate queue of waiters). + */ + mysql_mutex_t m_LOCK_wait_status; + mysql_cond_t m_COND_wait_status; + enum_wait_status m_wait_status; +}; + + +typedef I_P_List<MDL_request, I_P_List_adapter<MDL_request, + &MDL_request::next_in_list, + &MDL_request::prev_in_list>, + I_P_List_counter> + MDL_request_list; + +/** + Context of the owner of metadata locks. I.e. each server + connection has such a context. +*/ + +class MDL_context +{ +public: + typedef I_P_List<MDL_ticket, + I_P_List_adapter<MDL_ticket, + &MDL_ticket::next_in_context, + &MDL_ticket::prev_in_context> > + Ticket_list; + + typedef Ticket_list::Iterator Ticket_iterator; + + MDL_context(); + void destroy(); + + bool try_acquire_lock(MDL_request *mdl_request); + bool acquire_lock(MDL_request *mdl_request, double lock_wait_timeout); + bool acquire_locks(MDL_request_list *requests, double lock_wait_timeout); + bool upgrade_shared_lock(MDL_ticket *mdl_ticket, + enum_mdl_type new_type, + double lock_wait_timeout); + + bool clone_ticket(MDL_request *mdl_request); + + void release_all_locks_for_name(MDL_ticket *ticket); + void release_lock(MDL_ticket *ticket); + + bool is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, + const char *db, const char *name, + enum_mdl_type mdl_type); + unsigned long get_lock_owner(MDL_key *mdl_key); + + bool has_lock(const MDL_savepoint &mdl_savepoint, MDL_ticket *mdl_ticket); + + inline bool has_locks() const + { + return !(m_tickets[MDL_STATEMENT].is_empty() && + m_tickets[MDL_TRANSACTION].is_empty() && + m_tickets[MDL_EXPLICIT].is_empty()); + } + bool has_explicit_locks() const + { + return !m_tickets[MDL_EXPLICIT].is_empty(); + } + inline bool has_transactional_locks() const + { + return !m_tickets[MDL_TRANSACTION].is_empty(); + } + + MDL_savepoint mdl_savepoint() + { + return MDL_savepoint(m_tickets[MDL_STATEMENT].front(), + m_tickets[MDL_TRANSACTION].front()); + } + + void set_explicit_duration_for_all_locks(); + void set_transaction_duration_for_all_locks(); + void set_lock_duration(MDL_ticket *mdl_ticket, enum_mdl_duration duration); + + void release_statement_locks(); + void release_transactional_locks(THD *thd); + void release_explicit_locks(); + void rollback_to_savepoint(const MDL_savepoint &mdl_savepoint); + + MDL_context_owner *get_owner() { return m_owner; } + + /** @pre Only valid if we started waiting for lock. */ + inline uint get_deadlock_weight() const + { return m_waiting_for->get_deadlock_weight() + m_deadlock_overweight; } + void inc_deadlock_overweight() { m_deadlock_overweight++; } + /** + Post signal to the context (and wake it up if necessary). + + @retval FALSE - Success, signal was posted. + @retval TRUE - Failure, signal was not posted since context + already has received some signal or closed + signal slot. + */ + void init(MDL_context_owner *arg) { m_owner= arg; } + + void set_needs_thr_lock_abort(bool needs_thr_lock_abort) + { + /* + @note In theory, this member should be modified under protection + of some lock since it can be accessed from different threads. + In practice, this is not necessary as code which reads this + value and so might miss the fact that value was changed will + always re-try reading it after small timeout and therefore + will see the new value eventually. + */ + m_needs_thr_lock_abort= needs_thr_lock_abort; + } + bool get_needs_thr_lock_abort() const + { + return m_needs_thr_lock_abort; + } +public: + /** + If our request for a lock is scheduled, or aborted by the deadlock + detector, the result is recorded in this class. + */ + MDL_wait m_wait; +private: + /** + Lists of all MDL tickets acquired by this connection. + + Lists of MDL tickets: + --------------------- + The entire set of locks acquired by a connection can be separated + in three subsets according to their duration: locks released at + the end of statement, at the end of transaction and locks are + released explicitly. + + Statement and transactional locks are locks with automatic scope. + They are accumulated in the course of a transaction, and released + either at the end of uppermost statement (for statement locks) or + on COMMIT, ROLLBACK or ROLLBACK TO SAVEPOINT (for transactional + locks). They must not be (and never are) released manually, + i.e. with release_lock() call. + + Tickets with explicit duration are taken for locks that span + multiple transactions or savepoints. + These are: HANDLER SQL locks (HANDLER SQL is + transaction-agnostic), LOCK TABLES locks (you can COMMIT/etc + under LOCK TABLES, and the locked tables stay locked), user level + locks (GET_LOCK()/RELEASE_LOCK() functions) and + locks implementing "global read lock". + + Statement/transactional locks are always prepended to the + beginning of the appropriate list. In other words, they are + stored in reverse temporal order. Thus, when we rollback to + a savepoint, we start popping and releasing tickets from the + front until we reach the last ticket acquired after the savepoint. + + Locks with explicit duration are not stored in any + particular order, and among each other can be split into + four sets: + + [LOCK TABLES locks] [USER locks] [HANDLER locks] [GLOBAL READ LOCK locks] + + The following is known about these sets: + + * GLOBAL READ LOCK locks are always stored last. + This is because one can't say SET GLOBAL read_only=1 or + FLUSH TABLES WITH READ LOCK if one has locked tables. One can, + however, LOCK TABLES after having entered the read only mode. + Note, that subsequent LOCK TABLES statement will unlock the previous + set of tables, but not the GRL! + There are no HANDLER locks after GRL locks because + SET GLOBAL read_only performs a FLUSH TABLES WITH + READ LOCK internally, and FLUSH TABLES, in turn, implicitly + closes all open HANDLERs. + However, one can open a few HANDLERs after entering the + read only mode. + * LOCK TABLES locks include intention exclusive locks on + involved schemas and global intention exclusive lock. + */ + Ticket_list m_tickets[MDL_DURATION_END]; + MDL_context_owner *m_owner; + /** + TRUE - if for this context we will break protocol and try to + acquire table-level locks while having only S lock on + some table. + To avoid deadlocks which might occur during concurrent + upgrade of SNRW lock on such object to X lock we have to + abort waits for table-level locks for such connections. + FALSE - Otherwise. + */ + bool m_needs_thr_lock_abort; + + /** + Read-write lock protecting m_waiting_for member. + + @note The fact that this read-write lock prefers readers is + important as deadlock detector won't work correctly + otherwise. @sa Comment for MDL_lock::m_rwlock. + */ + mysql_prlock_t m_LOCK_waiting_for; + /** + Tell the deadlock detector what metadata lock or table + definition cache entry this session is waiting for. + In principle, this is redundant, as information can be found + by inspecting waiting queues, but we'd very much like it to be + readily available to the wait-for graph iterator. + */ + MDL_wait_for_subgraph *m_waiting_for; + LF_PINS *m_pins; + uint m_deadlock_overweight= 0; +private: + MDL_ticket *find_ticket(MDL_request *mdl_req, + enum_mdl_duration *duration); + void release_locks_stored_before(enum_mdl_duration duration, MDL_ticket *sentinel); + void release_lock(enum_mdl_duration duration, MDL_ticket *ticket); + bool try_acquire_lock_impl(MDL_request *mdl_request, + MDL_ticket **out_ticket); + bool fix_pins(); + +public: + THD *get_thd() const { return m_owner->get_thd(); } + bool has_explicit_locks(); + void find_deadlock(); + + ulong get_thread_id() const { return thd_get_thread_id(get_thd()); } + + bool visit_subgraph(MDL_wait_for_graph_visitor *dvisitor); + + /** Inform the deadlock detector there is an edge in the wait-for graph. */ + void will_wait_for(MDL_wait_for_subgraph *waiting_for_arg) + { + mysql_prlock_wrlock(&m_LOCK_waiting_for); + m_waiting_for= waiting_for_arg; + mysql_prlock_unlock(&m_LOCK_waiting_for); + } + + /** Remove the wait-for edge from the graph after we're done waiting. */ + void done_waiting_for() + { + mysql_prlock_wrlock(&m_LOCK_waiting_for); + m_waiting_for= NULL; + mysql_prlock_unlock(&m_LOCK_waiting_for); + } + void lock_deadlock_victim() + { + mysql_prlock_rdlock(&m_LOCK_waiting_for); + } + void unlock_deadlock_victim() + { + mysql_prlock_unlock(&m_LOCK_waiting_for); + } +private: + MDL_context(const MDL_context &rhs); /* not implemented */ + MDL_context &operator=(MDL_context &rhs); /* not implemented */ + + /* metadata_lock_info plugin */ + friend int i_s_metadata_lock_info_fill_row(MDL_ticket*, void*); +}; + + +void mdl_init(); +void mdl_destroy(); + +extern "C" unsigned long thd_get_thread_id(const MYSQL_THD thd); + +/** + Check if a connection in question is no longer connected. + + @details + Replication apply thread is always connected. Otherwise, + does a poll on the associated socket to check if the client + is gone. +*/ +extern "C" int thd_is_connected(MYSQL_THD thd); + + +/* + Metadata locking subsystem tries not to grant more than + max_write_lock_count high-prio, strong locks successively, + to avoid starving out weak, low-prio locks. +*/ +extern "C" ulong max_write_lock_count; + +typedef int (*mdl_iterator_callback)(MDL_ticket *ticket, void *arg, + bool granted); +extern MYSQL_PLUGIN_IMPORT +int mdl_iterate(mdl_iterator_callback callback, void *arg); +#endif /* MDL_H */ |