summaryrefslogtreecommitdiff
path: root/sql/mdl.cc
diff options
context:
space:
mode:
authorSergey Vojtovich <svoj@mariadb.org>2015-02-26 13:12:28 +0400
committerSergey Vojtovich <svoj@mariadb.org>2015-03-04 13:34:52 +0400
commitf475a7f9490a59e38fade4bc334986b6681abc59 (patch)
tree330d4b81c4126718eec949016e4b35c0a2e233c8 /sql/mdl.cc
parentecd3ff9f24d8cf0c51ad564d9c8b335299dcdd9e (diff)
downloadmariadb-git-f475a7f9490a59e38fade4bc334986b6681abc59.tar.gz
MDEV-6089 - MySQL WL#7305 "Improve MDL scalability by using lock-free hash"
Replaced m_ref_usage, m_ref_release, m_is_destroyed and m_version with single m_state, which is updated atomically. Needed to get rid of MDL_map::m_mutex.
Diffstat (limited to 'sql/mdl.cc')
-rw-r--r--sql/mdl.cc155
1 files changed, 26 insertions, 129 deletions
diff --git a/sql/mdl.cc b/sql/mdl.cc
index feb1b0c13b5..ce11e2aee27 100644
--- a/sql/mdl.cc
+++ b/sql/mdl.cc
@@ -410,10 +410,7 @@ public:
MDL_lock(const MDL_key *key_arg)
: key(key_arg),
m_hog_lock_count(0),
- m_ref_usage(0),
- m_ref_release(0),
- m_is_destroyed(FALSE),
- m_version(0)
+ m_state(0)
{
mysql_prlock_init(key_MDL_lock_rwlock, &m_rwlock);
}
@@ -423,47 +420,12 @@ public:
mysql_prlock_destroy(&m_rwlock);
}
inline static void destroy(MDL_lock *lock);
-public:
- /**
- These three members are used to make it possible to separate
- the MDL_map::m_mutex mutex and MDL_lock::m_rwlock in
- MDL_map::find_or_insert() for increased scalability.
- The 'm_is_destroyed' member is only set by destroyers that
- have both the MDL_map::m_mutex and MDL_lock::m_rwlock, thus
- holding any of the mutexes is sufficient to read it.
- The 'm_ref_usage; is incremented under protection by
- MDL_map::m_mutex, but when 'm_is_destroyed' is set to TRUE, this
- member is moved to be protected by the MDL_lock::m_rwlock.
- This means that the MDL_map::find_or_insert() which only
- holds the MDL_lock::m_rwlock can compare it to 'm_ref_release'
- without acquiring MDL_map::m_mutex again and if equal
- it can also destroy the lock object safely.
- The 'm_ref_release' is incremented under protection by
- MDL_lock::m_rwlock.
- Note since we are only interested in equality of these two
- counters we don't have to worry about overflows as long as
- their size is big enough to hold maximum number of concurrent
- threads on the system.
- */
- uint m_ref_usage;
- uint m_ref_release;
- bool m_is_destroyed;
+
/**
- We use the same idea and an additional version counter to support
- caching of unused MDL_lock object for further re-use.
- This counter is incremented while holding both MDL_map::m_mutex
- and MDL_lock::m_rwlock locks each time when a MDL_lock is moved from
- the hash to the unused objects list (or destroyed).
- A thread, which has found a MDL_lock object for the key in the hash
- and then released the MDL_map::m_mutex before acquiring the
- MDL_lock::m_rwlock, can determine that this object was moved to the
- unused objects list (or destroyed) while it held no locks by comparing
- the version value which it read while holding the MDL_map::m_mutex
- with the value read after acquiring the MDL_lock::m_rwlock.
- Note that since it takes several years to overflow this counter such
- theoretically possible overflows should not have any practical effects.
+ Lock state: first 31 bits are reference counter, 32-nd bit is deleted flag.
*/
- ulonglong m_version;
+ static const int32 DELETED= 1 << 31;
+ int32 m_state;
};
@@ -648,7 +610,7 @@ int mdl_iterate(int (*callback)(MDL_ticket *ticket, void *arg), void *arg)
for (i= 0; i < mdl_locks.m_locks.records; i++)
{
MDL_lock *lock= (MDL_lock*) my_hash_element(&mdl_locks.m_locks, i);
- lock->m_ref_usage++;
+ my_atomic_add32_explicit(&lock->m_state, 1, MY_MEMORY_ORDER_RELAXED);
insert_dynamic(&locks, &lock);
}
mysql_mutex_unlock(&mdl_locks.m_mutex);
@@ -657,19 +619,15 @@ int mdl_iterate(int (*callback)(MDL_ticket *ticket, void *arg), void *arg)
for (i= 0; i < locks.elements; i++)
{
MDL_lock *lock= (MDL_lock*) *dynamic_element(&locks, i, MDL_lock**);
- res= mdl_iterate_lock(lock, callback, arg);
+ res|= mdl_iterate_lock(lock, callback, arg);
mysql_prlock_wrlock(&lock->m_rwlock);
- uint ref_usage= lock->m_ref_usage;
- uint ref_release= ++lock->m_ref_release;
- bool is_destroyed= lock->m_is_destroyed;
+ int32 old_state= my_atomic_add32_explicit(&lock->m_state, -1,
+ MY_MEMORY_ORDER_RELAXED);
mysql_prlock_unlock(&lock->m_rwlock);
- if (unlikely(is_destroyed && ref_usage == ref_release))
+ if (unlikely(old_state == MDL_lock::DELETED + 1))
MDL_lock::destroy(lock);
-
- if (res)
- break;
}
end:
delete_dynamic(&locks);
@@ -759,10 +717,7 @@ retry:
mdl_key->ptr(),
mdl_key->length())))
{
- /*
- No lock object found so we need to create a new one
- or reuse an existing unused object.
- */
+ /* No lock object found so we need to create a new one. */
lock= MDL_lock::create(mdl_key);
if (!lock || my_hash_insert(&m_locks, (uchar*)lock))
{
@@ -791,61 +746,26 @@ retry:
bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock)
{
- ulonglong version;
-
- DBUG_ASSERT(! lock->m_is_destroyed);
+ DBUG_ASSERT(!(my_atomic_load32(&lock->m_state) & MDL_lock::DELETED));
mysql_mutex_assert_owner(&m_mutex);
- /*
- We increment m_ref_usage which is a reference counter protected by
- MDL_map::m_mutex under the condition it is present in the hash
- and m_is_destroyed is FALSE.
- */
- lock->m_ref_usage++;
- /* Read value of the version counter under protection of m_mutex lock. */
- version= lock->m_version;
+ my_atomic_add32_explicit(&lock->m_state, 1, MY_MEMORY_ORDER_RELAXED);
mysql_mutex_unlock(&m_mutex);
mysql_prlock_wrlock(&lock->m_rwlock);
- lock->m_ref_release++;
-
- if (unlikely(lock->m_version != version))
+ int32 old_state= my_atomic_add32_explicit(&lock->m_state, -1,
+ MY_MEMORY_ORDER_RELAXED);
+ if (unlikely(old_state & MDL_lock::DELETED))
{
/*
- If the current value of version differs from one that was read while
- we held m_mutex mutex, this MDL_lock object was moved to the unused
- objects list or destroyed while we held no locks.
- We should retry our search. But first we should destroy the MDL_lock
- object if necessary.
+ Object was released while we held no locks, we need to
+ release it if no others hold references to it, while our own
+ reference count ensured that the object as such haven't got
+ its memory released yet.
*/
- if (unlikely(lock->m_is_destroyed))
- {
- /*
- Object was released while we held no locks, we need to
- release it if no others hold references to it, while our own
- reference count ensured that the object as such haven't got
- its memory released yet. We can also safely compare
- m_ref_usage and m_ref_release since the object is no longer
- present in the hash (or unused objects list) so no one will
- be able to find it and increment m_ref_usage anymore.
- */
- uint ref_usage= lock->m_ref_usage;
- uint ref_release= lock->m_ref_release;
- mysql_prlock_unlock(&lock->m_rwlock);
- if (ref_usage == ref_release)
- MDL_lock::destroy(lock);
- }
- else
- {
- /*
- Object was not destroyed but its version has changed.
- This means that it was moved to the unused objects list
- (and even might be already re-used). So now it might
- correspond to a different key, therefore we should simply
- retry our search.
- */
- mysql_prlock_unlock(&lock->m_rwlock);
- }
+ mysql_prlock_unlock(&lock->m_rwlock);
+ if (old_state == MDL_lock::DELETED + 1)
+ MDL_lock::destroy(lock);
return TRUE;
}
return FALSE;
@@ -907,40 +827,17 @@ void MDL_map::remove(MDL_lock *lock)
mysql_mutex_lock(&m_mutex);
my_hash_delete(&m_locks, (uchar*) lock);
- /*
- To let threads holding references to the MDL_lock object know that it was
- moved to the list of unused objects or destroyed, we increment the version
- counter under protection of both MDL_map::m_mutex and
- MDL_lock::m_rwlock locks. This allows us to read the version value while
- having either one of those locks.
- */
- lock->m_version++;
/*
Destroy the MDL_lock object, but ensure that anyone that is
holding a reference to the object is not remaining, if so he
has the responsibility to release it.
-
- Setting of m_is_destroyed to TRUE while holding _both_
- MDL_map::m_mutex and MDL_lock::m_rwlock mutexes transfers
- the protection of m_ref_usage from MDL_map::m_mutex to
- MDL_lock::m_rwlock while removal of the object from the hash
- (and cache of unused objects) makes it read-only. Therefore
- whoever acquires MDL_lock::m_rwlock next will see the most up
- to date version of m_ref_usage.
-
- This means that when m_is_destroyed is TRUE and we hold the
- MDL_lock::m_rwlock we can safely read the m_ref_usage
- member.
*/
- uint ref_usage, ref_release;
-
- lock->m_is_destroyed= TRUE;
- ref_usage= lock->m_ref_usage;
- ref_release= lock->m_ref_release;
+ int32 old_state= my_atomic_add32_explicit(&lock->m_state, MDL_lock::DELETED,
+ MY_MEMORY_ORDER_RELAXED);
mysql_mutex_unlock(&m_mutex);
mysql_prlock_unlock(&lock->m_rwlock);
- if (ref_usage == ref_release)
+ if (!old_state)
MDL_lock::destroy(lock);
}