mariadb/storage/innobase/sync/srw_lock.cc
2024-10-03 10:55:08 +03:00

745 lines
20 KiB
C++

/*****************************************************************************
Copyright (c) 2020, 2022, MariaDB Corporation.
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 Street, Fifth Floor, Boston, MA 02110-1335 USA
*****************************************************************************/
#include "srw_lock.h"
#include "srv0srv.h"
#include "my_cpu.h"
#include "transactional_lock_guard.h"
#ifdef NO_ELISION
#elif defined _MSC_VER && (defined _M_IX86 || defined _M_X64)
# include <intrin.h>
bool have_transactional_memory;
bool transactional_lock_enabled() noexcept
{
int regs[4];
__cpuid(regs, 0);
if (regs[0] < 7)
return false;
__cpuidex(regs, 7, 0);
/* Restricted Transactional Memory (RTM) */
have_transactional_memory= regs[1] & 1U << 11;
return have_transactional_memory;
}
#elif defined __GNUC__ && (defined __i386__ || defined __x86_64__)
# include <cpuid.h>
bool have_transactional_memory;
bool transactional_lock_enabled() noexcept
{
if (__get_cpuid_max(0, nullptr) < 7)
return false;
unsigned eax, ebx, ecx, edx;
__cpuid_count(7, 0, eax, ebx, ecx, edx);
/* Restricted Transactional Memory (RTM) */
have_transactional_memory= ebx & 1U << 11;
return have_transactional_memory;
}
# ifdef UNIV_DEBUG
TRANSACTIONAL_TARGET
bool xtest() noexcept { return have_transactional_memory && _xtest(); }
# endif
#elif defined __powerpc64__ || defined __s390__
# include <htmxlintrin.h>
# include <setjmp.h>
# include <signal.h>
__attribute__((target("htm"),hot))
bool xbegin() noexcept
{
return have_transactional_memory &&
__TM_simple_begin() == _HTM_TBEGIN_STARTED;
}
__attribute__((target("htm"),hot))
void xabort() noexcept { __TM_abort(); }
__attribute__((target("htm"),hot))
void xend() noexcept { __TM_end(); }
bool have_transactional_memory;
static sigjmp_buf ill_jmp;
static void ill_handler(int sig) noexcept
{
siglongjmp(ill_jmp, sig);
}
/**
Here we are testing we can do a transaction without SIGILL
and a 1 instruction store can succeed.
*/
__attribute__((noinline))
static void test_tm(bool *r) noexcept
{
if (__TM_simple_begin() == _HTM_TBEGIN_STARTED)
{
*r= true;
__TM_end();
}
}
bool transactional_lock_enabled() noexcept
{
bool r= false;
sigset_t oset;
struct sigaction ill_act, oact_ill;
memset(&ill_act, 0, sizeof(ill_act));
ill_act.sa_handler = ill_handler;
sigfillset(&ill_act.sa_mask);
sigdelset(&ill_act.sa_mask, SIGILL);
sigprocmask(SIG_SETMASK, &ill_act.sa_mask, &oset);
sigaction(SIGILL, &ill_act, &oact_ill);
if (sigsetjmp(ill_jmp, 1) == 0)
{
test_tm(&r);
}
sigaction(SIGILL, &oact_ill, NULL);
sigprocmask(SIG_SETMASK, &oset, NULL);
return r;
}
# ifdef UNIV_DEBUG
__attribute__((target("htm"),hot))
bool xtest() noexcept
{
# ifdef __s390x__
return have_transactional_memory &&
__builtin_tx_nesting_depth() > 0;
# else
return have_transactional_memory &&
_HTM_STATE (__builtin_ttest ()) == _HTM_TRANSACTIONAL;
# endif
}
# endif
#endif
/** @return the parameter for srw_pause() */
static inline unsigned srw_pause_delay() noexcept
{
return my_cpu_relax_multiplier / 4 * srv_spin_wait_delay;
}
/** Pause the CPU for some time, with no memory accesses. */
static inline void srw_pause(unsigned delay) noexcept
{
HMT_low();
while (delay--)
MY_RELAX_CPU();
HMT_medium();
}
#ifndef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP
template<> void pthread_mutex_wrapper<true>::wr_wait() noexcept
{
const unsigned delay= srw_pause_delay();
for (auto spin= srv_n_spin_wait_rounds; spin; spin--)
{
srw_pause(delay);
if (wr_lock_try())
return;
}
pthread_mutex_lock(&lock);
}
#endif
#ifdef SUX_LOCK_GENERIC
template void ssux_lock_impl<false>::init() noexcept;
template void ssux_lock_impl<true>::init() noexcept;
template void ssux_lock_impl<false>::destroy() noexcept;
template void ssux_lock_impl<true>::destroy() noexcept;
template<bool spinloop>
inline void srw_mutex_impl<spinloop>::wait(uint32_t lk) noexcept
{
pthread_mutex_lock(&mutex);
while (lock.load(std::memory_order_relaxed) == lk)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
}
template<bool spinloop>
inline void ssux_lock_impl<spinloop>::wait(uint32_t lk) noexcept
{
pthread_mutex_lock(&writer.mutex);
while (readers.load(std::memory_order_relaxed) == lk)
pthread_cond_wait(&readers_cond, &writer.mutex);
pthread_mutex_unlock(&writer.mutex);
}
template<bool spinloop>
void srw_mutex_impl<spinloop>::wake() noexcept
{
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
template<bool spinloop>
inline void srw_mutex_impl<spinloop>::wake_all() noexcept
{
pthread_mutex_lock(&mutex);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
}
template<bool spinloop>
void ssux_lock_impl<spinloop>::wake() noexcept
{
pthread_mutex_lock(&writer.mutex);
pthread_cond_signal(&readers_cond);
pthread_mutex_unlock(&writer.mutex);
}
#else
static_assert(4 == sizeof(rw_lock), "ABI");
# ifdef _WIN32
# include <synchapi.h>
template<bool spinloop>
inline void srw_mutex_impl<spinloop>::wait(uint32_t lk) noexcept
{ WaitOnAddress(&lock, &lk, 4, INFINITE); }
template<bool spinloop>
void srw_mutex_impl<spinloop>::wake() noexcept { WakeByAddressSingle(&lock); }
template<bool spinloop>
inline void srw_mutex_impl<spinloop>::wake_all() noexcept { WakeByAddressAll(&lock); }
template<bool spinloop>
inline void ssux_lock_impl<spinloop>::wait(uint32_t lk) noexcept
{ WaitOnAddress(&readers, &lk, 4, INFINITE); }
template<bool spinloop>
void ssux_lock_impl<spinloop>::wake() noexcept { WakeByAddressSingle(&readers); }
# else
# ifdef __linux__
# include <linux/futex.h>
# include <sys/syscall.h>
# define SRW_FUTEX(a,op,n) \
syscall(SYS_futex, a, FUTEX_ ## op ## _PRIVATE, n, nullptr, nullptr, 0)
# elif defined __OpenBSD__
# include <sys/time.h>
# include <sys/futex.h>
# define SRW_FUTEX(a,op,n) \
futex((volatile uint32_t*) a, FUTEX_ ## op, n, nullptr, nullptr)
# elif defined __FreeBSD__
# include <sys/types.h>
# include <sys/umtx.h>
# define FUTEX_WAKE UMTX_OP_WAKE_PRIVATE
# define FUTEX_WAIT UMTX_OP_WAIT_UINT_PRIVATE
# define SRW_FUTEX(a,op,n) _umtx_op(a, FUTEX_ ## op, n, nullptr, nullptr)
# elif defined __DragonFly__
# include <unistd.h>
# define FUTEX_WAKE(a,n) umtx_wakeup(a,n)
# define FUTEX_WAIT(a,n) umtx_sleep(a,n,0)
# define SRW_FUTEX(a,op,n) FUTEX_ ## op((volatile int*) a, int(n))
# else
# error "no futex support"
# endif
template<bool spinloop>
inline void srw_mutex_impl<spinloop>::wait(uint32_t lk) noexcept
{ SRW_FUTEX(&lock, WAIT, lk); }
template<bool spinloop>
void srw_mutex_impl<spinloop>::wake() noexcept { SRW_FUTEX(&lock, WAKE, 1); }
template<bool spinloop>
void srw_mutex_impl<spinloop>::wake_all() noexcept { SRW_FUTEX(&lock, WAKE, INT_MAX); }
template<bool spinloop>
inline void ssux_lock_impl<spinloop>::wait(uint32_t lk) noexcept
{ SRW_FUTEX(&readers, WAIT, lk); }
template<bool spinloop>
void ssux_lock_impl<spinloop>::wake() noexcept { SRW_FUTEX(&readers, WAKE, 1); }
# endif
#endif
template void srw_mutex_impl<false>::wake() noexcept;
template void ssux_lock_impl<false>::wake() noexcept;
template void srw_mutex_impl<true>::wake() noexcept;
template void ssux_lock_impl<true>::wake() noexcept;
template<bool spinloop>
void srw_mutex_impl<spinloop>::wait_and_lock() noexcept
{
uint32_t lk= WAITER + lock.fetch_add(WAITER, std::memory_order_relaxed);
if (spinloop)
{
const unsigned delay= srw_pause_delay();
for (auto spin= srv_n_spin_wait_rounds;;)
{
DBUG_ASSERT(~HOLDER & lk);
lk= lock.load(std::memory_order_relaxed);
if (!(lk & HOLDER))
{
#if defined __i386__||defined __x86_64__||defined _M_IX86||defined _M_X64
lk |= HOLDER;
# ifdef _MSC_VER
static_assert(HOLDER == (1U << 0), "compatibility");
if (!_interlockedbittestandset
(reinterpret_cast<volatile long*>(&lock), 0))
# else
if (!(lock.fetch_or(HOLDER, std::memory_order_relaxed) & HOLDER))
# endif
goto acquired;
#else
if (!((lk= lock.fetch_or(HOLDER, std::memory_order_relaxed)) & HOLDER))
goto acquired;
#endif
}
if (!--spin)
break;
srw_pause(delay);
}
}
for (;;)
{
DBUG_ASSERT(~HOLDER & lk);
if (lk & HOLDER)
{
wait(lk);
#if defined __i386__||defined __x86_64__||defined _M_IX86||defined _M_X64
reload:
#endif
lk= lock.load(std::memory_order_relaxed);
}
else
{
#if defined __i386__||defined __x86_64__||defined _M_IX86||defined _M_X64
# ifdef _MSC_VER
static_assert(HOLDER == (1U << 0), "compatibility");
if (_interlockedbittestandset
(reinterpret_cast<volatile long*>(&lock), 0))
# else
if (lock.fetch_or(HOLDER, std::memory_order_relaxed) & HOLDER)
# endif
goto reload;
#else
if ((lk= lock.fetch_or(HOLDER, std::memory_order_relaxed)) & HOLDER)
continue;
DBUG_ASSERT(lk);
#endif
acquired:
std::atomic_thread_fence(std::memory_order_acquire);
return;
}
}
}
template void srw_mutex_impl<false>::wait_and_lock() noexcept;
template void srw_mutex_impl<true>::wait_and_lock() noexcept;
template<bool spinloop>
void ssux_lock_impl<spinloop>::wr_wait(uint32_t lk) noexcept
{
DBUG_ASSERT(writer.is_locked());
DBUG_ASSERT(lk);
DBUG_ASSERT(lk < WRITER);
if (spinloop)
{
const unsigned delay= srw_pause_delay();
for (auto spin= srv_n_spin_wait_rounds; spin; spin--)
{
srw_pause(delay);
lk= readers.load(std::memory_order_acquire);
if (lk == WRITER)
return;
DBUG_ASSERT(lk > WRITER);
}
}
lk|= WRITER;
do
{
DBUG_ASSERT(lk > WRITER);
wait(lk);
lk= readers.load(std::memory_order_acquire);
}
while (lk != WRITER);
}
template void ssux_lock_impl<true>::wr_wait(uint32_t) noexcept;
template void ssux_lock_impl<false>::wr_wait(uint32_t) noexcept;
template<bool spinloop>
void ssux_lock_impl<spinloop>::rd_wait() noexcept
{
const unsigned delay= srw_pause_delay();
if (spinloop)
{
for (auto spin= srv_n_spin_wait_rounds; spin; spin--)
{
srw_pause(delay);
if (rd_lock_try())
return;
}
}
/* Subscribe to writer.wake() or write.wake_all() calls by
concurrently executing rd_wait() or writer.wr_unlock(). */
uint32_t wl= writer.WAITER +
writer.lock.fetch_add(writer.WAITER, std::memory_order_acquire);
for (;;)
{
if (UNIV_LIKELY(writer.HOLDER & wl))
writer.wait(wl);
uint32_t lk= rd_lock_try_low();
if (!lk)
break;
if (UNIV_UNLIKELY(lk == WRITER)) /* A wr_lock() just succeeded. */
/* Immediately wake up (also) wr_lock(). We may also unnecessarily
wake up other concurrent threads that are executing rd_wait().
If we invoked writer.wake() here to wake up just one thread,
we could wake up a rd_wait(), which then would invoke writer.wake(),
waking up possibly another rd_wait(), and we could end up doing
lots of non-productive context switching until the wr_lock()
is finally woken up. */
writer.wake_all();
srw_pause(delay);
wl= writer.lock.load(std::memory_order_acquire);
ut_ad(wl);
}
/* Unsubscribe writer.wake() and writer.wake_all(). */
wl= writer.lock.fetch_sub(writer.WAITER, std::memory_order_release);
ut_ad(wl);
/* Wake any other threads that may be blocked in writer.wait().
All other waiters than this rd_wait() would end up acquiring writer.lock
and waking up other threads on unlock(). */
if (wl > writer.WAITER)
writer.wake_all();
}
template void ssux_lock_impl<true>::rd_wait() noexcept;
template void ssux_lock_impl<false>::rd_wait() noexcept;
#if defined _WIN32 || defined SUX_LOCK_GENERIC
template<> void srw_lock_<true>::rd_wait() noexcept
{
const unsigned delay= srw_pause_delay();
for (auto spin= srv_n_spin_wait_rounds; spin; spin--)
{
srw_pause(delay);
if (rd_lock_try())
return;
}
IF_WIN(AcquireSRWLockShared(&lk), rw_rdlock(&lk));
}
template<> void srw_lock_<true>::wr_wait() noexcept
{
const unsigned delay= srw_pause_delay();
for (auto spin= srv_n_spin_wait_rounds; spin; spin--)
{
srw_pause(delay);
if (wr_lock_try())
return;
}
IF_WIN(AcquireSRWLockExclusive(&lk), rw_wrlock(&lk));
}
#endif
#ifdef UNIV_PFS_RWLOCK
template void srw_lock_impl<false>::psi_rd_lock(const char*, unsigned) noexcept;
template void srw_lock_impl<false>::psi_wr_lock(const char*, unsigned) noexcept;
template void srw_lock_impl<true>::psi_rd_lock(const char*, unsigned) noexcept;
template void srw_lock_impl<true>::psi_wr_lock(const char*, unsigned) noexcept;
template<bool spinloop>
void srw_lock_impl<spinloop>::psi_rd_lock(const char *file, unsigned line) noexcept
{
PSI_rwlock_locker_state state;
const bool nowait= lock.rd_lock_try();
if (PSI_rwlock_locker *locker= PSI_RWLOCK_CALL(start_rwlock_rdwait)
(&state, pfs_psi,
nowait ? PSI_RWLOCK_TRYREADLOCK : PSI_RWLOCK_READLOCK, file, line))
{
if (!nowait)
lock.rd_lock();
PSI_RWLOCK_CALL(end_rwlock_rdwait)(locker, 0);
}
else if (!nowait)
lock.rd_lock();
}
template<bool spinloop>
void srw_lock_impl<spinloop>::psi_wr_lock(const char *file, unsigned line) noexcept
{
PSI_rwlock_locker_state state;
# if defined _WIN32 || defined SUX_LOCK_GENERIC
const bool nowait2= lock.wr_lock_try();
# else
const bool nowait1= lock.writer.wr_lock_try();
uint32_t lk= 0;
const bool nowait2= nowait1 &&
lock.readers.compare_exchange_strong(lk, lock.WRITER,
std::memory_order_acquire,
std::memory_order_relaxed);
# endif
if (PSI_rwlock_locker *locker= PSI_RWLOCK_CALL(start_rwlock_wrwait)
(&state, pfs_psi,
nowait2 ? PSI_RWLOCK_TRYWRITELOCK : PSI_RWLOCK_WRITELOCK, file, line))
{
# if defined _WIN32 || defined SUX_LOCK_GENERIC
if (!nowait2)
lock.wr_lock();
# else
if (!nowait1)
lock.wr_lock();
else if (!nowait2)
lock.u_wr_upgrade();
# endif
PSI_RWLOCK_CALL(end_rwlock_rdwait)(locker, 0);
}
# if defined _WIN32 || defined SUX_LOCK_GENERIC
else if (!nowait2)
lock.wr_lock();
# else
else if (!nowait1)
lock.wr_lock();
else if (!nowait2)
lock.u_wr_upgrade();
# endif
}
void ssux_lock::psi_rd_lock(const char *file, unsigned line) noexcept
{
PSI_rwlock_locker_state state;
const bool nowait= lock.rd_lock_try();
if (PSI_rwlock_locker *locker= PSI_RWLOCK_CALL(start_rwlock_rdwait)
(&state, pfs_psi,
nowait ? PSI_RWLOCK_TRYSHAREDLOCK : PSI_RWLOCK_SHAREDLOCK, file, line))
{
if (!nowait)
lock.rd_lock();
PSI_RWLOCK_CALL(end_rwlock_rdwait)(locker, 0);
}
else if (!nowait)
lock.rd_lock();
}
void ssux_lock::psi_u_lock(const char *file, unsigned line) noexcept
{
PSI_rwlock_locker_state state;
if (PSI_rwlock_locker *locker= PSI_RWLOCK_CALL(start_rwlock_wrwait)
(&state, pfs_psi, PSI_RWLOCK_SHAREDEXCLUSIVELOCK, file, line))
{
lock.u_lock();
PSI_RWLOCK_CALL(end_rwlock_rdwait)(locker, 0);
}
else
lock.u_lock();
}
void ssux_lock::psi_wr_lock(const char *file, unsigned line) noexcept
{
PSI_rwlock_locker_state state;
# if defined _WIN32 || defined SUX_LOCK_GENERIC
const bool nowait2= lock.wr_lock_try();
# else
const bool nowait1= lock.writer.wr_lock_try();
uint32_t lk= 0;
const bool nowait2= nowait1 &&
lock.readers.compare_exchange_strong(lk, lock.WRITER,
std::memory_order_acquire,
std::memory_order_relaxed);
# endif
if (PSI_rwlock_locker *locker= PSI_RWLOCK_CALL(start_rwlock_wrwait)
(&state, pfs_psi,
nowait2 ? PSI_RWLOCK_TRYEXCLUSIVELOCK : PSI_RWLOCK_EXCLUSIVELOCK,
file, line))
{
# if defined _WIN32 || defined SUX_LOCK_GENERIC
if (!nowait2)
lock.wr_lock();
# else
if (!nowait1)
lock.wr_lock();
else if (!nowait2)
lock.u_wr_upgrade();
# endif
PSI_RWLOCK_CALL(end_rwlock_rdwait)(locker, 0);
}
# if defined _WIN32 || defined SUX_LOCK_GENERIC
else if (!nowait2)
lock.wr_lock();
# else
else if (!nowait1)
lock.wr_lock();
else if (!nowait2)
lock.u_wr_upgrade();
# endif
}
void ssux_lock::psi_u_wr_upgrade(const char *file, unsigned line) noexcept
{
PSI_rwlock_locker_state state;
DBUG_ASSERT(lock.writer.is_locked());
uint32_t lk= 0;
const bool nowait=
lock.readers.compare_exchange_strong(lk, ssux_lock_impl<false>::WRITER,
std::memory_order_acquire,
std::memory_order_relaxed);
if (PSI_rwlock_locker *locker= PSI_RWLOCK_CALL(start_rwlock_wrwait)
(&state, pfs_psi,
nowait ? PSI_RWLOCK_TRYEXCLUSIVELOCK : PSI_RWLOCK_EXCLUSIVELOCK,
file, line))
{
if (!nowait)
lock.u_wr_upgrade();
PSI_RWLOCK_CALL(end_rwlock_rdwait)(locker, 0);
}
else if (!nowait)
lock.u_wr_upgrade();
}
#else /* UNIV_PFS_RWLOCK */
template void ssux_lock_impl<false>::rd_lock() noexcept;
template void ssux_lock_impl<false>::rd_unlock() noexcept;
template void ssux_lock_impl<false>::u_unlock() noexcept;
template void ssux_lock_impl<false>::wr_unlock() noexcept;
#endif /* UNIV_PFS_RWLOCK */
#ifdef UNIV_DEBUG
void srw_lock_debug::SRW_LOCK_INIT(mysql_pfs_key_t key) noexcept
{
srw_lock::SRW_LOCK_INIT(key);
readers_lock.init();
ut_ad(!readers.load(std::memory_order_relaxed));
ut_ad(!have_any());
}
void srw_lock_debug::destroy() noexcept
{
ut_ad(!writer);
if (auto r= readers.load(std::memory_order_relaxed))
{
readers.store(0, std::memory_order_relaxed);
ut_ad(r->empty());
delete r;
}
readers_lock.destroy();
srw_lock::destroy();
}
bool srw_lock_debug::wr_lock_try() noexcept
{
ut_ad(!have_any());
if (!srw_lock::wr_lock_try())
return false;
ut_ad(!writer);
writer.store(pthread_self(), std::memory_order_relaxed);
return true;
}
void srw_lock_debug::wr_lock(SRW_LOCK_ARGS(const char *file, unsigned line)) noexcept
{
ut_ad(!have_any());
srw_lock::wr_lock(SRW_LOCK_ARGS(file, line));
ut_ad(!writer);
writer.store(pthread_self(), std::memory_order_relaxed);
}
void srw_lock_debug::wr_unlock() noexcept
{
ut_ad(have_wr());
writer.store(0, std::memory_order_relaxed);
srw_lock::wr_unlock();
}
void srw_lock_debug::readers_register() noexcept
{
readers_lock.wr_lock();
auto r= readers.load(std::memory_order_relaxed);
if (!r)
{
r= new std::unordered_multiset<pthread_t>();
readers.store(r, std::memory_order_relaxed);
}
r->emplace(pthread_self());
readers_lock.wr_unlock();
}
bool srw_lock_debug::rd_lock_try() noexcept
{
ut_ad(!have_any());
if (!srw_lock::rd_lock_try())
return false;
readers_register();
return true;
}
void srw_lock_debug::rd_lock(SRW_LOCK_ARGS(const char *file, unsigned line)) noexcept
{
ut_ad(!have_any());
srw_lock::rd_lock(SRW_LOCK_ARGS(file, line));
readers_register();
}
void srw_lock_debug::rd_unlock() noexcept
{
const pthread_t self= pthread_self();
ut_ad(writer != self);
readers_lock.wr_lock();
auto r= readers.load(std::memory_order_relaxed);
ut_ad(r);
auto i= r->find(self);
ut_ad(i != r->end());
r->erase(i);
readers_lock.wr_unlock();
srw_lock::rd_unlock();
}
bool srw_lock_debug::have_rd() const noexcept
{
if (auto r= readers.load(std::memory_order_relaxed))
{
readers_lock.wr_lock();
bool found= r->find(pthread_self()) != r->end();
readers_lock.wr_unlock();
# ifndef SUX_LOCK_GENERIC
ut_ad(!found || is_locked());
# endif
return found;
}
return false;
}
bool srw_lock_debug::have_wr() const noexcept
{
if (writer != pthread_self())
return false;
# ifndef SUX_LOCK_GENERIC
ut_ad(is_write_locked());
# endif
return true;
}
bool srw_lock_debug::have_any() const noexcept
{
return have_wr() || have_rd();
}
#endif