diff options
Diffstat (limited to 'mysys/thr_lock.c')
-rw-r--r-- | mysys/thr_lock.c | 325 |
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; } |