| Type of active | Request | scoped lock | type | IS(*) IX S X | ---------+------------------+ IS | + + + + | IX | + + - - | S | + - + - | X | + - - - | ======================================== | Pending | Request | scoped lock | type | IS(*) IX S X | ---------+-----------------+ IS | + + + + | IX | + + - - | S | + + + - | X | + + + + | Here: "+" -- means that request can be satisfied "-" -- means that request can't be satisfied and should wait
/** Check if ticket represents metadata lock of "stronger" or equal type than specified one. I.e. if metadata lock represented by ticket won't allow any of locks which are not allowed by specified type of lock. @return true if ticket has stronger or equal type false otherwise. */ bool MDL_ticket::has_stronger_or_equal_type(enum_mdl_type type) const { const MDL_lock::bitmap_t *granted_incompat_map = m_lock->incompatible_granted_types_bitmap(); return !(granted_incompat_map[type] & ~(granted_incompat_map[m_type])); }
表达式的写法有点绕,可以理解为,如果 type 类型与某种 m_type 类型兼容的 MDL 不兼容,那么 type 类型更强;否则 m_type 类型相同或更强。或者较弱的类型不兼容的 MDL 类型,较强的 MDL 都不兼容。
/** Array of increments for "unobtrusive" types of lock requests for per-object locks. @sa MDL_lock::get_unobtrusive_lock_increment(). For per-object locks: - "unobtrusive" types: S, SH, SR and SW - "obtrusive" types: SU, SRO, SNW, SNRW, X Number of locks acquired using "fast path" are encoded in the following bits of MDL_lock::m_fast_path_state: - bits 0 .. 19 - S and SH (we don't differentiate them once acquired) - bits 20 .. 39 - SR - bits 40 .. 59 - SW and SWLP (we don't differentiate them once acquired) Overflow is not an issue as we are unlikely to support more than 2^20 - 1 concurrent connections in foreseeable future. This encoding defines the below contents of increment array. */ {0, 1, 1, 1ULL << 20, 1ULL << 40, 1ULL << 40, 0, 0, 0, 0, 0},
/** @returns "Fast path" increment for request for "unobtrusive" type of lock, 0 - if it is request for "obtrusive" type of lock. @sa Description at method declaration for more details. */ MDL_lock::fast_path_state_t MDL_lock::get_unobtrusive_lock_increment( const MDL_request *request) { return MDL_lock::get_strategy(request->key) ->m_unobtrusive_lock_increment[request->type]; }
如果非 0,代表着该类型锁是 unobtrusive,就会走 fast path,直接通过 CAS 来给 MDL_lock::m_fast_path_state 递增上对应的整型值即可。但是需要确认一个条件,就是该对象没有被其他线程以 obtrusive 的方式锁住,因为 unobtrusive 和 obtrusive 的锁类型有些是互斥的,只有在没有 obtrusive 的锁存在时,其他的 unobtrusive 锁彼此兼容,才可以不用判断其他线程的锁持有情况直接获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
MDL_lock::fast_path_state_t old_state = lock->m_fast_path_state; do { /* Check if hash look-up returned object marked as destroyed or it was marked as such while it was pinned by us. If yes we need to unpin it and retry look-up. */ if (old_state & MDL_lock::IS_DESTROYED) { if (pinned) lf_hash_search_unpin(m_pins); goto retry; } /* Check that there are no granted/pending "obtrusive" locks and nobody even is about to try to check if such lock can be acquired. In these cases we need to take "slow path". */ if (old_state & MDL_lock::HAS_OBTRUSIVE) goto slow_path; } while (!lock->fast_path_state_cas( &old_state, old_state + unobtrusive_lock_increment));
CAS 完成后,设置相关数据结构的状态和引用,将当前 MDL_ticket 加入到线程的 MDL_ticket_store 中即可返回:
1 2 3 4 5 6 7 8 9 10 11 12
/* Since this MDL_ticket is not visible to any threads other than the current one, we can set MDL_ticket::m_lock member without protect of MDL_lock::m_rwlock. MDL_lock won't be deleted underneath our feet as MDL_lock::m_fast_path_state serves as reference counter in this case. */ ticket->m_lock = lock; ticket->m_is_fast_path = true; m_ticket_store.push_front(mdl_request->duration, ticket); mdl_request->ticket = ticket; mysql_mdl_set_status(ticket->m_psi, MDL_ticket::GRANTED);
/** "Materialize" requests for locks which were satisfied using "fast path" by properly including them into corresponding MDL_lock::m_granted bitmaps/lists and removing it from packed counter in MDL_lock::m_fast_path_state. */ void MDL_context::materialize_fast_path_locks() { int i; for (i = 0; i < MDL_DURATION_END; i++) { MDL_ticket_store::List_iterator it = m_ticket_store.list_iterator(i); MDL_ticket *matf = m_ticket_store.materialized_front(i); for (MDL_ticket *ticket = it++; ticket != matf; ticket = it++) { if (ticket->m_is_fast_path) { MDL_lock *lock = ticket->m_lock; MDL_lock::fast_path_state_t unobtrusive_lock_increment = lock->get_unobtrusive_lock_increment(ticket->get_type()); ticket->m_is_fast_path = false; mysql_prlock_wrlock(&lock->m_rwlock); lock->m_granted.add_ticket(ticket); /* Atomically decrement counter in MDL_lock::m_fast_path_state. This needs to happen under protection of MDL_lock::m_rwlock to make it atomic with addition of ticket to MDL_lock::m_granted list and to enforce invariant [INV1]. */ MDL_lock::fast_path_state_t old_state = lock->m_fast_path_state; while (!lock->fast_path_state_cas( &old_state, ((old_state - unobtrusive_lock_increment) | MDL_lock::HAS_SLOW_PATH))) { } mysql_prlock_unlock(&lock->m_rwlock); } } } m_ticket_store.set_materialized(); }
bool MDL_lock::can_grant_lock(enum_mdl_type type_arg, const MDL_context *requestor_ctx) const { bool can_grant = false; bitmap_t waiting_incompat_map = incompatible_waiting_types_bitmap()[type_arg]; bitmap_t granted_incompat_map = incompatible_granted_types_bitmap()[type_arg]; /* New lock request can be satisfied iff: - There are no incompatible types of satisfied requests in other contexts - There are no waiting requests which have higher priority than this request. */ if (!(m_waiting.bitmap() & waiting_incompat_map)) { if (!(fast_path_granted_bitmap() & granted_incompat_map)) { if (!(m_granted.bitmap() & granted_incompat_map)) can_grant = true; else { Ticket_iterator it(m_granted); MDL_ticket *ticket; /* There is an incompatible lock. Check that it belongs to some other context. */ while ((ticket = it++)) { if (ticket->get_ctx() != requestor_ctx && ticket->is_incompatible_when_granted(type_arg)) break; } if (ticket == NULL) /* Incompatible locks are our own. */ can_grant = true; } } } return can_grant; }
// WS_EMPTY since EMPTY conflicts with #define in system headers on some // platforms. enum enum_wait_status { WS_EMPTY = 0, GRANTED, VICTIM, TIMEOUT, KILLED };
/** Inform the deadlock detector there is an edge in the wait-for graph. */ void will_wait_for(MDL_wait_for_subgraph *waiting_for_arg) { /* Before starting wait for any resource we need to materialize all "fast path" tickets belonging to this thread. Otherwise locks acquired which are represented by these tickets won't be present in wait-for graph and could cause missed deadlocks. It is OK for context which doesn't wait for any resource to have "fast path" tickets, as such context can't participate in any deadlock. */ materialize_fast_path_locks(); mysql_prlock_wrlock(&m_LOCK_waiting_for); m_waiting_for = waiting_for_arg; mysql_prlock_unlock(&m_LOCK_waiting_for); }
/** Inspect a wait-for graph edge from one MDL context to another. @retval true A loop is found. @retval false No loop is found. */ bool Deadlock_detection_visitor::inspect_edge(MDL_context *node) { m_found_deadlock = node == m_start_node; return m_found_deadlock; } /** Change the deadlock victim to a new one if it has lower deadlock weight. @param new_victim New candidate for deadlock victim. */ void Deadlock_detection_visitor::opt_change_victim_to(MDL_context *new_victim) { if (m_victim == NULL || m_victim->get_deadlock_weight() >= new_victim->get_deadlock_weight()) { /* Swap victims, unlock the old one. */ MDL_context *tmp = m_victim; m_victim = new_victim; m_victim->lock_deadlock_victim(); if (tmp) tmp->unlock_deadlock_victim(); } }
/* We do a breadth-first search first -- that is, inspect all edges of the current node, and only then follow up to the next node. In workloads that involve wait-for graph loops this has proven to be a more efficient strategy [citation missing]. */ while ((ticket = granted_it++)) { /* Filter out edges that point to the same node. */ if (ticket->get_ctx() != src_ctx && ticket->is_incompatible_when_granted(waiting_ticket->get_type()) && gvisitor->inspect_edge(ticket->get_ctx())) { goto end_leave_node; } } ... /* Recurse and inspect all adjacent nodes. */ granted_it.rewind(); while ((ticket = granted_it++)) { if (ticket->get_ctx() != src_ctx && ticket->is_incompatible_when_granted(waiting_ticket->get_type()) && ticket->get_ctx()->visit_subgraph(gvisitor)) { goto end_leave_node; } } ...
Copyright 2021 sunfy.top ALL Rights Reserved