summaryrefslogtreecommitdiff
path: root/mysys/thr_lock.c
diff options
context:
space:
mode:
Diffstat (limited to 'mysys/thr_lock.c')
-rw-r--r--mysys/thr_lock.c325
1 files changed, 293 insertions, 32 deletions
diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c
index 853e1f96b49..b925c5588be 100644
--- a/mysys/thr_lock.c
+++ b/mysys/thr_lock.c
@@ -71,7 +71,7 @@ multiple read locks.
*/
#if !defined(MAIN) && !defined(DBUG_OFF) && !defined(EXTRA_DEBUG)
-#define DBUG_OFF
+#define FORCE_DBUG_OFF
#endif
#include "mysys_priv.h"
@@ -121,12 +121,11 @@ thr_lock_owner_equal(THR_LOCK_OWNER *rhs, THR_LOCK_OWNER *lhs)
static uint found_errors=0;
static int check_lock(struct st_lock_list *list, const char* lock_type,
- const char *where, my_bool same_owner, bool no_cond)
+ const char *where, my_bool same_owner, my_bool no_cond)
{
THR_LOCK_DATA *data,**prev;
uint count=0;
- THR_LOCK_OWNER *first_owner;
- LINT_INIT(first_owner);
+ THR_LOCK_OWNER *UNINIT_VAR(first_owner);
prev= &list->data;
if (list->data)
@@ -343,8 +342,9 @@ void thr_lock_delete(THR_LOCK *lock)
void thr_lock_info_init(THR_LOCK_INFO *info)
{
- info->thread= pthread_self();
- info->thread_id= my_thread_id(); /* for debugging */
+ struct st_my_thread_var *tmp= my_thread_var;
+ info->thread= tmp->pthread_self;
+ info->thread_id= tmp->id;
info->n_cursors= 0;
}
@@ -395,6 +395,29 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
struct timespec wait_timeout;
enum enum_thr_lock_result result= THR_LOCK_ABORTED;
my_bool can_deadlock= test(data->owner->info->n_cursors);
+ DBUG_ENTER("wait_for_lock");
+
+ /*
+ One can use this to signal when a thread is going to wait for a lock.
+ See debug_sync.cc.
+
+ Beware of waiting for a signal here. The lock has aquired its mutex.
+ While waiting on a signal here, the locking thread could not aquire
+ the mutex to release the lock. One could lock up the table
+ completely.
+
+ In detail it works so: When thr_lock() tries to acquire a table
+ lock, it locks the lock->mutex, checks if it can have the lock, and
+ if not, it calls wait_for_lock(). Here it unlocks the table lock
+ while waiting on a condition. The sync point is located before this
+ wait for condition. If we have a waiting action here, we hold the
+ the table locks mutex all the time. Any attempt to look at the table
+ lock by another thread blocks it immediately on lock->mutex. This
+ can easily become an unexpected and unobvious blockage. So be
+ warned: Do not request a WAIT_FOR action for the 'wait_for_lock'
+ sync point unless you really know what you do.
+ */
+ DEBUG_SYNC_C("wait_for_lock");
if (!in_wait_list)
{
@@ -403,6 +426,8 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
wait->last= &data->next;
}
+ statistic_increment(locks_waited, &THR_LOCK_lock);
+
/* Set up control struct to allow others to abort locks */
thread_var->current_mutex= &data->lock->mutex;
thread_var->current_cond= cond;
@@ -430,13 +455,21 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
if the predicate is true.
*/
if (data->cond == 0)
+ {
+ DBUG_PRINT("thr_lock", ("lock granted/aborted"));
break;
+ }
if (rc == ETIMEDOUT || rc == ETIME)
{
+ /* purecov: begin inspected */
+ DBUG_PRINT("thr_lock", ("lock timed out"));
result= THR_LOCK_WAIT_TIMEOUT;
break;
+ /* purecov: end */
}
}
+ DBUG_PRINT("thr_lock", ("aborted: %d in_wait_list: %d",
+ thread_var->abort, in_wait_list));
if (data->cond || data->type == TL_UNLOCK)
{
@@ -452,13 +485,13 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
}
else
{
+ DBUG_PRINT("thr_lock", ("lock aborted"));
check_locks(data->lock, "aborted wait_for_lock", 0);
}
}
else
{
result= THR_LOCK_SUCCESS;
- statistic_increment(locks_waited, &THR_LOCK_lock);
if (data->lock->get_status)
(*data->lock->get_status)(data->status_param, 0);
check_locks(data->lock,"got wait_for_lock",0);
@@ -470,7 +503,7 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
thread_var->current_mutex= 0;
thread_var->current_cond= 0;
pthread_mutex_unlock(&thread_var->mutex);
- return result;
+ DBUG_RETURN(result);
}
@@ -489,7 +522,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
data->type=lock_type;
data->owner= owner; /* Must be reset ! */
VOID(pthread_mutex_lock(&lock->mutex));
- DBUG_PRINT("lock",("data: 0x%lx thread: %ld lock: 0x%lx type: %d",
+ DBUG_PRINT("lock",("data: 0x%lx thread: 0x%lx lock: 0x%lx type: %d",
(long) data, data->owner->info->thread_id,
(long) lock, (int) lock_type));
check_locks(lock,(uint) lock_type <= (uint) TL_READ_NO_INSERT ?
@@ -508,7 +541,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
and the read lock is not TL_READ_NO_INSERT
*/
- DBUG_PRINT("lock",("write locked by thread: %ld",
+ DBUG_PRINT("lock",("write locked 1 by thread: 0x%lx",
lock->write.data->owner->info->thread_id));
if (thr_lock_owner_equal(data->owner, lock->write.data->owner) ||
(lock->write.data->type <= TL_WRITE_DELAYED &&
@@ -597,10 +630,14 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
{
if (lock->write.data->type == TL_WRITE_ONLY)
{
- /* We are not allowed to get a lock in this case */
- data->type=TL_UNLOCK;
- result= THR_LOCK_ABORTED; /* Can't wait for this one */
- goto end;
+ /* Allow lock owner to bypass TL_WRITE_ONLY. */
+ if (!thr_lock_owner_equal(data->owner, lock->write.data->owner))
+ {
+ /* We are not allowed to get a lock in this case */
+ data->type=TL_UNLOCK;
+ result= THR_LOCK_ABORTED; /* Can't wait for this one */
+ goto end;
+ }
}
/*
@@ -630,7 +667,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
statistic_increment(locks_immediate,&THR_LOCK_lock);
goto end;
}
- DBUG_PRINT("lock",("write locked by thread: %ld",
+ DBUG_PRINT("lock",("write locked 2 by thread: 0x%lx",
lock->write.data->owner->info->thread_id));
}
else
@@ -666,7 +703,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
goto end;
}
}
- DBUG_PRINT("lock",("write locked by thread: %ld type: %d",
+ DBUG_PRINT("lock",("write locked 3 by thread: 0x%lx type: %d",
lock->read.data->owner->info->thread_id, data->type));
}
wait_queue= &lock->write_wait;
@@ -680,6 +717,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
lock_owner= lock->read.data ? lock->read.data : lock->write.data;
if (lock_owner && lock_owner->owner->info == owner->info)
{
+ DBUG_PRINT("lock",("deadlock"));
result= THR_LOCK_DEADLOCK;
goto end;
}
@@ -692,7 +730,7 @@ end:
static inline void free_all_read_locks(THR_LOCK *lock,
- bool using_concurrent_insert)
+ my_bool using_concurrent_insert)
{
THR_LOCK_DATA *data=lock->read_wait.data;
@@ -728,8 +766,10 @@ static inline void free_all_read_locks(THR_LOCK *lock,
}
lock->read_no_write_count++;
}
- DBUG_PRINT("lock",("giving read lock to thread: %ld",
+ /* purecov: begin inspected */
+ DBUG_PRINT("lock",("giving read lock to thread: 0x%lx",
data->owner->info->thread_id));
+ /* purecov: end */
data->cond=0; /* Mark thread free */
VOID(pthread_cond_signal(cond));
} while ((data=data->next));
@@ -746,7 +786,7 @@ void thr_unlock(THR_LOCK_DATA *data)
THR_LOCK *lock=data->lock;
enum thr_lock_type lock_type=data->type;
DBUG_ENTER("thr_unlock");
- DBUG_PRINT("lock",("data: 0x%lx thread: %ld lock: 0x%lx",
+ DBUG_PRINT("lock",("data: 0x%lx thread: 0x%lx lock: 0x%lx",
(long) data, data->owner->info->thread_id, (long) lock));
pthread_mutex_lock(&lock->mutex);
check_locks(lock,"start of release lock",0);
@@ -834,8 +874,10 @@ static void wake_up_waiters(THR_LOCK *lock)
if (data->type == TL_WRITE_CONCURRENT_INSERT &&
(*lock->check_status)(data->status_param))
data->type=TL_WRITE; /* Upgrade lock */
- DBUG_PRINT("lock",("giving write lock of type %d to thread: %ld",
+ /* purecov: begin inspected */
+ DBUG_PRINT("lock",("giving write lock of type %d to thread: 0x%lx",
data->type, data->owner->info->thread_id));
+ /* purecov: end */
{
pthread_cond_t *cond=data->cond;
data->cond=0; /* Mark thread free */
@@ -915,7 +957,7 @@ end:
*/
-#define LOCK_CMP(A,B) ((byte*) (A->lock) - (uint) ((A)->type) < (byte*) (B->lock)- (uint) ((B)->type))
+#define LOCK_CMP(A,B) ((uchar*) (A->lock) - (uint) ((A)->type) < (uchar*) (B->lock)- (uint) ((B)->type))
static void sort_locks(THR_LOCK_DATA **data,uint count)
{
@@ -1029,7 +1071,7 @@ void thr_multi_unlock(THR_LOCK_DATA **data,uint count)
thr_unlock(*pos);
else
{
- DBUG_PRINT("lock",("Free lock: data: 0x%lx thread: %ld lock: 0x%lx",
+ DBUG_PRINT("lock",("Free lock: data: 0x%lx thread: 0x%lx lock: 0x%lx",
(long) *pos, (*pos)->owner->info->thread_id,
(long) (*pos)->lock));
}
@@ -1042,7 +1084,7 @@ void thr_multi_unlock(THR_LOCK_DATA **data,uint count)
TL_WRITE_ONLY to abort any new accesses to the lock
*/
-void thr_abort_locks(THR_LOCK *lock)
+void thr_abort_locks(THR_LOCK *lock, my_bool upgrade_lock)
{
THR_LOCK_DATA *data;
DBUG_ENTER("thr_abort_locks");
@@ -1064,7 +1106,7 @@ void thr_abort_locks(THR_LOCK *lock)
lock->read_wait.last= &lock->read_wait.data;
lock->write_wait.last= &lock->write_wait.data;
lock->read_wait.data=lock->write_wait.data=0;
- if (lock->write.data)
+ if (upgrade_lock && lock->write.data)
lock->write.data->type=TL_WRITE_ONLY;
pthread_mutex_unlock(&lock->mutex);
DBUG_VOID_RETURN;
@@ -1077,7 +1119,7 @@ void thr_abort_locks(THR_LOCK *lock)
This is used to abort all locks for a specific thread
*/
-my_bool thr_abort_locks_for_thread(THR_LOCK *lock, pthread_t thread)
+my_bool thr_abort_locks_for_thread(THR_LOCK *lock, my_thread_id thread_id)
{
THR_LOCK_DATA *data;
my_bool found= FALSE;
@@ -1086,7 +1128,7 @@ my_bool thr_abort_locks_for_thread(THR_LOCK *lock, pthread_t thread)
pthread_mutex_lock(&lock->mutex);
for (data= lock->read_wait.data; data ; data= data->next)
{
- if (pthread_equal(thread, data->owner->info->thread))
+ if (data->owner->info->thread_id == thread_id) /* purecov: tested */
{
DBUG_PRINT("info",("Aborting read-wait lock"));
data->type= TL_UNLOCK; /* Mark killed */
@@ -1103,7 +1145,7 @@ my_bool thr_abort_locks_for_thread(THR_LOCK *lock, pthread_t thread)
}
for (data= lock->write_wait.data; data ; data= data->next)
{
- if (pthread_equal(thread, data->owner->info->thread))
+ if (data->owner->info->thread_id == thread_id) /* purecov: tested */
{
DBUG_PRINT("info",("Aborting write-wait lock"));
data->type= TL_UNLOCK;
@@ -1123,10 +1165,223 @@ my_bool thr_abort_locks_for_thread(THR_LOCK *lock, pthread_t thread)
}
+/*
+ Downgrade a WRITE_* to a lower WRITE level
+ SYNOPSIS
+ thr_downgrade_write_lock()
+ in_data Lock data of thread downgrading its lock
+ new_lock_type New write lock type
+ RETURN VALUE
+ NONE
+ DESCRIPTION
+ This can be used to downgrade a lock already owned. When the downgrade
+ occurs also other waiters, both readers and writers can be allowed to
+ start.
+ The previous lock is often TL_WRITE_ONLY but can also be
+ TL_WRITE and TL_WRITE_ALLOW_READ. The normal downgrade variants are
+ TL_WRITE_ONLY => TL_WRITE_ALLOW_READ After a short exclusive lock
+ TL_WRITE_ALLOW_READ => TL_WRITE_ALLOW_WRITE After discovering that the
+ operation didn't need such a high lock.
+ TL_WRITE_ONLY => TL_WRITE after a short exclusive lock while holding a
+ write table lock
+ TL_WRITE_ONLY => TL_WRITE_ALLOW_WRITE After a short exclusive lock after
+ already earlier having dongraded lock to TL_WRITE_ALLOW_WRITE
+ The implementation is conservative and rather don't start rather than
+ go on unknown paths to start, the common cases are handled.
+
+ NOTE:
+ In its current implementation it is only allowed to downgrade from
+ TL_WRITE_ONLY. In this case there are no waiters. Thus no wake up
+ logic is required.
+*/
+
+void thr_downgrade_write_lock(THR_LOCK_DATA *in_data,
+ enum thr_lock_type new_lock_type)
+{
+ THR_LOCK *lock=in_data->lock;
+#ifndef DBUG_OFF
+ enum thr_lock_type old_lock_type= in_data->type;
+#endif
+#ifdef TO_BE_REMOVED
+ THR_LOCK_DATA *data, *next;
+ bool start_writers= FALSE;
+ bool start_readers= FALSE;
+#endif
+ DBUG_ENTER("thr_downgrade_write_only_lock");
+
+ pthread_mutex_lock(&lock->mutex);
+ DBUG_ASSERT(old_lock_type == TL_WRITE_ONLY);
+ DBUG_ASSERT(old_lock_type > new_lock_type);
+ in_data->type= new_lock_type;
+ check_locks(lock,"after downgrading lock",0);
+
+#if TO_BE_REMOVED
+ switch (old_lock_type)
+ {
+ case TL_WRITE_ONLY:
+ case TL_WRITE:
+ case TL_WRITE_LOW_PRIORITY:
+ /*
+ Previous lock was exclusive we are now ready to start up most waiting
+ threads.
+ */
+ switch (new_lock_type)
+ {
+ case TL_WRITE_ALLOW_READ:
+ /* Still cannot start WRITE operations. Can only start readers. */
+ start_readers= TRUE;
+ break;
+ case TL_WRITE:
+ case TL_WRITE_LOW_PRIORITY:
+ /*
+ Still cannot start anything, but new requests are no longer
+ aborted.
+ */
+ break;
+ case TL_WRITE_ALLOW_WRITE:
+ /*
+ We can start both writers and readers.
+ */
+ start_writers= TRUE;
+ start_readers= TRUE;
+ break;
+ case TL_WRITE_CONCURRENT_INSERT:
+ case TL_WRITE_DELAYED:
+ /*
+ This routine is not designed for those. Lock will be downgraded
+ but no start of waiters will occur. This is not the optimal but
+ should be a correct behaviour.
+ */
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ break;
+ case TL_WRITE_DELAYED:
+ case TL_WRITE_CONCURRENT_INSERT:
+ /*
+ This routine is not designed for those. Lock will be downgraded
+ but no start of waiters will occur. This is not the optimal but
+ should be a correct behaviour.
+ */
+ break;
+ case TL_WRITE_ALLOW_READ:
+ DBUG_ASSERT(new_lock_type == TL_WRITE_ALLOW_WRITE);
+ /*
+ Previously writers were not allowed to start, now it is ok to
+ start them again. Readers are already allowed so no reason to
+ handle them.
+ */
+ start_writers= TRUE;
+ break;
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+ if (start_writers)
+ {
+ /*
+ At this time the only active writer can be ourselves. Thus we need
+ not worry about that there are other concurrent write operations
+ active on the table. Thus we only need to worry about starting
+ waiting operations.
+ We also only come here with TL_WRITE_ALLOW_WRITE as the new
+ lock type, thus we can start other writers also of the same type.
+ If we find a lock at exclusive level >= TL_WRITE_LOW_PRIORITY we
+ don't start any more operations that would be mean those operations
+ will have to wait for things started afterwards.
+ */
+ DBUG_ASSERT(new_lock_type == TL_WRITE_ALLOW_WRITE);
+ for (data=lock->write_wait.data; data ; data= next)
+ {
+ /*
+ All WRITE requests compatible with new lock type are also
+ started
+ */
+ next= data->next;
+ if (start_writers && data->type == new_lock_type)
+ {
+ pthread_cond_t *cond= data->cond;
+ /*
+ It is ok to start this waiter.
+ Move from being first in wait queue to be last in write queue.
+ */
+ if (((*data->prev)= data->next))
+ data->next->prev= data->prev;
+ else
+ lock->write_wait.last= data->prev;
+ data->prev= lock->write.last;
+ lock->write.last= &data->next;
+ data->next= 0;
+ check_locks(lock, "Started write lock after downgrade",0);
+ data->cond= 0;
+ pthread_cond_signal(cond);
+ }
+ else
+ {
+ /*
+ We found an incompatible lock, we won't start any more write
+ requests to avoid letting writers pass other writers in the
+ queue.
+ */
+ start_writers= FALSE;
+ if (data->type >= TL_WRITE_LOW_PRIORITY)
+ {
+ /*
+ We have an exclusive writer in the queue so we won't start
+ readers either.
+ */
+ start_readers= FALSE;
+ }
+ }
+ }
+ }
+ if (start_readers)
+ {
+ DBUG_ASSERT(new_lock_type == TL_WRITE_ALLOW_WRITE ||
+ new_lock_type == TL_WRITE_ALLOW_READ);
+ /*
+ When we come here we know that the write locks are
+ TL_WRITE_ALLOW_WRITE or TL_WRITE_ALLOW_READ. This means that reads
+ are ok
+ */
+ for (data=lock->read_wait.data; data ; data=next)
+ {
+ next= data->next;
+ /*
+ All reads are ok to start now except TL_READ_NO_INSERT when
+ write lock is TL_WRITE_ALLOW_READ.
+ */
+ if (new_lock_type != TL_WRITE_ALLOW_READ ||
+ data->type != TL_READ_NO_INSERT)
+ {
+ pthread_cond_t *cond= data->cond;
+ if (((*data->prev)= data->next))
+ data->next->prev= data->prev;
+ else
+ lock->read_wait.last= data->prev;
+ data->prev= lock->read.last;
+ lock->read.last= &data->next;
+ data->next= 0;
+
+ if (data->type == TL_READ_NO_INSERT)
+ lock->read_no_write_count++;
+ check_locks(lock, "Started read lock after downgrade",0);
+ data->cond= 0;
+ pthread_cond_signal(cond);
+ }
+ }
+ }
+ check_locks(lock,"after starting waiters after downgrading lock",0);
+#endif
+ pthread_mutex_unlock(&lock->mutex);
+ DBUG_VOID_RETURN;
+}
/* Upgrade a WRITE_DELAY lock to a WRITE_LOCK */
-my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data)
+my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data,
+ enum thr_lock_type new_lock_type)
{
THR_LOCK *lock=data->lock;
DBUG_ENTER("thr_upgrade_write_delay_lock");
@@ -1139,7 +1394,7 @@ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data)
}
check_locks(lock,"before upgrading lock",0);
/* TODO: Upgrade to TL_WRITE_CONCURRENT_INSERT in some cases */
- data->type=TL_WRITE; /* Upgrade lock */
+ data->type= new_lock_type; /* Upgrade lock */
/* Check if someone has given us the lock */
if (!data->cond)
@@ -1178,6 +1433,7 @@ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data)
my_bool thr_reschedule_write_lock(THR_LOCK_DATA *data)
{
THR_LOCK *lock=data->lock;
+ enum thr_lock_type write_lock_type;
DBUG_ENTER("thr_reschedule_write_lock");
pthread_mutex_lock(&lock->mutex);
@@ -1187,6 +1443,7 @@ my_bool thr_reschedule_write_lock(THR_LOCK_DATA *data)
DBUG_RETURN(0);
}
+ write_lock_type= data->type;
data->type=TL_WRITE_DELAYED;
if (lock->update_status)
(*lock->update_status)(data->status_param);
@@ -1205,7 +1462,7 @@ my_bool thr_reschedule_write_lock(THR_LOCK_DATA *data)
free_all_read_locks(lock,0);
pthread_mutex_unlock(&lock->mutex);
- DBUG_RETURN(thr_upgrade_write_delay_lock(data));
+ DBUG_RETURN(thr_upgrade_write_delay_lock(data, write_lock_type));
}
@@ -1342,6 +1599,10 @@ static void test_get_status(void* param __attribute__((unused)),
{
}
+static void test_update_status(void* param __attribute__((unused)))
+{
+}
+
static void test_copy_status(void* to __attribute__((unused)) ,
void *from __attribute__((unused)))
{
@@ -1401,7 +1662,7 @@ static void *test_thread(void *arg)
thread_count--;
VOID(pthread_cond_signal(&COND_thread_count)); /* Tell main we are ready */
pthread_mutex_unlock(&LOCK_thread_count);
- free((gptr) arg);
+ free((uchar*) arg);
return 0;
}
@@ -1434,7 +1695,7 @@ int main(int argc __attribute__((unused)),char **argv __attribute__((unused)))
{
thr_lock_init(locks+i);
locks[i].check_status= test_check_status;
- locks[i].update_status=test_get_status;
+ locks[i].update_status=test_update_status;
locks[i].copy_status= test_copy_status;
locks[i].get_status= test_get_status;
}