mirror of
https://github.com/MariaDB/server.git
synced 2025-01-19 05:22:25 +01:00
539 lines
15 KiB
Text
539 lines
15 KiB
Text
/******************************************************
|
|
The read-write lock (for threads)
|
|
|
|
(c) 1995 Innobase Oy
|
|
|
|
Created 9/11/1995 Heikki Tuuri
|
|
*******************************************************/
|
|
|
|
/**********************************************************************
|
|
Lock an rw-lock in shared mode for the current thread. If the rw-lock is
|
|
locked in exclusive mode, or there is an exclusive lock request waiting,
|
|
the function spins a preset time (controlled by SYNC_SPIN_ROUNDS),
|
|
waiting for the lock before suspending the thread. */
|
|
|
|
void
|
|
rw_lock_s_lock_spin(
|
|
/*================*/
|
|
rw_lock_t* lock, /* in: pointer to rw-lock */
|
|
ulint pass, /* in: pass value; != 0, if the lock will
|
|
be passed to another thread to unlock */
|
|
const char* file_name,/* in: file name where lock requested */
|
|
ulint line); /* in: line where requested */
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
/**********************************************************************
|
|
Inserts the debug information for an rw-lock. */
|
|
|
|
void
|
|
rw_lock_add_debug_info(
|
|
/*===================*/
|
|
rw_lock_t* lock, /* in: rw-lock */
|
|
ulint pass, /* in: pass value */
|
|
ulint lock_type, /* in: lock type */
|
|
const char* file_name, /* in: file where requested */
|
|
ulint line); /* in: line where requested */
|
|
/**********************************************************************
|
|
Removes a debug information struct for an rw-lock. */
|
|
|
|
void
|
|
rw_lock_remove_debug_info(
|
|
/*======================*/
|
|
rw_lock_t* lock, /* in: rw-lock */
|
|
ulint pass, /* in: pass value */
|
|
ulint lock_type); /* in: lock type */
|
|
#endif /* UNIV_SYNC_DEBUG */
|
|
|
|
/************************************************************************
|
|
Accessor functions for rw lock. */
|
|
UNIV_INLINE
|
|
ulint
|
|
rw_lock_get_waiters(
|
|
/*================*/
|
|
rw_lock_t* lock)
|
|
{
|
|
return(lock->waiters);
|
|
}
|
|
UNIV_INLINE
|
|
void
|
|
rw_lock_set_waiters(
|
|
/*================*/
|
|
rw_lock_t* lock,
|
|
ulint flag)
|
|
{
|
|
lock->waiters = flag;
|
|
}
|
|
|
|
/**********************************************************************
|
|
Returns the write-status of the lock - this function made more sense
|
|
with the old rw_lock implementation.
|
|
*/
|
|
UNIV_INLINE
|
|
ulint
|
|
rw_lock_get_writer(
|
|
/*===============*/
|
|
rw_lock_t* lock)
|
|
{
|
|
lint lock_word = lock->lock_word;
|
|
if(lock_word > 0) {
|
|
/* return NOT_LOCKED in s-lock state, like the writer
|
|
member of the old lock implementation. */
|
|
return RW_LOCK_NOT_LOCKED;
|
|
} else if (((-lock_word) % X_LOCK_DECR) == 0) {
|
|
return RW_LOCK_EX;
|
|
} else {
|
|
ut_ad(lock_word > -X_LOCK_DECR);
|
|
return RW_LOCK_WAIT_EX;
|
|
}
|
|
}
|
|
|
|
UNIV_INLINE
|
|
ulint
|
|
rw_lock_get_reader_count(
|
|
/*=====================*/
|
|
rw_lock_t* lock)
|
|
{
|
|
lint lock_word = lock->lock_word;
|
|
if(lock_word > 0) {
|
|
/* s-locked, no x-waiters */
|
|
return(X_LOCK_DECR - lock_word);
|
|
} else if (lock_word < 0 && lock_word > -X_LOCK_DECR) {
|
|
/* s-locked, with x-waiters */
|
|
return (ulint)(-lock_word);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifndef HAVE_GCC_ATOMIC_BUILTINS
|
|
UNIV_INLINE
|
|
mutex_t*
|
|
rw_lock_get_mutex(
|
|
/*==============*/
|
|
rw_lock_t* lock)
|
|
{
|
|
return(&(lock->mutex));
|
|
}
|
|
#endif
|
|
|
|
/**********************************************************************
|
|
Returns the value of writer_count for the lock. Does not reserve the lock
|
|
mutex, so the caller must be sure it is not changed during the call. */
|
|
UNIV_INLINE
|
|
ulint
|
|
rw_lock_get_x_lock_count(
|
|
/*=====================*/
|
|
/* out: value of writer_count */
|
|
rw_lock_t* lock) /* in: rw-lock */
|
|
{
|
|
lint lock_copy = lock->lock_word;
|
|
/* If there is a reader, lock_word is not divisible by X_LOCK_DECR */
|
|
if(lock_copy > 0 || (-lock_copy) % X_LOCK_DECR != 0) {
|
|
return 0;
|
|
}
|
|
return ((-lock_copy) / X_LOCK_DECR) + 1;
|
|
}
|
|
|
|
/**********************************************************************
|
|
Two different implementations for decrementing the lock_word of a rw_lock:
|
|
one for systems supporting atomic operations, one for others. This does
|
|
does not support recusive x-locks: they should be handled by the caller and
|
|
need not be atomic since they are performed by the current lock holder.
|
|
Returns true if the decrement was made, false if not. */
|
|
UNIV_INLINE
|
|
ibool
|
|
rw_lock_lock_word_decr(
|
|
/* out: TRUE if decr occurs */
|
|
rw_lock_t* lock, /* in: rw-lock */
|
|
ulint amount) /* in: amount of decrement */
|
|
{
|
|
|
|
#ifdef HAVE_GCC_ATOMIC_BUILTINS
|
|
|
|
lint local_lock_word = lock->lock_word;
|
|
while (local_lock_word > 0) {
|
|
if(os_compare_and_swap(&(lock->lock_word),
|
|
local_lock_word,
|
|
local_lock_word - amount)) {
|
|
return TRUE;
|
|
}
|
|
local_lock_word = lock->lock_word;
|
|
}
|
|
return(FALSE);
|
|
|
|
#else /* HAVE_GCC_ATOMIC_BUILTINS */
|
|
|
|
ibool success = FALSE;
|
|
mutex_enter(&(lock->mutex));
|
|
if(lock->lock_word > 0) {
|
|
lock->lock_word -= amount;
|
|
success = TRUE;
|
|
}
|
|
mutex_exit(&(lock->mutex));
|
|
return success;
|
|
|
|
#endif /* HAVE_GCC_ATOMIC_BUILTINS */
|
|
|
|
}
|
|
|
|
/**********************************************************************
|
|
Two different implementations for incrementing the lock_word of a rw_lock:
|
|
one for systems supporting atomic operations, one for others.
|
|
Returns the value of lock_word after increment. */
|
|
UNIV_INLINE
|
|
lint
|
|
rw_lock_lock_word_incr(
|
|
/* out: lock->lock_word after increment */
|
|
rw_lock_t* lock, /* in: rw-lock */
|
|
ulint amount) /* in: amount of increment */
|
|
{
|
|
|
|
#ifdef HAVE_GCC_ATOMIC_BUILTINS
|
|
|
|
return(os_atomic_increment(&(lock->lock_word), amount));
|
|
|
|
#else /* HAVE_GCC_ATOMIC_BUILTINS */
|
|
|
|
lint local_lock_word;
|
|
|
|
mutex_enter(&(lock->mutex));
|
|
|
|
lock->lock_word += amount;
|
|
local_lock_word = lock->lock_word;
|
|
|
|
mutex_exit(&(lock->mutex));
|
|
|
|
return local_lock_word;
|
|
|
|
#endif /* HAVE_GCC_ATOMIC_BUILTINS */
|
|
|
|
}
|
|
|
|
/**********************************************************************
|
|
Low-level function which tries to lock an rw-lock in s-mode. Performs no
|
|
spinning. */
|
|
UNIV_INLINE
|
|
ibool
|
|
rw_lock_s_lock_low(
|
|
/*===============*/
|
|
/* out: TRUE if success */
|
|
rw_lock_t* lock, /* in: pointer to rw-lock */
|
|
ulint pass __attribute__((unused)),
|
|
/* in: pass value; != 0, if the lock will be
|
|
passed to another thread to unlock */
|
|
const char* file_name, /* in: file name where lock requested */
|
|
ulint line) /* in: line where requested */
|
|
{
|
|
/* TODO: study performance of UNIV_LIKELY branch prediction hints. */
|
|
if (!rw_lock_lock_word_decr(lock, 1)) {
|
|
/* Locking did not succeed */
|
|
return(FALSE);
|
|
}
|
|
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
rw_lock_add_debug_info(lock, pass, RW_LOCK_SHARED, file_name, line);
|
|
#endif
|
|
/* These debugging values are not set safely: they may be incorrect
|
|
or even refer to a line that is invalid for the file name. */
|
|
lock->last_s_file_name = file_name;
|
|
lock->last_s_line = line;
|
|
|
|
return(TRUE); /* locking succeeded */
|
|
}
|
|
|
|
/**********************************************************************
|
|
Low-level function which locks an rw-lock in s-mode when we know that it
|
|
is possible and none else is currently accessing the rw-lock structure.
|
|
Then we can do the locking without reserving the mutex. */
|
|
UNIV_INLINE
|
|
void
|
|
rw_lock_s_lock_direct(
|
|
/*==================*/
|
|
rw_lock_t* lock, /* in: pointer to rw-lock */
|
|
const char* file_name, /* in: file name where requested */
|
|
ulint line) /* in: line where lock requested */
|
|
{
|
|
ut_ad(lock->lock_word == X_LOCK_DECR);
|
|
|
|
/* Indicate there is a new reader by decrementing lock_word */
|
|
lock->lock_word--;
|
|
|
|
lock->last_s_file_name = file_name;
|
|
lock->last_s_line = line;
|
|
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
rw_lock_add_debug_info(lock, 0, RW_LOCK_SHARED, file_name, line);
|
|
#endif
|
|
}
|
|
|
|
/**********************************************************************
|
|
Low-level function which locks an rw-lock in x-mode when we know that it
|
|
is not locked and none else is currently accessing the rw-lock structure.
|
|
Then we can do the locking without reserving the mutex. */
|
|
UNIV_INLINE
|
|
void
|
|
rw_lock_x_lock_direct(
|
|
/*==================*/
|
|
rw_lock_t* lock, /* in: pointer to rw-lock */
|
|
const char* file_name, /* in: file name where requested */
|
|
ulint line) /* in: line where lock requested */
|
|
{
|
|
ut_ad(rw_lock_validate(lock));
|
|
ut_ad(lock->lock_word == X_LOCK_DECR);
|
|
|
|
lock->lock_word -= X_LOCK_DECR;
|
|
lock->writer_thread = os_thread_get_curr_id();
|
|
lock->pass = 0;
|
|
|
|
lock->last_x_file_name = file_name;
|
|
lock->last_x_line = line;
|
|
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
rw_lock_add_debug_info(lock, 0, RW_LOCK_EX, file_name, line);
|
|
#endif
|
|
}
|
|
|
|
/**********************************************************************
|
|
NOTE! Use the corresponding macro, not directly this function! Lock an
|
|
rw-lock in shared mode for the current thread. If the rw-lock is locked
|
|
in exclusive mode, or there is an exclusive lock request waiting, the
|
|
function spins a preset time (controlled by SYNC_SPIN_ROUNDS), waiting for
|
|
the lock, before suspending the thread. */
|
|
UNIV_INLINE
|
|
void
|
|
rw_lock_s_lock_func(
|
|
/*================*/
|
|
rw_lock_t* lock, /* in: pointer to rw-lock */
|
|
ulint pass, /* in: pass value; != 0, if the lock will
|
|
be passed to another thread to unlock */
|
|
const char* file_name,/* in: file name where lock requested */
|
|
ulint line) /* in: line where requested */
|
|
{
|
|
/* NOTE: As we do not know the thread ids for threads which have
|
|
s-locked a latch, and s-lockers will be served only after waiting
|
|
x-lock requests have been fulfilled, then if this thread already
|
|
owns an s-lock here, it may end up in a deadlock with another thread
|
|
which requests an x-lock here. Therefore, we will forbid recursive
|
|
s-locking of a latch: the following assert will warn the programmer
|
|
of the possibility of this kind of a deadlock. If we want to implement
|
|
safe recursive s-locking, we should keep in a list the thread ids of
|
|
the threads which have s-locked a latch. This would use some CPU
|
|
time. */
|
|
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
ut_ad(!rw_lock_own(lock, RW_LOCK_SHARED)); /* see NOTE above */
|
|
#endif /* UNIV_SYNC_DEBUG */
|
|
|
|
/* TODO: study performance of UNIV_LIKELY branch prediction hints. */
|
|
if (rw_lock_s_lock_low(lock, pass, file_name, line)) {
|
|
|
|
return; /* Success */
|
|
} else {
|
|
/* Did not succeed, try spin wait */
|
|
|
|
rw_lock_s_lock_spin(lock, pass, file_name, line);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**********************************************************************
|
|
NOTE! Use the corresponding macro, not directly this function! Lock an
|
|
rw-lock in exclusive mode for the current thread if the lock can be
|
|
obtained immediately. */
|
|
UNIV_INLINE
|
|
ibool
|
|
rw_lock_x_lock_func_nowait(
|
|
/*=======================*/
|
|
/* out: TRUE if success */
|
|
rw_lock_t* lock, /* in: pointer to rw-lock */
|
|
const char* file_name,/* in: file name where lock requested */
|
|
ulint line) /* in: line where requested */
|
|
{
|
|
os_thread_id_t curr_thread = os_thread_get_curr_id();
|
|
|
|
ibool success;
|
|
|
|
#ifdef HAVE_GCC_ATOMIC_BUILTINS
|
|
success = os_compare_and_swap(&(lock->lock_word), X_LOCK_DECR, 0);
|
|
#else
|
|
|
|
success = FALSE;
|
|
mutex_enter(&(lock->mutex));
|
|
if(lock->lock_word == X_LOCK_DECR) {
|
|
lock->lock_word = 0;
|
|
success = TRUE;
|
|
}
|
|
mutex_exit(&(lock->mutex));
|
|
|
|
#endif
|
|
if(success) {
|
|
lock->writer_thread = curr_thread;
|
|
lock->pass = 0;
|
|
|
|
} else if (!(lock->pass) &&
|
|
os_thread_eq(lock->writer_thread, curr_thread)) {
|
|
/* Must verify pass first: otherwise another thread can
|
|
call move_ownership suddenly allowing recursive locks.
|
|
and after we have verified our thread_id matches
|
|
(though move_ownership has since changed it).*/
|
|
|
|
/* Relock: this lock_word modification is safe since no other
|
|
threads can modify (lock, unlock, or reserve) lock_word while
|
|
there is an exclusive writer and this is the writer thread. */
|
|
lock->lock_word -= X_LOCK_DECR;
|
|
|
|
ut_ad(((-lock->lock_word) % X_LOCK_DECR) == 0);
|
|
|
|
} else {
|
|
/* Failure */
|
|
return(FALSE);
|
|
}
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
rw_lock_add_debug_info(lock, 0, RW_LOCK_EX, file_name, line);
|
|
#endif
|
|
|
|
lock->last_x_file_name = file_name;
|
|
lock->last_x_line = line;
|
|
|
|
ut_ad(rw_lock_validate(lock));
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
/**********************************************************************
|
|
Releases a shared mode lock. */
|
|
UNIV_INLINE
|
|
void
|
|
rw_lock_s_unlock_func(
|
|
/*==================*/
|
|
rw_lock_t* lock /* in: rw-lock */
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
,ulint pass /* in: pass value; != 0, if the lock may have
|
|
been passed to another thread to unlock */
|
|
#endif
|
|
)
|
|
{
|
|
ut_ad((lock->lock_word % X_LOCK_DECR) != 0);
|
|
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
rw_lock_remove_debug_info(lock, pass, RW_LOCK_SHARED);
|
|
#endif
|
|
|
|
/* Increment lock_word to indicate 1 less reader */
|
|
if(rw_lock_lock_word_incr(lock, 1) == 0) {
|
|
|
|
/* wait_ex waiter exists. It may not be asleep, but we signal
|
|
anyway. We do not wake other waiters, because they can't
|
|
exist without wait_ex waiter and wait_ex waiter goes first.*/
|
|
os_event_set(lock->wait_ex_event);
|
|
sync_array_object_signalled(sync_primary_wait_array);
|
|
|
|
}
|
|
|
|
ut_ad(rw_lock_validate(lock));
|
|
|
|
#ifdef UNIV_SYNC_PERF_STAT
|
|
rw_s_exit_count++;
|
|
#endif
|
|
}
|
|
|
|
/**********************************************************************
|
|
Releases a shared mode lock when we know there are no waiters and none
|
|
else will access the lock during the time this function is executed. */
|
|
UNIV_INLINE
|
|
void
|
|
rw_lock_s_unlock_direct(
|
|
/*====================*/
|
|
rw_lock_t* lock) /* in: rw-lock */
|
|
{
|
|
ut_ad(lock->lock_word < X_LOCK_DECR);
|
|
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
rw_lock_remove_debug_info(lock, 0, RW_LOCK_SHARED);
|
|
#endif
|
|
|
|
/* Decrease reader count by incrementing lock_word */
|
|
lock->lock_word++;
|
|
|
|
ut_ad(!lock->waiters);
|
|
ut_ad(rw_lock_validate(lock));
|
|
#ifdef UNIV_SYNC_PERF_STAT
|
|
rw_s_exit_count++;
|
|
#endif
|
|
}
|
|
|
|
/**********************************************************************
|
|
Releases an exclusive mode lock. */
|
|
UNIV_INLINE
|
|
void
|
|
rw_lock_x_unlock_func(
|
|
/*==================*/
|
|
rw_lock_t* lock /* in: rw-lock */
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
,ulint pass /* in: pass value; != 0, if the lock may have
|
|
been passed to another thread to unlock */
|
|
#endif
|
|
)
|
|
{
|
|
ut_ad((lock->lock_word % X_LOCK_DECR) == 0);
|
|
|
|
/* Must reset writer_thread while we still have the lock.
|
|
If we are not the last unlocker, we correct it later in the function,
|
|
which is harmless since we still hold the lock. */
|
|
/* TODO: are there any risks of a thread id == -1 on any platform? */
|
|
os_thread_id_t local_writer_thread;
|
|
local_write_thread = lock->writer_thread;
|
|
lock->writer_thread = -1;
|
|
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
rw_lock_remove_debug_info(lock, pass, RW_LOCK_EX);
|
|
#endif
|
|
|
|
if(rw_lock_lock_word_incr(lock, X_LOCK_DECR) == X_LOCK_DECR) {
|
|
/* Lock is now free. May have to signal read/write waiters.
|
|
We do not need to signal wait_ex waiters, since they cannot
|
|
exist when there is a writer. */
|
|
if(lock->waiters) {
|
|
rw_lock_set_waiters(lock, 0);
|
|
os_event_set(lock->event);
|
|
sync_array_object_signalled(sync_primary_wait_array);
|
|
}
|
|
|
|
} else {
|
|
/* We still hold x-lock, so we correct writer_thread. */
|
|
lock->writer_thread = local_writer_thread;
|
|
}
|
|
|
|
ut_ad(rw_lock_validate(lock));
|
|
|
|
#ifdef UNIV_SYNC_PERF_STAT
|
|
rw_x_exit_count++;
|
|
#endif
|
|
}
|
|
|
|
/**********************************************************************
|
|
Releases an exclusive mode lock when we know there are no waiters, and
|
|
none else will access the lock durint the time this function is executed. */
|
|
UNIV_INLINE
|
|
void
|
|
rw_lock_x_unlock_direct(
|
|
/*====================*/
|
|
rw_lock_t* lock) /* in: rw-lock */
|
|
{
|
|
/* Reset the exclusive lock if this thread no longer has an x-mode
|
|
lock */
|
|
|
|
ut_ad((lock->lock_word % X_LOCK_DECR) == 0);
|
|
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
rw_lock_remove_debug_info(lock, 0, RW_LOCK_EX);
|
|
#endif
|
|
|
|
lock->lock_word += X_LOCK_DECR;
|
|
|
|
ut_ad(!lock->waiters);
|
|
ut_ad(rw_lock_validate(lock));
|
|
|
|
#ifdef UNIV_SYNC_PERF_STAT
|
|
rw_x_exit_count++;
|
|
#endif
|
|
}
|