diff options
Diffstat (limited to 'mysys/thr_lock.c')
-rw-r--r-- | mysys/thr_lock.c | 1288 |
1 files changed, 1288 insertions, 0 deletions
diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c new file mode 100644 index 00000000000..5c0803c6402 --- /dev/null +++ b/mysys/thr_lock.c @@ -0,0 +1,1288 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA */ + +/* +Read and write locks for Posix threads. All tread must acquire +all locks it needs through thr_multi_lock() to avoid dead-locks. +A lock consists of a master lock (THR_LOCK), and lock instances +(THR_LOCK_DATA). +Any thread can have any number of lock instances (read and write:s) on +any lock. All lock instances must be freed. +Locks are prioritized according to: + +The current lock types are: + +TL_READ # Low priority read +TL_READ_HIGH_PRIORITY # High priority read +TL_READ_NO_INSERT # Read without concurrent inserts +TL_WRITE_ALLOW_WRITE # Write lock that allows other writers +TL_WRITE_ALLOW_READ # Write lock, but allow reading +TL_WRITE_CONCURRENT_INSERT + # Insert that can be mixed when selects +TL_WRITE_DELAYED # Used by delayed insert + # Allows lower locks to take over +TL_WRITE_LOW_PRIORITY # Low priority write +TL_WRITE # High priority write +TL_WRITE_ONLY # High priority write + # Abort all new lock request with an error + +Locks are prioritized according to: + +WRITE_ALLOW_WRITE, WRITE_ALLOW_READ, WRITE_CONCURRENT_INSERT, WRITE_DELAYED, +WRITE_LOW_PRIORITY, READ, WRITE, READ_HIGH_PRIORITY and WRITE_ONLY + +Locks in the same privilege level are scheduled in first-in-first-out order. + +To allow concurrent read/writes locks, with 'WRITE_CONCURRENT_INSERT' one +should put a pointer to the following functions in the lock structure: +(If the pointer is zero (default), the function is not called) + +check_status: + Before giving a lock of type TL_WRITE_CONCURRENT_INSERT, + we check if this function exists and returns 0. + If not, then the lock is upgraded to TL_WRITE_LOCK + In MyISAM this is a simple check if the insert can be done + at the end of the datafile. +update_status: + Before a write lock is released, this function is called. + In MyISAM this functions updates the count and length of the datafile +get_status: + When one gets a lock this functions is called. + In MyISAM this stores the number of rows and size of the datafile + for concurrent reads. + +The lock algorithm allows one to have one TL_WRITE_ALLOW_READ, +TL_WRITE_CONCURRENT_INSERT or one TL_WRITE_DELAYED lock at the same time as +multiple read locks. + +*/ + +#if !defined(MAIN) && !defined(DBUG_OFF) && !defined(EXTRA_DEBUG) +#define DBUG_OFF +#endif + +#include "mysys_priv.h" +#include "thr_lock.h" +#include <m_string.h> +#include <errno.h> + +my_bool thr_lock_inited=0; + +#ifdef THREAD + +/* The following constants are only for debug output */ +#define MAX_THREADS 100 +#define MAX_LOCKS 100 + + +static LIST *thread_list; /* List of threads in use */ +ulong max_write_lock_count= ~(ulong) 0L; + +static inline pthread_cond_t *get_cond(void) +{ + return &my_thread_var->suspend; +} + +/* +** For the future (now the thread specific cond is alloced by my_pthread.c) +*/ + +my_bool init_thr_lock() +{ + thr_lock_inited=1; + return 0; +} + +#ifdef EXTRA_DEBUG +static int found_errors=0; + +static int check_lock(struct st_lock_list *list, const char* lock_type, + const char *where, my_bool same_thread) +{ + THR_LOCK_DATA *data,**prev; + uint count=0; + pthread_t first_thread; + LINT_INIT(first_thread); + + prev= &list->data; + if (list->data) + { + enum thr_lock_type last_lock_type=list->data->type; + + if (same_thread && list->data) + first_thread=list->data->thread; + for (data=list->data; data && count++ < MAX_LOCKS ; data=data->next) + { + if (data->type != last_lock_type) + last_lock_type=TL_IGNORE; + if (data->prev != prev) + { + fprintf(stderr, + "Warning: prev link %d didn't point at previous lock at %s: %s\n", + count, lock_type, where); + return 1; + } + if (same_thread && ! pthread_equal(data->thread,first_thread) && + last_lock_type != TL_WRITE_ALLOW_WRITE) + { + fprintf(stderr, + "Warning: Found locks from different threads in %s: %s\n", + lock_type,where); + return 1; + } + prev= &data->next; + } + if (data) + { + fprintf(stderr,"Warning: found too many locks at %s: %s\n", + lock_type,where); + return 1; + } + } + if (prev != list->last) + { + fprintf(stderr,"Warning: last didn't point at last lock at %s: %s\n", + lock_type, where); + return 1; + } + return 0; +} + +static void check_locks(THR_LOCK *lock, const char *where, + my_bool allow_no_locks) +{ + if (!found_errors) + { + if (check_lock(&lock->write,"write",where,1) | + check_lock(&lock->write_wait,"write_wait",where,0) | + check_lock(&lock->read,"read",where,0) | + check_lock(&lock->read_wait,"read_wait",where,0)) + found_errors=1; + + if (!found_errors) + { + uint count=0; + THR_LOCK_DATA *data; + for (data=lock->read.data ; data ; data=data->next) + { + if ((int) data->type == (int) TL_READ_NO_INSERT) + count++; + } + if (count != lock->read_no_write_count) + { + found_errors=1; + fprintf(stderr, + "Warning at '%s': Locks read_no_write_count was %u when it should have been %u\n", where, lock->read_no_write_count,count); + } + + if (!lock->write.data) + { + if (!allow_no_locks && !lock->read.data && + (lock->write_wait.data || lock->read_wait.data)) + { + found_errors=1; + fprintf(stderr, + "Warning at '%s': No locks in use but locks are in wait queue\n", + where); + } + if (!lock->write_wait.data) + { + if (!allow_no_locks && lock->read_wait.data) + { + found_errors=1; + fprintf(stderr, + "Warning at '%s': No write locks and waiting read locks\n", + where); + } + } + else + { + if (!allow_no_locks && + (((lock->write_wait.data->type == TL_WRITE_CONCURRENT_INSERT || + lock->write_wait.data->type == TL_WRITE_ALLOW_WRITE) && + !lock->read_no_write_count) || + lock->write_wait.data->type == TL_WRITE_ALLOW_READ || + (lock->write_wait.data->type == TL_WRITE_DELAYED && + !lock->read.data))) + { + found_errors=1; + fprintf(stderr, + "Warning at '%s': Write lock %d waiting while no exclusive read locks\n",where,(int) lock->write_wait.data->type); + } + } + } + else + { /* Have write lock */ + if (lock->write_wait.data) + { + if (!allow_no_locks && + lock->write.data->type == TL_WRITE_ALLOW_WRITE && + lock->write_wait.data->type == TL_WRITE_ALLOW_WRITE) + { + found_errors=1; + fprintf(stderr, + "Warning at '%s': Found WRITE_ALLOW_WRITE lock waiting for WRITE_ALLOW_WRITE lock\n", + where); + } + } + if (lock->read.data) + { + if ((!pthread_equal(lock->write.data->thread,lock->read.data->thread) && + lock->write.data->type > TL_WRITE_DELAYED) || + ((lock->write.data->type == TL_WRITE_CONCURRENT_INSERT || + lock->write.data->type == TL_WRITE_ALLOW_WRITE) && + lock->read_no_write_count)) + { + found_errors=1; + fprintf(stderr, + "Warning at '%s': Found lock that is write and read locked\n", + where); + } + } + if (lock->read_wait.data) + { + if (!allow_no_locks && lock->write.data->type <= TL_WRITE_DELAYED && + lock->read_wait.data->type <= TL_READ_HIGH_PRIORITY) + { + found_errors=1; + fprintf(stderr, + "Warning at '%s': Found read lock of type %d waiting for write lock of type %d\n", + where, + (int) lock->read_wait.data->type, + (int) lock->write.data->type); + } + } + } + } + if (found_errors) + { + DBUG_PRINT("error",("Found wrong lock")); + } + } +} + +#else /* EXTRA_DEBUG */ +#define check_locks(A,B,C) +#endif + + + /* Initialize a lock */ + +void thr_lock_init(THR_LOCK *lock) +{ + DBUG_ENTER("thr_lock_init"); + bzero((char*) lock,sizeof(*lock)); + VOID(pthread_mutex_init(&lock->mutex,NULL)); + lock->read.last= &lock->read.data; + lock->read_wait.last= &lock->read_wait.data; + lock->write_wait.last= &lock->write_wait.data; + lock->write.last= &lock->write.data; + + pthread_mutex_lock(&THR_LOCK_lock); /* Add to locks in use */ + lock->list.data=(void*) lock; + thread_list=list_add(thread_list,&lock->list); + pthread_mutex_unlock(&THR_LOCK_lock); + DBUG_VOID_RETURN; +} + + +void thr_lock_delete(THR_LOCK *lock) +{ + DBUG_ENTER("thr_lock_delete"); + VOID(pthread_mutex_destroy(&lock->mutex)); + pthread_mutex_lock(&THR_LOCK_lock); + thread_list=list_delete(thread_list,&lock->list); + pthread_mutex_unlock(&THR_LOCK_lock); + DBUG_VOID_RETURN; +} + + /* Initialize a lock instance */ + +void thr_lock_data_init(THR_LOCK *lock,THR_LOCK_DATA *data, void *param) +{ + data->lock=lock; + data->type=TL_UNLOCK; + data->thread=pthread_self(); + data->thread_id=my_thread_id(); /* for debugging */ + data->status_param=param; +} + + +static inline my_bool have_old_read_lock(THR_LOCK_DATA *data,pthread_t thread) +{ + for ( ; data ; data=data->next) + { + if ((pthread_equal(data->thread,thread))) + return 1; /* Already locked by thread */ + } + return 0; +} + +static inline my_bool have_specific_lock(THR_LOCK_DATA *data, + enum thr_lock_type type) +{ + for ( ; data ; data=data->next) + { + if (data->type == type) + return 1; + } + return 0; +} + + +static my_bool wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, + my_bool in_wait_list) +{ + pthread_cond_t *cond=get_cond(); + struct st_my_thread_var *thread_var=my_thread_var; + int result; + + if (!in_wait_list) + { + (*wait->last)=data; /* Wait for lock */ + data->prev= wait->last; + wait->last= &data->next; + } + + /* Set up control struct to allow others to abort locks */ + pthread_mutex_lock(&thread_var->mutex); + thread_var->current_mutex= &data->lock->mutex; + thread_var->current_cond= cond; + pthread_mutex_unlock(&thread_var->mutex); + + data->cond=cond; + do + { + pthread_cond_wait(cond,&data->lock->mutex); + } while (data->cond == cond && !thread_var->abort); + + if (data->cond || data->type == TL_UNLOCK) + { + if (data->cond) /* aborted */ + { + if (((*data->prev)=data->next)) /* remove from wait-list */ + data->next->prev= data->prev; + else + wait->last=data->prev; + } + data->type=TL_UNLOCK; /* No lock */ + result=1; /* Didn't get lock */ + check_locks(data->lock,"failed wait_for_lock",0); + } + else + { + result=0; + if (data->lock->get_status) + (*data->lock->get_status)(data->status_param); + check_locks(data->lock,"got wait_for_lock",0); + } + pthread_mutex_unlock(&data->lock->mutex); + + /* The following must be done after unlock of lock->mutex */ + pthread_mutex_lock(&thread_var->mutex); + thread_var->current_mutex= 0; + thread_var->current_cond= 0; + pthread_mutex_unlock(&thread_var->mutex); + return result; +} + + +int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type) +{ + THR_LOCK *lock=data->lock; + int result=0; + DBUG_ENTER("thr_lock"); + + data->next=0; + data->type=lock_type; + data->thread=pthread_self(); /* Must be reset ! */ + data->thread_id=my_thread_id(); /* Must be reset ! */ + VOID(pthread_mutex_lock(&lock->mutex)); + DBUG_PRINT("lock",("data: %lx thread: %ld lock: %lx type: %d", + data,data->thread_id,lock,(int) lock_type)); + check_locks(lock,(uint) lock_type <= (uint) TL_READ_NO_INSERT ? + "enter read_lock" : "enter write_lock",0); + if ((int) lock_type <= (int) TL_READ_NO_INSERT) + { + /* Request for READ lock */ + if (lock->write.data) + { + /* We can get allow a read lock even if there is already a write lock + one the table in one the following cases: + - This thread alread have a write lock on the table + - The write lock is TL_WRITE_ALLOW_READ or TL_WRITE_DELAYED + and the read lock is TL_READ_HIGH_PRIORITY or TL_READ + - The write lock is TL_WRITE_CONCURRENT_INSERT or TL_WRITE_ALLOW_WRITE + and the read lock is not TL_READ_NO_INSERT + */ + + DBUG_PRINT("lock",("write locked by thread: %ld", + lock->write.data->thread_id)); + if (pthread_equal(data->thread,lock->write.data->thread) || + (lock->write.data->type <= TL_WRITE_DELAYED && + (((int) lock_type <= (int) TL_READ_HIGH_PRIORITY) || + (lock->write.data->type != TL_WRITE_CONCURRENT_INSERT && + lock->write.data->type != TL_WRITE_ALLOW_READ)))) + { /* Already got a write lock */ + (*lock->read.last)=data; /* Add to running FIFO */ + data->prev=lock->read.last; + lock->read.last= &data->next; + if ((int) lock_type == (int) TL_READ_NO_INSERT) + lock->read_no_write_count++; + check_locks(lock,"read lock with old write lock",0); + if (lock->get_status) + (*lock->get_status)(data->status_param); + goto end; + } + if (lock->write.data->type == TL_WRITE_ONLY) + { + /* We are not allowed to get a READ lock in this case */ + data->type=TL_UNLOCK; + result=1; /* Can't wait for this one */ + goto end; + } + } + else if (!lock->write_wait.data || + lock->write_wait.data->type <= TL_WRITE_LOW_PRIORITY || + lock_type == TL_READ_HIGH_PRIORITY || + have_old_read_lock(lock->read.data,data->thread)) + { /* No important write-locks */ + (*lock->read.last)=data; /* Add to running FIFO */ + data->prev=lock->read.last; + lock->read.last= &data->next; + if (lock->get_status) + (*lock->get_status)(data->status_param); + if ((int) lock_type == (int) TL_READ_NO_INSERT) + lock->read_no_write_count++; + check_locks(lock,"read lock with no write locks",0); + goto end; + } + /* Can't get lock yet; Wait for it */ + DBUG_RETURN(wait_for_lock(&lock->read_wait,data,0)); + } + else /* Request for WRITE lock */ + { + if (lock_type == TL_WRITE_DELAYED) + { + if (lock->write.data && lock->write.data->type == TL_WRITE_ONLY) + { + data->type=TL_UNLOCK; + result=1; /* Can't wait for this one */ + goto end; + } + /* + if there is a TL_WRITE_ALLOW_READ lock, we have to wait for a lock + (TL_WRITE_ALLOW_READ is used for ALTER TABLE in MySQL) + */ + if ((!lock->write.data || + lock->write.data->type != TL_WRITE_ALLOW_READ) && + !have_specific_lock(lock->write_wait.data,TL_WRITE_ALLOW_READ) && + (lock->write.data || lock->read.data)) + { + /* Add delayed write lock to write_wait queue, and return at once */ + (*lock->write_wait.last)=data; + data->prev=lock->write_wait.last; + lock->write_wait.last= &data->next; + data->cond=get_cond(); + if (lock->get_status) + (*lock->get_status)(data->status_param); + goto end; + } + } + else if (lock_type == TL_WRITE_CONCURRENT_INSERT && ! lock->check_status) + data->type=lock_type= TL_WRITE; /* not supported */ + + if (lock->write.data) /* If there is a write lock */ + { + if (lock->write.data->type == TL_WRITE_ONLY) + { + /* We are not allowed to get a lock in this case */ + data->type=TL_UNLOCK; + result=1; /* Can't wait for this one */ + goto end; + } + + /* + The following test will not work if the old lock was a + TL_WRITE_ALLOW_WRITE, TL_WRITE_ALLOW_READ or TL_WRITE_DELAYED in + the same thread, but this will never happen within MySQL. + */ + if (pthread_equal(data->thread,lock->write.data->thread) || + (lock_type == TL_WRITE_ALLOW_WRITE && + !lock->write_wait.data && + lock->write.data->type == TL_WRITE_ALLOW_WRITE)) + { + /* We have already got a write lock or all locks are + TL_WRITE_ALLOW_WRITE */ + (*lock->write.last)=data; /* Add to running fifo */ + data->prev=lock->write.last; + lock->write.last= &data->next; + check_locks(lock,"second write lock",0); + if (data->lock->get_status) + (*data->lock->get_status)(data->status_param); + goto end; + } + DBUG_PRINT("lock",("write locked by thread: %ld", + lock->write.data->thread_id)); + } + else + { + if (!lock->write_wait.data) + { /* no scheduled write locks */ + if (lock_type == TL_WRITE_CONCURRENT_INSERT && + (*lock->check_status)(data->status_param)) + data->type=lock_type=TL_WRITE; /* Upgrade lock */ + + if (!lock->read.data || + (lock_type <= TL_WRITE_DELAYED && + ((lock_type != TL_WRITE_CONCURRENT_INSERT && + lock_type != TL_WRITE_ALLOW_WRITE) || + !lock->read_no_write_count))) + { + (*lock->write.last)=data; /* Add as current write lock */ + data->prev=lock->write.last; + lock->write.last= &data->next; + if (data->lock->get_status) + (*data->lock->get_status)(data->status_param); + check_locks(lock,"only write lock",0); + goto end; + } + } + DBUG_PRINT("lock",("write locked by thread: %ld, type: %ld", + lock->read.data->thread_id,data->type)); + } + DBUG_RETURN(wait_for_lock(&lock->write_wait,data,0)); + } +end: + pthread_mutex_unlock(&lock->mutex); + DBUG_RETURN(result); +} + + +static inline void free_all_read_locks(THR_LOCK *lock, + bool using_concurrent_insert) +{ + THR_LOCK_DATA *data=lock->read_wait.data; + + check_locks(lock,"before freeing read locks",1); + + /* move all locks from read_wait list to read list */ + (*lock->read.last)=data; + data->prev=lock->read.last; + lock->read.last=lock->read_wait.last; + + /* Clear read_wait list */ + lock->read_wait.last= &lock->read_wait.data; + + do + { + pthread_cond_t *cond=data->cond; + if ((int) data->type == (int) TL_READ_NO_INSERT) + { + if (using_concurrent_insert) + { + /* + We can't free this lock; + Link lock away from read chain back into read_wait chain + */ + if (((*data->prev)=data->next)) + data->next->prev=data->prev; + else + lock->read.last=data->prev; + *lock->read_wait.last= data; + data->prev= lock->read_wait.last; + lock->read_wait.last= &data->next; + continue; + } + lock->read_no_write_count++; + } + DBUG_PRINT("lock",("giving read lock to thread: %ld", + data->thread_id)); + data->cond=0; /* Mark thread free */ + VOID(pthread_cond_signal(cond)); + } while ((data=data->next)); + *lock->read_wait.last=0; + if (!lock->read_wait.data) + lock->write_lock_count=0; + check_locks(lock,"after giving read locks",0); +} + + /* Unlock lock and free next thread on same lock */ + +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: %lx thread: %ld lock: %lx", + data,data->thread_id,lock)); + pthread_mutex_lock(&lock->mutex); + check_locks(lock,"start of release lock",0); + + if (((*data->prev)=data->next)) /* remove from lock-list */ + data->next->prev= data->prev; + else if (lock_type <= TL_READ_NO_INSERT) + lock->read.last=data->prev; + else if (lock_type == TL_WRITE_DELAYED && data->cond) + { + /* This only happens in extreme circumstances when a + write delayed lock that is waiting for a lock */ + lock->write_wait.last=data->prev; /* Put it on wait queue */ + } + else + lock->write.last=data->prev; + if (lock_type >= TL_WRITE_CONCURRENT_INSERT && lock->update_status) + (*lock->update_status)(data->status_param); + if (lock_type == TL_READ_NO_INSERT) + lock->read_no_write_count--; + data->type=TL_UNLOCK; /* Mark unlocked */ + check_locks(lock,"after releasing lock",1); + + if (!lock->write.data) /* If no active read locks */ + { + data=lock->write_wait.data; + if (!lock->read.data) /* If no more locks in use */ + { + /* Release write-locks with TL_WRITE or TL_WRITE_ONLY priority first */ + if (data && + (data->type != TL_WRITE_LOW_PRIORITY || !lock->read_wait.data || + lock->read_wait.data->type == TL_READ)) + { + if (lock->write_lock_count++ > max_write_lock_count) + { + /* Too many write locks in a row; Release all waiting read locks */ + lock->write_lock_count=0; + if (lock->read_wait.data) + { + DBUG_PRINT("info",("Freeing all read_locks because of max_write_lock_count")); + free_all_read_locks(lock,0); + goto end; + } + } + for (;;) + { + if (((*data->prev)=data->next)) /* remove from wait-list */ + data->next->prev= data->prev; + else + lock->write_wait.last=data->prev; + (*lock->write.last)=data; /* Put in execute list */ + data->prev=lock->write.last; + data->next=0; + lock->write.last= &data->next; + 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", + data->type,data->thread_id)); + { + pthread_cond_t *cond=data->cond; + data->cond=0; /* Mark thread free */ + VOID(pthread_cond_signal(cond)); /* Start waiting thread */ + } + if (data->type != TL_WRITE_ALLOW_WRITE || + !lock->write_wait.data || + lock->write_wait.data->type != TL_WRITE_ALLOW_WRITE) + break; + data=lock->write_wait.data; /* Free this too */ + } + if (data->type >= TL_WRITE_LOW_PRIORITY) + { + check_locks(lock,"giving write lock",0); + pthread_mutex_unlock(&lock->mutex); + DBUG_VOID_RETURN; + } + /* Release possible read locks together with the write lock */ + } + if (lock->read_wait.data) + free_all_read_locks(lock, + data && + (data->type == TL_WRITE_CONCURRENT_INSERT || + data->type == TL_WRITE_ALLOW_WRITE)); + else + { + DBUG_PRINT("lock",("No locks to free")); + } + } + else if (data && + (lock_type=data->type) <= TL_WRITE_DELAYED && + ((lock_type != TL_WRITE_CONCURRENT_INSERT && + lock_type != TL_WRITE_ALLOW_WRITE) || + !lock->read_no_write_count)) + { + /* + For DELAYED, ALLOW_READ, WRITE_ALLOW_WRITE or CONCURRENT_INSERT locks + start WRITE locks together with the READ locks + */ + if (lock_type == TL_WRITE_CONCURRENT_INSERT && + (*lock->check_status)(data->status_param)) + { + data->type=TL_WRITE; /* Upgrade lock */ + if (lock->read_wait.data) + free_all_read_locks(lock,0); + goto end; + } + do { + pthread_cond_t *cond=data->cond; + if (((*data->prev)=data->next)) /* remove from wait-list */ + data->next->prev= data->prev; + else + lock->write_wait.last=data->prev; + (*lock->write.last)=data; /* Put in execute list */ + data->prev=lock->write.last; + lock->write.last= &data->next; + data->next=0; /* Only one write lock */ + data->cond=0; /* Mark thread free */ + VOID(pthread_cond_signal(cond)); /* Start waiting thread */ + } while (lock_type == TL_WRITE_ALLOW_WRITE && + (data=lock->write_wait.data) && + data->type == TL_WRITE_ALLOW_WRITE); + if (lock->read_wait.data) + free_all_read_locks(lock, + (lock_type == TL_WRITE_CONCURRENT_INSERT || + lock_type == TL_WRITE_ALLOW_WRITE)); + } + else if (lock->read_wait.data) + free_all_read_locks(lock,0); + } +end: + check_locks(lock,"thr_unlock",0); + pthread_mutex_unlock(&lock->mutex); + DBUG_VOID_RETURN; +} + + +/* +** Get all locks in a specific order to avoid dead-locks +** Sort acording to lock position and put write_locks before read_locks if +** lock on same lock. +*/ + + +#define LOCK_CMP(A,B) ((byte*) (A->lock) - (uint) ((A)->type) < (byte*) (B->lock)- (uint) ((B)->type)) + +static void sort_locks(THR_LOCK_DATA **data,uint count) +{ + THR_LOCK_DATA **pos,**end,**prev,*tmp; + + /* Sort locks with insertion sort (fast because almost always few locks) */ + + for (pos=data+1,end=data+count; pos < end ; pos++) + { + tmp= *pos; + if (LOCK_CMP(tmp,pos[-1])) + { + prev=pos; + do { + prev[0]=prev[-1]; + } while (--prev != data && LOCK_CMP(tmp,prev[-1])); + prev[0]=tmp; + } + } +} + + +int thr_multi_lock(THR_LOCK_DATA **data,uint count) +{ + THR_LOCK_DATA **pos,**end; + DBUG_ENTER("thr_multi_lock"); + DBUG_PRINT("lock",("data: %lx count: %d",data,count)); + if (count > 1) + sort_locks(data,count); + /* lock everything */ + for (pos=data,end=data+count; pos < end ; pos++) + { + if (thr_lock(*pos,(*pos)->type)) + { /* Aborted */ + thr_multi_unlock(data,(uint) (pos-data)); + DBUG_RETURN(1); + } +#ifdef MAIN + printf("Thread: %s Got lock: %lx type: %d\n",my_thread_name(), + (long) pos[0]->lock, pos[0]->type); fflush(stdout); +#endif + } + /* + Ensure that all get_locks() have the same status + If we lock the same table multiple times, we must use the same + status_param! + */ +#if !defined(DONT_USE_RW_LOCKS) + if (count > 1) + { + THR_LOCK_DATA *last_lock= end[-1]; + pos=end-1; + do + { + pos--; + if (last_lock->lock == (*pos)->lock && + last_lock->lock->copy_status) + { + if (last_lock->type <= TL_READ_NO_INSERT) + { + THR_LOCK_DATA **read_lock; + /* + If we are locking the same table with read locks we must ensure + that all tables share the status of the last write lock or + the same read lock. + */ + for (; + (*pos)->type <= TL_READ_NO_INSERT && + pos != data && + pos[-1]->lock == (*pos)->lock ; + pos--) ; + + read_lock = pos+1; + do + { + (last_lock->lock->copy_status)((*read_lock)->status_param, + (*pos)->status_param); + } while (*(read_lock++) != last_lock); + last_lock= (*pos); /* Point at last write lock */ + } + else + (*last_lock->lock->copy_status)((*pos)->status_param, + last_lock->status_param); + } + else + last_lock=(*pos); + } while (pos != data); + } +#endif + DBUG_RETURN(0); +} + + /* free all locks */ + +void thr_multi_unlock(THR_LOCK_DATA **data,uint count) +{ + THR_LOCK_DATA **pos,**end; + DBUG_ENTER("thr_multi_unlock"); + DBUG_PRINT("lock",("data: %lx count: %d",data,count)); + + for (pos=data,end=data+count; pos < end ; pos++) + { +#ifdef MAIN + printf("Thread: %s Rel lock: %lx type: %d\n", + my_thread_name(), (long) pos[0]->lock, pos[0]->type); + fflush(stdout); +#endif + if ((*pos)->type != TL_UNLOCK) + thr_unlock(*pos); + else + { + DBUG_PRINT("lock",("Free lock: data: %lx thread: %ld lock: %lx", + *pos,(*pos)->thread_id,(*pos)->lock)); + } + } + DBUG_VOID_RETURN; +} + +/* Abort all threads waiting for a lock. The lock will be upgraded to a + TL_WRITE_ONLY to abort any new accesses to the lock +*/ + +void thr_abort_locks(THR_LOCK *lock) +{ + THR_LOCK_DATA *data; + DBUG_ENTER("thr_abort_locks"); + pthread_mutex_lock(&lock->mutex); + + for (data=lock->read_wait.data; data ; data=data->next) + { + data->type=TL_UNLOCK; /* Mark killed */ + pthread_cond_signal(data->cond); + data->cond=0; /* Removed from list */ + } + for (data=lock->write_wait.data; data ; data=data->next) + { + data->type=TL_UNLOCK; + pthread_cond_signal(data->cond); + data->cond=0; + } + 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) + lock->write.data->type=TL_WRITE_ONLY; + 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) +{ + THR_LOCK *lock=data->lock; + DBUG_ENTER("thr_upgrade_write_delay_lock"); + + pthread_mutex_lock(&lock->mutex); + if (data->type == TL_UNLOCK || data->type == TL_WRITE) /* Aborted */ + { + pthread_mutex_unlock(&lock->mutex); + DBUG_RETURN(data->type == TL_UNLOCK); + } + check_locks(lock,"before upgrading lock",0); + /* TODO: Upgrade to TL_WRITE_CONCURRENT_INSERT in some cases */ + data->type=TL_WRITE; /* Upgrade lock */ + + /* Check if someone has given us the lock */ + if (!data->cond) + { + if (!lock->read.data) /* No read locks */ + { /* We have the lock */ + if (data->lock->get_status) + (*data->lock->get_status)(data->status_param); + pthread_mutex_unlock(&lock->mutex); + DBUG_RETURN(0); + } + + if (((*data->prev)=data->next)) /* remove from lock-list */ + data->next->prev= data->prev; + else + lock->write.last=data->prev; + + if ((data->next=lock->write_wait.data)) /* Put first in lock_list */ + data->next->prev= &data->next; + else + lock->write_wait.last= &data->next; + data->prev= &lock->write_wait.data; + lock->write_wait.data=data; + check_locks(lock,"upgrading lock",0); + } + DBUG_RETURN(wait_for_lock(&lock->write_wait,data,1)); +} + + +/* downgrade a WRITE lock to a WRITE_DELAY lock if there is pending locks */ + +my_bool thr_reschedule_write_lock(THR_LOCK_DATA *data) +{ + THR_LOCK *lock=data->lock; + DBUG_ENTER("thr_reschedule_write_lock"); + + pthread_mutex_lock(&lock->mutex); + if (!lock->read_wait.data) /* No waiting read locks */ + { + pthread_mutex_unlock(&lock->mutex); + DBUG_RETURN(0); + } + + data->type=TL_WRITE_DELAYED; + if (lock->update_status) + (*lock->update_status)(data->status_param); + if (((*data->prev)=data->next)) /* remove from lock-list */ + data->next->prev= data->prev; + else + lock->write.last=data->prev; + + if ((data->next=lock->write_wait.data)) /* Put first in lock_list */ + data->next->prev= &data->next; + else + lock->write_wait.last= &data->next; + data->prev= &lock->write_wait.data; + data->cond=get_cond(); /* This was zero */ + lock->write_wait.data=data; + free_all_read_locks(lock,0); + + pthread_mutex_unlock(&lock->mutex); + DBUG_RETURN(thr_upgrade_write_delay_lock(data)); +} + + +#include <my_sys.h> + +static void thr_print_lock(const char* name,struct st_lock_list *list) +{ + THR_LOCK_DATA *data,**prev; + uint count=0; + + if (list->data) + { + printf("%-10s: ",name); + prev= &list->data; + for (data=list->data; data && count++ < MAX_LOCKS ; data=data->next) + { + printf("%lx (%lu:%d); ",(ulong) data,data->thread_id,(int) data->type); + if (data->prev != prev) + printf("\nWarning: prev didn't point at previous lock\n"); + prev= &data->next; + } + puts(""); + if (prev != list->last) + printf("Warning: last didn't point at last lock\n"); + } +} + +void thr_print_locks(void) +{ + LIST *list; + uint count=0; + + pthread_mutex_lock(&THR_LOCK_lock); + puts("Current locks:"); + for (list=thread_list ; list && count++ < MAX_THREADS ; list=rest(list)) + { + THR_LOCK *lock=(THR_LOCK*) list->data; + VOID(pthread_mutex_lock(&lock->mutex)); + printf("lock: %lx:",(ulong) lock); + if ((lock->write_wait.data || lock->read_wait.data) && + (! lock->read.data && ! lock->write.data)) + printf(" WARNING: "); + if (lock->write.data) + printf(" write"); + if (lock->write_wait.data) + printf(" write_wait"); + if (lock->read.data) + printf(" read"); + if (lock->read_wait.data) + printf(" read_wait"); + puts(""); + thr_print_lock("write",&lock->write); + thr_print_lock("write_wait",&lock->write_wait); + thr_print_lock("read",&lock->read); + thr_print_lock("read_wait",&lock->read_wait); + VOID(pthread_mutex_unlock(&lock->mutex)); + puts(""); + } + fflush(stdout); + pthread_mutex_unlock(&THR_LOCK_lock); +} + +#ifdef MAIN + +struct st_test { + uint lock_nr; + enum thr_lock_type lock_type; +}; + +THR_LOCK locks[5]; /* 4 locks */ + +struct st_test test_0[] = {{0,TL_READ}}; /* One lock */ +struct st_test test_1[] = {{0,TL_READ},{0,TL_WRITE}}; /* Read and write lock of lock 0 */ +struct st_test test_2[] = {{1,TL_WRITE},{0,TL_READ},{2,TL_READ}}; +struct st_test test_3[] = {{2,TL_WRITE},{1,TL_READ},{0,TL_READ}}; /* Deadlock with test_2 ? */ +struct st_test test_4[] = {{0,TL_WRITE},{0,TL_READ},{0,TL_WRITE},{0,TL_READ}}; +struct st_test test_5[] = {{0,TL_READ},{1,TL_READ},{2,TL_READ},{3,TL_READ}}; /* Many reads */ +struct st_test test_6[] = {{0,TL_WRITE},{1,TL_WRITE},{2,TL_WRITE},{3,TL_WRITE}}; /* Many writes */ +struct st_test test_7[] = {{3,TL_READ}}; +struct st_test test_8[] = {{1,TL_READ_NO_INSERT},{2,TL_READ_NO_INSERT},{3,TL_READ_NO_INSERT}}; /* Should be quick */ +struct st_test test_9[] = {{4,TL_READ_HIGH_PRIORITY}}; +struct st_test test_10[] ={{4,TL_WRITE}}; +struct st_test test_11[] = {{0,TL_WRITE_LOW_PRIORITY},{1,TL_WRITE_LOW_PRIORITY},{2,TL_WRITE_LOW_PRIORITY},{3,TL_WRITE_LOW_PRIORITY}}; /* Many writes */ +struct st_test test_12[] = {{0,TL_WRITE_ALLOW_READ},{1,TL_WRITE_ALLOW_READ},{2,TL_WRITE_ALLOW_READ},{3,TL_WRITE_ALLOW_READ}}; /* Many writes */ +struct st_test test_13[] = {{0,TL_WRITE_CONCURRENT_INSERT},{1,TL_WRITE_CONCURRENT_INSERT},{2,TL_WRITE_CONCURRENT_INSERT},{3,TL_WRITE_CONCURRENT_INSERT}}; +struct st_test test_14[] = {{0,TL_WRITE_CONCURRENT_INSERT},{1,TL_READ}}; +struct st_test test_15[] = {{0,TL_WRITE_ALLOW_WRITE},{1,TL_READ}}; +struct st_test test_16[] = {{0,TL_WRITE_ALLOW_WRITE},{1,TL_WRITE_ALLOW_WRITE}}; + +struct st_test *tests[] = {test_0,test_1,test_2,test_3,test_4,test_5,test_6, + test_7,test_8,test_9,test_10,test_11,test_12, + test_13,test_14,test_15,test_16}; +int lock_counts[]= {sizeof(test_0)/sizeof(struct st_test), + sizeof(test_1)/sizeof(struct st_test), + sizeof(test_2)/sizeof(struct st_test), + sizeof(test_3)/sizeof(struct st_test), + sizeof(test_4)/sizeof(struct st_test), + sizeof(test_5)/sizeof(struct st_test), + sizeof(test_6)/sizeof(struct st_test), + sizeof(test_7)/sizeof(struct st_test), + sizeof(test_8)/sizeof(struct st_test), + sizeof(test_9)/sizeof(struct st_test), + sizeof(test_10)/sizeof(struct st_test), + sizeof(test_11)/sizeof(struct st_test), + sizeof(test_12)/sizeof(struct st_test), + sizeof(test_13)/sizeof(struct st_test), + sizeof(test_14)/sizeof(struct st_test), + sizeof(test_15)/sizeof(struct st_test), + sizeof(test_16)/sizeof(struct st_test) +}; + + +static pthread_cond_t COND_thread_count; +static pthread_mutex_t LOCK_thread_count; +static uint thread_count; +static ulong sum=0; + +#define MAX_LOCK_COUNT 8 + +/* The following functions is for WRITE_CONCURRENT_INSERT */ + +static void test_get_status(void* param __attribute__((unused))) +{ +} + +static void test_copy_status(void* to __attribute__((unused)) , + void *from __attribute__((unused))) +{ +} + +static my_bool test_check_status(void* param __attribute__((unused))) +{ + return 0; +} + + +static void *test_thread(void *arg) +{ + int i,j,param=*((int*) arg); + THR_LOCK_DATA data[MAX_LOCK_COUNT]; + THR_LOCK_DATA *multi_locks[MAX_LOCK_COUNT]; + my_thread_init(); + + printf("Thread %s (%d) started\n",my_thread_name(),param); fflush(stdout); + + for (i=0; i < lock_counts[param] ; i++) + thr_lock_data_init(locks+tests[param][i].lock_nr,data+i,NULL); + for (j=1 ; j < 10 ; j++) /* try locking 10 times */ + { + for (i=0; i < lock_counts[param] ; i++) + { /* Init multi locks */ + multi_locks[i]= &data[i]; + data[i].type= tests[param][i].lock_type; + } + thr_multi_lock(multi_locks,lock_counts[param]); + pthread_mutex_lock(&LOCK_thread_count); + { + int tmp=rand() & 7; /* Do something from 0-2 sec */ + if (tmp == 0) + sleep(1); + else if (tmp == 1) + sleep(2); + else + { + ulong k; + for (k=0 ; k < (ulong) (tmp-2)*100000L ; k++) + sum+=k; + } + } + pthread_mutex_unlock(&LOCK_thread_count); + thr_multi_unlock(multi_locks,lock_counts[param]); + } + + printf("Tread %s (%d) ended\n",my_thread_name(),param); fflush(stdout); + thr_print_locks(); + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + VOID(pthread_cond_signal(&COND_thread_count)); /* Tell main we are ready */ + pthread_mutex_unlock(&LOCK_thread_count); + free((gptr) arg); + return 0; +} + + +int main(int argc __attribute__((unused)),char **argv __attribute__((unused))) +{ + pthread_t tid; + pthread_attr_t thr_attr; + int i,*param,error; + MY_INIT(argv[0]); + if (argc > 1 && argv[1][0] == '-' && argv[1][1] == '#') + DBUG_PUSH(argv[1]+2); + + printf("Main thread: %s\n",my_thread_name()); + + if ((error=pthread_cond_init(&COND_thread_count,NULL))) + { + fprintf(stderr,"Got error: %d from pthread_cond_init (errno: %d)", + error,errno); + exit(1); + } + if ((error=pthread_mutex_init(&LOCK_thread_count,NULL))) + { + fprintf(stderr,"Got error: %d from pthread_cond_init (errno: %d)", + error,errno); + exit(1); + } + + for (i=0 ; i < (int) array_elements(locks) ; i++) + { + thr_lock_init(locks+i); + locks[i].check_status= test_check_status; + locks[i].update_status=test_get_status; + locks[i].copy_status= test_copy_status; + locks[i].get_status= test_get_status; + } + if ((error=pthread_attr_init(&thr_attr))) + { + fprintf(stderr,"Got error: %d from pthread_attr_init (errno: %d)", + error,errno); + exit(1); + } + if ((error=pthread_attr_setdetachstate(&thr_attr,PTHREAD_CREATE_DETACHED))) + { + fprintf(stderr, + "Got error: %d from pthread_attr_setdetachstate (errno: %d)", + error,errno); + exit(1); + } +#ifndef pthread_attr_setstacksize /* void return value */ + if ((error=pthread_attr_setstacksize(&thr_attr,65536L))) + { + fprintf(stderr,"Got error: %d from pthread_attr_setstacksize (errno: %d)", + error,errno); + exit(1); + } +#endif +#ifdef HAVE_THR_SETCONCURRENCY + VOID(thr_setconcurrency(2)); +#endif + for (i=0 ; i < (int) array_elements(lock_counts) ; i++) + { + param=(int*) malloc(sizeof(int)); + *param=i; + + if ((error=pthread_mutex_lock(&LOCK_thread_count))) + { + fprintf(stderr,"Got error: %d from pthread_mutex_lock (errno: %d)", + error,errno); + exit(1); + } + if ((error=pthread_create(&tid,&thr_attr,test_thread,(void*) param))) + { + fprintf(stderr,"Got error: %d from pthread_create (errno: %d)\n", + error,errno); + pthread_mutex_unlock(&LOCK_thread_count); + exit(1); + } + thread_count++; + pthread_mutex_unlock(&LOCK_thread_count); + } + + pthread_attr_destroy(&thr_attr); + if ((error=pthread_mutex_lock(&LOCK_thread_count))) + fprintf(stderr,"Got error: %d from pthread_mutex_lock\n",error); + while (thread_count) + { + if ((error=pthread_cond_wait(&COND_thread_count,&LOCK_thread_count))) + fprintf(stderr,"Got error: %d from pthread_cond_wait\n",error); + } + if ((error=pthread_mutex_unlock(&LOCK_thread_count))) + fprintf(stderr,"Got error: %d from pthread_mutex_unlock\n",error); + for (i=0 ; i < (int) array_elements(locks) ; i++) + thr_lock_delete(locks+i); +#ifdef EXTRA_DEBUG + if (found_errors) + printf("Got %d warnings\n",found_errors); + else +#endif + printf("Test succeeded\n"); + return 0; +} +#endif + +#endif |