mariadb/storage/innobase/include/sync0rw.ic
2008-10-15 20:54:18 +02:00

538 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 = 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
}