diff options
34 files changed, 671 insertions, 147 deletions
diff --git a/include/thr_lock.h b/include/thr_lock.h index dc4f9968cb7..8c6540d83e2 100644 --- a/include/thr_lock.h +++ b/include/thr_lock.h @@ -62,17 +62,45 @@ enum thr_lock_type { TL_IGNORE=-1, /* Abort new lock request with an error */ TL_WRITE_ONLY}; +enum enum_thr_lock_result { THR_LOCK_SUCCESS= 0, THR_LOCK_ABORTED= 1, + THR_LOCK_WAIT_TIMEOUT= 2, THR_LOCK_DEADLOCK= 3 }; + + extern ulong max_write_lock_count; +extern ulong table_lock_wait_timeout; extern my_bool thr_lock_inited; extern enum thr_lock_type thr_upgraded_concurrent_insert_lock; -typedef struct st_thr_lock_data { +/* + A description of the thread which owns the lock. The address + of an instance of this structure is used to uniquely identify the thread. +*/ + +typedef struct st_thr_lock_info +{ pthread_t thread; + ulong thread_id; + ulong n_cursors; +} THR_LOCK_INFO; + +/* + Lock owner identifier. Globally identifies the lock owner within the + thread and among all the threads. The address of an instance of this + structure is used as id. +*/ + +typedef struct st_thr_lock_owner +{ + THR_LOCK_INFO *info; +} THR_LOCK_OWNER; + + +typedef struct st_thr_lock_data { + THR_LOCK_OWNER *owner; struct st_thr_lock_data *next,**prev; struct st_thr_lock *lock; pthread_cond_t *cond; enum thr_lock_type type; - ulong thread_id; void *status_param; /* Param to status functions */ void *debug_print_param; } THR_LOCK_DATA; @@ -102,13 +130,18 @@ extern LIST *thr_lock_thread_list; extern pthread_mutex_t THR_LOCK_lock; my_bool init_thr_lock(void); /* Must be called once/thread */ +#define thr_lock_owner_init(owner, info_arg) (owner)->info= (info_arg) +void thr_lock_info_init(THR_LOCK_INFO *info); void thr_lock_init(THR_LOCK *lock); void thr_lock_delete(THR_LOCK *lock); void thr_lock_data_init(THR_LOCK *lock,THR_LOCK_DATA *data, void *status_param); -int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type); +enum enum_thr_lock_result thr_lock(THR_LOCK_DATA *data, + THR_LOCK_OWNER *owner, + enum thr_lock_type lock_type); void thr_unlock(THR_LOCK_DATA *data); -int thr_multi_lock(THR_LOCK_DATA **data,uint count); +enum enum_thr_lock_result thr_multi_lock(THR_LOCK_DATA **data, + uint count, THR_LOCK_OWNER *owner); void thr_multi_unlock(THR_LOCK_DATA **data,uint count); void thr_abort_locks(THR_LOCK *lock); void thr_abort_locks_for_thread(THR_LOCK *lock, pthread_t thread); diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index 85a2b34b851..b12f8234c26 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -84,6 +84,7 @@ multiple read locks. my_bool thr_lock_inited=0; ulong locks_immediate = 0L, locks_waited = 0L; +ulong table_lock_wait_timeout; enum thr_lock_type thr_upgraded_concurrent_insert_lock = TL_WRITE; /* The following constants are only for debug output */ @@ -109,25 +110,32 @@ my_bool init_thr_lock() return 0; } +static inline my_bool +thr_lock_owner_equal(THR_LOCK_OWNER *rhs, THR_LOCK_OWNER *lhs) +{ + return rhs == lhs; +} + + #ifdef EXTRA_DEBUG #define MAX_FOUND_ERRORS 10 /* Report 10 first errors */ static uint found_errors=0; static int check_lock(struct st_lock_list *list, const char* lock_type, - const char *where, my_bool same_thread, bool no_cond) + const char *where, my_bool same_owner, bool no_cond) { THR_LOCK_DATA *data,**prev; uint count=0; - pthread_t first_thread; - LINT_INIT(first_thread); + THR_LOCK_OWNER *first_owner; + LINT_INIT(first_owner); 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; + if (same_owner && list->data) + first_owner= list->data->owner; for (data=list->data; data && count++ < MAX_LOCKS ; data=data->next) { if (data->type != last_lock_type) @@ -139,7 +147,8 @@ static int check_lock(struct st_lock_list *list, const char* lock_type, count, lock_type, where); return 1; } - if (same_thread && ! pthread_equal(data->thread,first_thread) && + if (same_owner && + !thr_lock_owner_equal(data->owner, first_owner) && last_lock_type != TL_WRITE_ALLOW_WRITE) { fprintf(stderr, @@ -255,8 +264,8 @@ static void check_locks(THR_LOCK *lock, const char *where, } if (lock->read.data) { - if (!pthread_equal(lock->write.data->thread, - lock->read.data->thread) && + if (!thr_lock_owner_equal(lock->write.data->owner, + lock->read.data->owner) && ((lock->write.data->type > TL_WRITE_DELAYED && lock->write.data->type != TL_WRITE_ONLY) || ((lock->write.data->type == TL_WRITE_CONCURRENT_INSERT || @@ -330,24 +339,32 @@ void thr_lock_delete(THR_LOCK *lock) DBUG_VOID_RETURN; } + +void thr_lock_info_init(THR_LOCK_INFO *info) +{ + info->thread= pthread_self(); + info->thread_id= my_thread_id(); /* for debugging */ + info->n_cursors= 0; +} + /* 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->owner= 0; /* no owner yet */ data->status_param=param; data->cond=0; } -static inline my_bool have_old_read_lock(THR_LOCK_DATA *data,pthread_t thread) +static inline my_bool +have_old_read_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner) { for ( ; data ; data=data->next) { - if ((pthread_equal(data->thread,thread))) + if (thr_lock_owner_equal(data->owner, owner)) return 1; /* Already locked by thread */ } return 0; @@ -365,12 +382,16 @@ static inline my_bool have_specific_lock(THR_LOCK_DATA *data, } -static my_bool wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, - my_bool in_wait_list) +static enum enum_thr_lock_result +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; + struct st_my_thread_var *thread_var= my_thread_var; + pthread_cond_t *cond= &thread_var->suspend; + struct timeval now; + struct timespec wait_timeout; + enum enum_thr_lock_result result= THR_LOCK_ABORTED; + my_bool can_deadlock= test(data->owner->info->n_cursors); if (!in_wait_list) { @@ -382,31 +403,56 @@ static my_bool wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, /* Set up control struct to allow others to abort locks */ thread_var->current_mutex= &data->lock->mutex; thread_var->current_cond= cond; + data->cond= cond; - data->cond=cond; + if (can_deadlock) + { + gettimeofday(&now, 0); + wait_timeout.tv_sec= now.tv_sec + table_lock_wait_timeout; + wait_timeout.tv_nsec= now.tv_usec * 1000; + } while (!thread_var->abort || in_wait_list) { - pthread_cond_wait(cond,&data->lock->mutex); - if (data->cond != cond) + int rc= can_deadlock ? pthread_cond_timedwait(cond, &data->lock->mutex, + &wait_timeout) : + pthread_cond_wait(cond, &data->lock->mutex); + /* + We must break the wait if one of the following occurs: + - the connection has been aborted (!thread_var->abort), but + this is not a delayed insert thread (in_wait_list). For a delayed + insert thread the proper action at shutdown is, apparently, to + acquire the lock and complete the insert. + - the lock has been granted (data->cond is set to NULL by the granter), + or the waiting has been aborted (additionally data->type is set to + TL_UNLOCK). + - the wait has timed out (rc == ETIMEDOUT) + Order of checks below is important to not report about timeout + if the predicate is true. + */ + if (data->cond == 0) + break; + if (rc == ETIMEDOUT) + { + result= THR_LOCK_WAIT_TIMEOUT; break; + } } if (data->cond || data->type == TL_UNLOCK) { - if (data->cond) /* aborted */ + if (data->cond) /* aborted or timed out */ { 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 */ } - data->type=TL_UNLOCK; /* No lock */ - result=1; /* Didn't get lock */ check_locks(data->lock,"failed wait_for_lock",0); } else { - result=0; + result= THR_LOCK_SUCCESS; statistic_increment(locks_waited, &THR_LOCK_lock); if (data->lock->get_status) (*data->lock->get_status)(data->status_param, 0); @@ -423,20 +469,24 @@ static my_bool wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, } -int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type) +enum enum_thr_lock_result +thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, + enum thr_lock_type lock_type) { THR_LOCK *lock=data->lock; - int result=0; + enum enum_thr_lock_result result= THR_LOCK_SUCCESS; + struct st_lock_list *wait_queue; + THR_LOCK_DATA *lock_owner; DBUG_ENTER("thr_lock"); data->next=0; data->cond=0; /* safety */ data->type=lock_type; - data->thread=pthread_self(); /* Must be reset ! */ - data->thread_id=my_thread_id(); /* Must be reset ! */ + 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", - data,data->thread_id,lock,(int) lock_type)); + data, data->owner->info->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) @@ -454,8 +504,8 @@ int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type) */ 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->owner->info->thread_id)); + if (thr_lock_owner_equal(data->owner, lock->write.data->owner) || (lock->write.data->type <= TL_WRITE_DELAYED && (((int) lock_type <= (int) TL_READ_HIGH_PRIORITY) || (lock->write.data->type != TL_WRITE_CONCURRENT_INSERT && @@ -476,14 +526,14 @@ int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type) { /* We are not allowed to get a READ lock in this case */ data->type=TL_UNLOCK; - result=1; /* Can't wait for this one */ + result= THR_LOCK_ABORTED; /* 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)) + have_old_read_lock(lock->read.data, data->owner)) { /* No important write-locks */ (*lock->read.last)=data; /* Add to running FIFO */ data->prev=lock->read.last; @@ -496,8 +546,12 @@ int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type) statistic_increment(locks_immediate,&THR_LOCK_lock); goto end; } - /* Can't get lock yet; Wait for it */ - DBUG_RETURN(wait_for_lock(&lock->read_wait,data,0)); + /* + We're here if there is an active write lock or no write + lock but a high priority write waiting in the write_wait queue. + In the latter case we should yield the lock to the writer. + */ + wait_queue= &lock->read_wait; } else /* Request for WRITE lock */ { @@ -506,7 +560,7 @@ int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type) if (lock->write.data && lock->write.data->type == TL_WRITE_ONLY) { data->type=TL_UNLOCK; - result=1; /* Can't wait for this one */ + result= THR_LOCK_ABORTED; /* Can't wait for this one */ goto end; } /* @@ -540,7 +594,7 @@ int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type) { /* We are not allowed to get a lock in this case */ data->type=TL_UNLOCK; - result=1; /* Can't wait for this one */ + result= THR_LOCK_ABORTED; /* Can't wait for this one */ goto end; } @@ -549,7 +603,7 @@ int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type) 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) || + if (thr_lock_owner_equal(data->owner, lock->write.data->owner) || (lock_type == TL_WRITE_ALLOW_WRITE && !lock->write_wait.data && lock->write.data->type == TL_WRITE_ALLOW_WRITE)) @@ -572,7 +626,7 @@ int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type) goto end; } DBUG_PRINT("lock",("write locked by thread: %ld", - lock->write.data->thread_id)); + lock->write.data->owner->info->thread_id)); } else { @@ -608,10 +662,24 @@ int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type) } } DBUG_PRINT("lock",("write locked by thread: %ld, type: %ld", - lock->read.data->thread_id,data->type)); + lock->read.data->owner->info->thread_id, data->type)); } - DBUG_RETURN(wait_for_lock(&lock->write_wait,data,0)); + wait_queue= &lock->write_wait; } + /* + Try to detect a trivial deadlock when using cursors: attempt to + lock a table that is already locked by an open cursor within the + same connection. lock_owner can be zero if we succumbed to a high + priority writer in the write_wait queue. + */ + lock_owner= lock->read.data ? lock->read.data : lock->write.data; + if (lock_owner && lock_owner->owner->info == owner->info) + { + result= THR_LOCK_DEADLOCK; + goto end; + } + /* Can't get lock yet; Wait for it */ + DBUG_RETURN(wait_for_lock(wait_queue, data, 0)); end: pthread_mutex_unlock(&lock->mutex); DBUG_RETURN(result); @@ -656,7 +724,7 @@ static inline void free_all_read_locks(THR_LOCK *lock, lock->read_no_write_count++; } DBUG_PRINT("lock",("giving read lock to thread: %ld", - data->thread_id)); + data->owner->info->thread_id)); data->cond=0; /* Mark thread free */ VOID(pthread_cond_signal(cond)); } while ((data=data->next)); @@ -674,7 +742,7 @@ void thr_unlock(THR_LOCK_DATA *data) enum thr_lock_type lock_type=data->type; DBUG_ENTER("thr_unlock"); DBUG_PRINT("lock",("data: 0x%lx thread: %ld lock: 0x%lx", - data,data->thread_id,lock)); + data, data->owner->info->thread_id, lock)); pthread_mutex_lock(&lock->mutex); check_locks(lock,"start of release lock",0); @@ -734,7 +802,7 @@ void thr_unlock(THR_LOCK_DATA *data) (*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)); + data->type, data->owner->info->thread_id)); { pthread_cond_t *cond=data->cond; data->cond=0; /* Mark thread free */ @@ -842,7 +910,8 @@ static void sort_locks(THR_LOCK_DATA **data,uint count) } -int thr_multi_lock(THR_LOCK_DATA **data,uint count) +enum enum_thr_lock_result +thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner) { THR_LOCK_DATA **pos,**end; DBUG_ENTER("thr_multi_lock"); @@ -852,10 +921,11 @@ int thr_multi_lock(THR_LOCK_DATA **data,uint count) /* lock everything */ for (pos=data,end=data+count; pos < end ; pos++) { - if (thr_lock(*pos,(*pos)->type)) + enum enum_thr_lock_result result= thr_lock(*pos, owner, (*pos)->type); + if (result != THR_LOCK_SUCCESS) { /* Aborted */ thr_multi_unlock(data,(uint) (pos-data)); - DBUG_RETURN(1); + DBUG_RETURN(result); } #ifdef MAIN printf("Thread: %s Got lock: 0x%lx type: %d\n",my_thread_name(), @@ -909,7 +979,7 @@ int thr_multi_lock(THR_LOCK_DATA **data,uint count) } while (pos != data); } #endif - DBUG_RETURN(0); + DBUG_RETURN(THR_LOCK_SUCCESS); } /* free all locks */ @@ -932,7 +1002,7 @@ void thr_multi_unlock(THR_LOCK_DATA **data,uint count) else { DBUG_PRINT("lock",("Free lock: data: 0x%lx thread: %ld lock: 0x%lx", - *pos,(*pos)->thread_id,(*pos)->lock)); + *pos, (*pos)->owner->info->thread_id, (*pos)->lock)); } } DBUG_VOID_RETURN; @@ -952,6 +1022,7 @@ void thr_abort_locks(THR_LOCK *lock) for (data=lock->read_wait.data; data ; data=data->next) { data->type=TL_UNLOCK; /* Mark killed */ + /* It's safe to signal the cond first: we're still holding the mutex. */ pthread_cond_signal(data->cond); data->cond=0; /* Removed from list */ } @@ -985,10 +1056,11 @@ void 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->thread)) + if (pthread_equal(thread, data->owner->info->thread)) { DBUG_PRINT("info",("Aborting read-wait lock")); data->type= TL_UNLOCK; /* Mark killed */ + /* It's safe to signal the cond first: we're still holding the mutex. */ pthread_cond_signal(data->cond); data->cond= 0; /* Removed from list */ @@ -1000,7 +1072,7 @@ void 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->thread)) + if (pthread_equal(thread, data->owner->info->thread)) { DBUG_PRINT("info",("Aborting write-wait lock")); data->type= TL_UNLOCK; @@ -1117,7 +1189,8 @@ static void thr_print_lock(const char* name,struct st_lock_list *list) prev= &list->data; for (data=list->data; data && count++ < MAX_LOCKS ; data=data->next) { - printf("0x%lx (%lu:%d); ",(ulong) data,data->thread_id,(int) data->type); + printf("0x%lx (%lu:%d); ", (ulong) data, data->owner->info->thread_id, + (int) data->type); if (data->prev != prev) printf("\nWarning: prev didn't point at previous lock\n"); prev= &data->next; @@ -1250,11 +1323,16 @@ static void *test_thread(void *arg) { int i,j,param=*((int*) arg); THR_LOCK_DATA data[MAX_LOCK_COUNT]; + THR_LOCK_OWNER owner; + THR_LOCK_INFO lock_info; THR_LOCK_DATA *multi_locks[MAX_LOCK_COUNT]; my_thread_init(); printf("Thread %s (%d) started\n",my_thread_name(),param); fflush(stdout); + + thr_lock_info_init(&lock_info); + thr_lock_owner_init(&owner, &lock_info); 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 */ @@ -1264,7 +1342,7 @@ static void *test_thread(void *arg) multi_locks[i]= &data[i]; data[i].type= tests[param][i].lock_type; } - thr_multi_lock(multi_locks,lock_counts[param]); + thr_multi_lock(multi_locks, lock_counts[param], &owner); pthread_mutex_lock(&LOCK_thread_count); { int tmp=rand() & 7; /* Do something from 0-2 sec */ diff --git a/sql/examples/ha_archive.cc b/sql/examples/ha_archive.cc index c362985f565..e5c35ae019d 100644 --- a/sql/examples/ha_archive.cc +++ b/sql/examples/ha_archive.cc @@ -149,7 +149,8 @@ static handlerton archive_hton = { 0, /* prepare */ 0, /* recover */ 0, /* commit_by_xid */ - 0 /* rollback_by_xid */ + 0, /* rollback_by_xid */ + HTON_NO_FLAGS }; @@ -208,6 +209,15 @@ bool archive_db_end() return FALSE; } +ha_archive::ha_archive(TABLE *table_arg) + :handler(&archive_hton, table_arg), delayed_insert(0), bulk_insert(0) +{ + /* Set our original buffer from pre-allocated memory */ + buffer.set((char *)byte_buffer, IO_SIZE, system_charset_info); + + /* The size of the offset value we will use for position() */ + ref_length = sizeof(z_off_t); +} /* This method reads the header of a datafile and returns whether or not it was successful. diff --git a/sql/examples/ha_archive.h b/sql/examples/ha_archive.h index 3932b62980c..41835c5fb6f 100644 --- a/sql/examples/ha_archive.h +++ b/sql/examples/ha_archive.h @@ -58,14 +58,7 @@ class ha_archive: public handler bool bulk_insert; /* If we are performing a bulk insert */ public: - ha_archive(TABLE *table): handler(table), delayed_insert(0), bulk_insert(0) - { - /* Set our original buffer from pre-allocated memory */ - buffer.set((char *)byte_buffer, IO_SIZE, system_charset_info); - - /* The size of the offset value we will use for position() */ - ref_length = sizeof(z_off_t); - } + ha_archive(TABLE *table_arg); ~ha_archive() { } diff --git a/sql/examples/ha_example.cc b/sql/examples/ha_example.cc index 9da297ccd1f..2818c176cd3 100644 --- a/sql/examples/ha_example.cc +++ b/sql/examples/ha_example.cc @@ -72,6 +72,24 @@ #ifdef HAVE_EXAMPLE_DB #include "ha_example.h" + +static handlerton example_hton= { + "CSV", + 0, /* slot */ + 0, /* savepoint size. */ + 0, /* close_connection */ + 0, /* savepoint */ + 0, /* rollback to savepoint */ + 0, /* release savepoint */ + 0, /* commit */ + 0, /* rollback */ + 0, /* prepare */ + 0, /* recover */ + 0, /* commit_by_xid */ + 0, /* rollback_by_xid */ + HTON_NO_FLAGS +}; + /* Variables for example share methods */ static HASH example_open_tables; // Hash used to track open tables pthread_mutex_t example_mutex; // This is the mutex we use to init the hash @@ -179,6 +197,10 @@ static int free_share(EXAMPLE_SHARE *share) } +ha_example::ha_example(TABLE *table_arg) + :handler(&example_hton, table_arg) +{} + /* If frm_error() is called then we will use this to to find out what file extentions exist for the storage engine. This is also used by the default rename_table and diff --git a/sql/examples/ha_example.h b/sql/examples/ha_example.h index ae72e5bb275..37f38fe5210 100644 --- a/sql/examples/ha_example.h +++ b/sql/examples/ha_example.h @@ -45,9 +45,7 @@ class ha_example: public handler EXAMPLE_SHARE *share; /* Shared lock info */ public: - ha_example(TABLE *table): handler(table) - { - } + ha_example(TABLE *table_arg); ~ha_example() { } diff --git a/sql/examples/ha_tina.cc b/sql/examples/ha_tina.cc index a030960d08a..9c774c1f75c 100644 --- a/sql/examples/ha_tina.cc +++ b/sql/examples/ha_tina.cc @@ -54,6 +54,23 @@ pthread_mutex_t tina_mutex; static HASH tina_open_tables; static int tina_init= 0; +static handlerton tina_hton= { + "CSV", + 0, /* slot */ + 0, /* savepoint size. */ + 0, /* close_connection */ + 0, /* savepoint */ + 0, /* rollback to savepoint */ + 0, /* release savepoint */ + 0, /* commit */ + 0, /* rollback */ + 0, /* prepare */ + 0, /* recover */ + 0, /* commit_by_xid */ + 0, /* rollback_by_xid */ + HTON_NO_FLAGS +}; + /***************************************************************************** ** TINA tables *****************************************************************************/ @@ -228,6 +245,20 @@ byte * find_eoln(byte *data, off_t begin, off_t end) return 0; } + +ha_tina::ha_tina(TABLE *table_arg) + :handler(&tina_hton, table_arg), + /* + These definitions are found in hanler.h + These are not probably completely right. + */ + current_position(0), next_position(0), chain_alloced(0), chain_size(DEFAULT_CHAIN_LENGTH) +{ + /* Set our original buffers from pre-allocated memory */ + buffer.set(byte_buffer, IO_SIZE, system_charset_info); + chain= chain_buffer; +} + /* Encode a buffer into the quoted format. */ diff --git a/sql/examples/ha_tina.h b/sql/examples/ha_tina.h index 22193c01013..5679d77a4dc 100644 --- a/sql/examples/ha_tina.h +++ b/sql/examples/ha_tina.h @@ -49,18 +49,8 @@ class ha_tina: public handler byte chain_alloced; uint32 chain_size; - public: - ha_tina(TABLE *table): handler(table), - /* - These definitions are found in hanler.h - Theses are not probably completely right. - */ - current_position(0), next_position(0), chain_alloced(0), chain_size(DEFAULT_CHAIN_LENGTH) - { - /* Set our original buffers from pre-allocated memory */ - buffer.set(byte_buffer, IO_SIZE, system_charset_info); - chain = chain_buffer; - } +public: + ha_tina(TABLE *table_arg); ~ha_tina() { if (chain_alloced) diff --git a/sql/ha_berkeley.cc b/sql/ha_berkeley.cc index 568fb727e63..26e743d4a71 100644 --- a/sql/ha_berkeley.cc +++ b/sql/ha_berkeley.cc @@ -120,7 +120,8 @@ static handlerton berkeley_hton = { NULL, /* prepare */ NULL, /* recover */ NULL, /* commit_by_xid */ - NULL /* rollback_by_xid */ + NULL, /* rollback_by_xid */ + HTON_CLOSE_CURSORS_AT_COMMIT }; typedef struct st_berkeley_trx_data { @@ -372,6 +373,17 @@ void berkeley_cleanup_log_files(void) /***************************************************************************** ** Berkeley DB tables *****************************************************************************/ + +ha_berkeley::ha_berkeley(TABLE *table_arg) + :handler(&berkeley_hton, table_arg), alloc_ptr(0), rec_buff(0), file(0), + int_table_flags(HA_REC_NOT_IN_SEQ | HA_FAST_KEY_READ | + HA_NULL_IN_KEY | HA_CAN_INDEX_BLOBS | HA_NOT_EXACT_COUNT | + HA_PRIMARY_KEY_IN_READ_INDEX | HA_FILE_BASED | + HA_AUTO_PART_KEY | HA_TABLE_SCAN_ON_INDEX), + changed_rows(0), last_dup_key((uint) -1), version(0), using_ignore(0) +{} + + static const char *ha_berkeley_exts[] = { ha_berkeley_ext, NullS diff --git a/sql/ha_berkeley.h b/sql/ha_berkeley.h index f6376939445..aa92908ecde 100644 --- a/sql/ha_berkeley.h +++ b/sql/ha_berkeley.h @@ -85,12 +85,7 @@ class ha_berkeley: public handler DBT *get_pos(DBT *to, byte *pos); public: - ha_berkeley(TABLE *table): handler(table), alloc_ptr(0),rec_buff(0), file(0), - int_table_flags(HA_REC_NOT_IN_SEQ | HA_FAST_KEY_READ | - HA_NULL_IN_KEY | HA_CAN_INDEX_BLOBS | HA_NOT_EXACT_COUNT | - HA_PRIMARY_KEY_IN_READ_INDEX | HA_FILE_BASED | - HA_AUTO_PART_KEY | HA_TABLE_SCAN_ON_INDEX), - changed_rows(0),last_dup_key((uint) -1),version(0),using_ignore(0) {} + ha_berkeley(TABLE *table_arg); ~ha_berkeley() {} const char *table_type() const { return "BerkeleyDB"; } ulong index_flags(uint idx, uint part, bool all_parts) const; diff --git a/sql/ha_blackhole.cc b/sql/ha_blackhole.cc index 6abbe983f48..856a053db9e 100644 --- a/sql/ha_blackhole.cc +++ b/sql/ha_blackhole.cc @@ -24,6 +24,34 @@ #include "ha_blackhole.h" +/* Blackhole storage engine handlerton */ + +static handlerton myisam_hton= { + "BLACKHOLE", + 0, /* slot */ + 0, /* savepoint size. */ + 0, /* close_connection */ + 0, /* savepoint */ + 0, /* rollback to savepoint */ + 0, /* release savepoint */ + 0, /* commit */ + 0, /* rollback */ + 0, /* prepare */ + 0, /* recover */ + 0, /* commit_by_xid */ + 0, /* rollback_by_xid */ + HTON_NO_FLAGS +}; + +/***************************************************************************** +** BLACKHOLE tables +*****************************************************************************/ + +ha_blackhole::ha_blackhole(TABLE *table_arg) + :handler(&blackhole_hton, table_arg) +{} + + static const char *ha_blackhole_exts[] = { NullS }; diff --git a/sql/ha_blackhole.h b/sql/ha_blackhole.h index 84a386e17f8..2dccabf17cc 100644 --- a/sql/ha_blackhole.h +++ b/sql/ha_blackhole.h @@ -28,9 +28,7 @@ class ha_blackhole: public handler THR_LOCK thr_lock; public: - ha_blackhole(TABLE *table): handler(table) - { - } + ha_blackhole(TABLE *table_arg); ~ha_blackhole() { } diff --git a/sql/ha_federated.cc b/sql/ha_federated.cc index e0e35c6b866..1d7b8cda8e2 100644 --- a/sql/ha_federated.cc +++ b/sql/ha_federated.cc @@ -679,6 +679,37 @@ error: } +/* Federated storage engine handlerton */ + +static handlerton federated_hton= { + "FEDERATED", + 0, /* slot */ + 0, /* savepoint size. */ + 0, /* close_connection */ + 0, /* savepoint */ + 0, /* rollback to savepoint */ + 0, /* release savepoint */ + 0, /* commit */ + 0, /* rollback */ + 0, /* prepare */ + 0, /* recover */ + 0, /* commit_by_xid */ + 0, /* rollback_by_xid */ + HTON_NO_FLAGS +}; + + +/***************************************************************************** +** FEDERATED tables +*****************************************************************************/ + +ha_federated::ha_federated(TABLE *table_arg) + :handler(&federated_hton, table_arg), + mysql(0), stored_result(0), scan_flag(0), + ref_length(sizeof(MYSQL_ROW_OFFSET)), current_position(0) +{} + + /* Convert MySQL result set row to handler internal format diff --git a/sql/ha_federated.h b/sql/ha_federated.h index 3e55419f266..58b78ab0dde 100644 --- a/sql/ha_federated.h +++ b/sql/ha_federated.h @@ -162,11 +162,7 @@ private: bool records_in_range); public: - ha_federated(TABLE *table): handler(table), - mysql(0), stored_result(0), scan_flag(0), - ref_length(sizeof(MYSQL_ROW_OFFSET)), current_position(0) - { - } + ha_federated(TABLE *table_arg); ~ha_federated() { } diff --git a/sql/ha_heap.cc b/sql/ha_heap.cc index 6e609a94be3..92a5fe0ea09 100644 --- a/sql/ha_heap.cc +++ b/sql/ha_heap.cc @@ -23,9 +23,33 @@ #include <myisampack.h> #include "ha_heap.h" +static handlerton heap_hton= { + "MEMORY", + 0, /* slot */ + 0, /* savepoint size. */ + 0, /* close_connection */ + 0, /* savepoint */ + 0, /* rollback to savepoint */ + 0, /* release savepoint */ + 0, /* commit */ + 0, /* rollback */ + 0, /* prepare */ + 0, /* recover */ + 0, /* commit_by_xid */ + 0, /* rollback_by_xid */ + HTON_NO_FLAGS +}; + /***************************************************************************** ** HEAP tables *****************************************************************************/ + +ha_heap::ha_heap(TABLE *table_arg) + :handler(&heap_hton, table_arg), file(0), records_changed(0), + key_stats_ok(0) +{} + + static const char *ha_heap_exts[] = { NullS }; diff --git a/sql/ha_heap.h b/sql/ha_heap.h index 7a97c727049..f7368436456 100644 --- a/sql/ha_heap.h +++ b/sql/ha_heap.h @@ -31,8 +31,7 @@ class ha_heap: public handler uint records_changed; bool key_stats_ok; public: - ha_heap(TABLE *table): handler(table), file(0), records_changed(0), - key_stats_ok(0) {} + ha_heap(TABLE *table); ~ha_heap() {} const char *table_type() const { diff --git a/sql/ha_innodb.cc b/sql/ha_innodb.cc index 98199a22efe..69451493d4b 100644 --- a/sql/ha_innodb.cc +++ b/sql/ha_innodb.cc @@ -215,7 +215,14 @@ static handlerton innobase_hton = { innobase_xa_prepare, /* prepare */ innobase_xa_recover, /* recover */ innobase_commit_by_xid, /* commit_by_xid */ - innobase_rollback_by_xid /* rollback_by_xid */ + innobase_rollback_by_xid, /* rollback_by_xid */ + /* + For now when one opens a cursor, MySQL does not create an own + InnoDB consistent read view for it, and uses the view of the + currently active transaction. Therefore, cursors can not + survive COMMIT or ROLLBACK statements, which free this view. + */ + HTON_CLOSE_CURSORS_AT_COMMIT }; /********************************************************************* @@ -765,6 +772,24 @@ check_trx_exists( return(trx); } + +/************************************************************************* +Construct ha_innobase handler. */ + +ha_innobase::ha_innobase(TABLE *table_arg) + :handler(&innobase_hton, table_arg), + int_table_flags(HA_REC_NOT_IN_SEQ | + HA_NULL_IN_KEY | + HA_CAN_INDEX_BLOBS | + HA_CAN_SQL_HANDLER | + HA_NOT_EXACT_COUNT | + HA_PRIMARY_KEY_IN_READ_INDEX | + HA_TABLE_SCAN_ON_INDEX), + last_dup_key((uint) -1), + start_of_scan(0), + num_write_row(0) +{} + /************************************************************************* Updates the user_thd field in a handle and also allocates a new InnoDB transaction handle if needed, and updates the transaction fields in the diff --git a/sql/ha_innodb.h b/sql/ha_innodb.h index 90cae3998ed..1584a2182c9 100644 --- a/sql/ha_innodb.h +++ b/sql/ha_innodb.h @@ -81,19 +81,7 @@ class ha_innobase: public handler /* Init values for the class: */ public: - ha_innobase(TABLE *table): handler(table), - int_table_flags(HA_REC_NOT_IN_SEQ | - HA_NULL_IN_KEY | - HA_CAN_INDEX_BLOBS | - HA_CAN_SQL_HANDLER | - HA_NOT_EXACT_COUNT | - HA_PRIMARY_KEY_IN_READ_INDEX | - HA_TABLE_SCAN_ON_INDEX), - last_dup_key((uint) -1), - start_of_scan(0), - num_write_row(0) - { - } + ha_innobase(TABLE *table_arg); ~ha_innobase() {} /* Get the row type from the storage engine. If this method returns diff --git a/sql/ha_myisam.cc b/sql/ha_myisam.cc index e94b697e0bb..fefa05e92b0 100644 --- a/sql/ha_myisam.cc +++ b/sql/ha_myisam.cc @@ -44,6 +44,29 @@ TYPELIB myisam_recover_typelib= {array_elements(myisam_recover_names)-1,"", ** MyISAM tables *****************************************************************************/ +/* MyISAM handlerton */ + +static handlerton myisam_hton= { + "MyISAM", + 0, /* slot */ + 0, /* savepoint size. */ + 0, /* close_connection */ + 0, /* savepoint */ + 0, /* rollback to savepoint */ + 0, /* release savepoint */ + 0, /* commit */ + 0, /* rollback */ + 0, /* prepare */ + 0, /* recover */ + 0, /* commit_by_xid */ + 0, /* rollback_by_xid */ + /* + MyISAM doesn't support transactions and doesn't have + transaction-dependent context: cursors can survive a commit. + */ + HTON_NO_FLAGS +}; + // collect errors printed by mi_check routines static void mi_check_print_msg(MI_CHECK *param, const char* msg_type, @@ -123,6 +146,17 @@ void mi_check_print_warning(MI_CHECK *param, const char *fmt,...) } + +ha_myisam::ha_myisam(TABLE *table_arg) + :handler(&myisam_hton, table_arg), file(0), + int_table_flags(HA_NULL_IN_KEY | HA_CAN_FULLTEXT | HA_CAN_SQL_HANDLER | + HA_DUPP_POS | HA_CAN_INDEX_BLOBS | HA_AUTO_PART_KEY | + HA_FILE_BASED | HA_CAN_GEOMETRY | HA_READ_RND_SAME | + HA_CAN_INSERT_DELAYED | HA_CAN_BIT_FIELD), + can_enable_indexes(1) +{} + + static const char *ha_myisam_exts[] = { ".MYI", ".MYD", diff --git a/sql/ha_myisam.h b/sql/ha_myisam.h index bbd9721f8e2..ca684463311 100644 --- a/sql/ha_myisam.h +++ b/sql/ha_myisam.h @@ -43,13 +43,7 @@ class ha_myisam: public handler int repair(THD *thd, MI_CHECK ¶m, bool optimize); public: - ha_myisam(TABLE *table): handler(table), file(0), - int_table_flags(HA_NULL_IN_KEY | HA_CAN_FULLTEXT | HA_CAN_SQL_HANDLER | - HA_DUPP_POS | HA_CAN_INDEX_BLOBS | HA_AUTO_PART_KEY | - HA_FILE_BASED | HA_CAN_GEOMETRY | HA_READ_RND_SAME | - HA_CAN_INSERT_DELAYED | HA_CAN_BIT_FIELD), - can_enable_indexes(1) - {} + ha_myisam(TABLE *table_arg); ~ha_myisam() {} const char *table_type() const { return "MyISAM"; } const char *index_type(uint key_number); diff --git a/sql/ha_myisammrg.cc b/sql/ha_myisammrg.cc index 5d3f379081c..8c4b4e790b1 100644 --- a/sql/ha_myisammrg.cc +++ b/sql/ha_myisammrg.cc @@ -32,6 +32,30 @@ ** MyISAM MERGE tables *****************************************************************************/ +/* MyISAM MERGE handlerton */ + +static handlerton myisammrg_hton= { + "MRG_MyISAM", + 0, /* slot */ + 0, /* savepoint size. */ + 0, /* close_connection */ + 0, /* savepoint */ + 0, /* rollback to savepoint */ + 0, /* release savepoint */ + 0, /* commit */ + 0, /* rollback */ + 0, /* prepare */ + 0, /* recover */ + 0, /* commit_by_xid */ + 0, /* rollback_by_xid */ + HTON_NO_FLAGS +}; + + +ha_myisammrg::ha_myisammrg(TABLE *table_arg) + :handler(&myisammrg_hton, table_arg), file(0) +{} + static const char *ha_myisammrg_exts[] = { ".MRG", NullS diff --git a/sql/ha_myisammrg.h b/sql/ha_myisammrg.h index 7348096b695..c762b7c286e 100644 --- a/sql/ha_myisammrg.h +++ b/sql/ha_myisammrg.h @@ -28,7 +28,7 @@ class ha_myisammrg: public handler MYRG_INFO *file; public: - ha_myisammrg(TABLE *table): handler(table), file(0) {} + ha_myisammrg(TABLE *table_arg); ~ha_myisammrg() {} const char *table_type() const { return "MRG_MyISAM"; } const char **bas_ext() const; diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index d35b8af80aa..81723690b83 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -62,7 +62,8 @@ static handlerton ndbcluster_hton = { NULL, /* prepare */ NULL, /* recover */ NULL, /* commit_by_xid */ - NULL /* rollback_by_xid */ + NULL, /* rollback_by_xid */ + HTON_NO_FLAGS }; #define NDB_HIDDEN_PRIMARY_KEY_LENGTH 8 @@ -4174,7 +4175,7 @@ ulonglong ha_ndbcluster::get_auto_increment() */ ha_ndbcluster::ha_ndbcluster(TABLE *table_arg): - handler(table_arg), + handler(&ndbcluster_hton, table_arg), m_active_trans(NULL), m_active_cursor(NULL), m_table(NULL), diff --git a/sql/handler.cc b/sql/handler.cc index a61dce35501..cf5ddd95764 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -208,15 +208,8 @@ handler *get_new_handler(TABLE *table, enum db_type db_type) case DB_TYPE_HASH: return new ha_hash(table); #endif -#ifdef HAVE_ISAM - case DB_TYPE_MRG_ISAM: - return new ha_isammrg(table); - case DB_TYPE_ISAM: - return new ha_isam(table); -#else case DB_TYPE_MRG_ISAM: return new ha_myisammrg(table); -#endif #ifdef HAVE_BERKELEY_DB case DB_TYPE_BERKELEY_DB: return new ha_berkeley(table); @@ -634,6 +627,11 @@ int ha_commit_trans(THD *thd, bool all) DBUG_RETURN(1); } DBUG_EXECUTE_IF("crash_commit_before", abort();); + + /* Close all cursors that can not survive COMMIT */ + if (is_real_trans) /* not a statement commit */ + thd->stmt_map.close_transient_cursors(); + if (!trans->no_2pc && trans->nht > 1) { for (; *ht && !error; ht++) @@ -735,6 +733,10 @@ int ha_rollback_trans(THD *thd, bool all) #ifdef USING_TRANSACTIONS if (trans->nht) { + /* Close all cursors that can not survive ROLLBACK */ + if (is_real_trans) /* not a statement commit */ + thd->stmt_map.close_transient_cursors(); + for (handlerton **ht=trans->ht; *ht; ht++) { int err; diff --git a/sql/handler.h b/sql/handler.h index df906e284e7..02b2353b890 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -349,8 +349,13 @@ typedef struct int (*recover)(XID *xid_list, uint len); int (*commit_by_xid)(XID *xid); int (*rollback_by_xid)(XID *xid); + uint32 flags; /* global handler flags */ } handlerton; +/* Possible flags of a handlerton */ +#define HTON_NO_FLAGS 0 +#define HTON_CLOSE_CURSORS_AT_COMMIT 1 + typedef struct st_thd_trans { /* number of entries in the ht[] */ @@ -445,6 +450,7 @@ class handler :public Sql_alloc virtual int rnd_end() { return 0; } public: + const handlerton *ht; /* storage engine of this handler */ byte *ref; /* Pointer to current row */ byte *dupp_ref; /* Pointer to dupp row */ ulonglong data_file_length; /* Length off data file */ @@ -486,7 +492,8 @@ public: bool implicit_emptied; /* Can be !=0 only if HEAP */ const COND *pushed_cond; - handler(TABLE *table_arg) :table(table_arg), + handler(const handlerton *ht_arg, TABLE *table_arg) :table(table_arg), + ht(ht_arg), ref(0), data_file_length(0), max_data_file_length(0), index_file_length(0), delete_length(0), auto_increment_value(0), records(0), deleted(0), mean_rec_length(0), diff --git a/sql/lock.cc b/sql/lock.cc index 7f3fe5ac5da..aa162a23b40 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -103,6 +103,10 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags) { MYSQL_LOCK *sql_lock; TABLE *write_lock_used; + int rc; + /* Map the return value of thr_lock to an error from errmsg.txt */ + const static int thr_lock_errno_to_mysql[]= + { 0, 1, ER_LOCK_WAIT_TIMEOUT, ER_LOCK_DEADLOCK }; DBUG_ENTER("mysql_lock_tables"); for (;;) @@ -135,15 +139,24 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags) { my_free((gptr) sql_lock,MYF(0)); sql_lock=0; - thd->proc_info=0; break; } thd->proc_info="Table lock"; thd->locked=1; - if (thr_multi_lock(sql_lock->locks,sql_lock->lock_count)) + rc= thr_lock_errno_to_mysql[(int) thr_multi_lock(sql_lock->locks, + sql_lock->lock_count, + thd->lock_id)]; + if (rc > 1) /* a timeout or a deadlock */ + { + my_error(rc, MYF(0)); + my_free((gptr) sql_lock,MYF(0)); + sql_lock= 0; + break; + } + else if (rc == 1) /* aborted */ { thd->some_tables_deleted=1; // Try again - sql_lock->lock_count=0; // Locks are alread freed + sql_lock->lock_count= 0; // Locks are already freed } else if (!thd->some_tables_deleted || (flags & MYSQL_LOCK_IGNORE_FLUSH)) { diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 7c8b2b781e4..f31a6727619 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -4342,7 +4342,8 @@ enum options_mysqld OPT_ENABLE_LARGE_PAGES, OPT_TIMED_MUTEXES, OPT_OLD_STYLE_USER_LIMITS, - OPT_LOG_SLOW_ADMIN_STATEMENTS + OPT_LOG_SLOW_ADMIN_STATEMENTS, + OPT_TABLE_LOCK_WAIT_TIMEOUT }; @@ -5595,6 +5596,11 @@ The minimum value for this variable is 4096.", "The number of open tables for all threads.", (gptr*) &table_cache_size, (gptr*) &table_cache_size, 0, GET_ULONG, REQUIRED_ARG, 64, 1, 512*1024L, 0, 1, 0}, + {"table_lock_wait_timeout", OPT_TABLE_LOCK_WAIT_TIMEOUT, "Timeout in " + "seconds to wait for a table level lock before returning an error. Used" + " only if the connection has active cursors.", + (gptr*) &table_lock_wait_timeout, (gptr*) &table_lock_wait_timeout, + 0, GET_ULONG, REQUIRED_ARG, 50, 1, 1024 * 1024 * 1024, 0, 1, 0}, {"thread_cache_size", OPT_THREAD_CACHE_SIZE, "How many threads we should keep in a cache for reuse.", (gptr*) &thread_cache_size, (gptr*) &thread_cache_size, 0, GET_ULONG, @@ -7083,4 +7089,6 @@ template class I_List_iterator<THD>; template class I_List<i_string>; template class I_List<i_string_pair>; template class I_List<NAMED_LIST>; +template class I_List<Statement>; +template class I_List_iterator<Statement>; #endif diff --git a/sql/set_var.cc b/sql/set_var.cc index 2d7c3364e41..09581aed217 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -375,6 +375,8 @@ sys_var_thd_ulong sys_sync_replication_timeout( sys_var_bool_ptr sys_sync_frm("sync_frm", &opt_sync_frm); sys_var_long_ptr sys_table_cache_size("table_cache", &table_cache_size); +sys_var_long_ptr sys_table_lock_wait_timeout("table_lock_wait_timeout", + &table_lock_wait_timeout); sys_var_long_ptr sys_thread_cache_size("thread_cache_size", &thread_cache_size); sys_var_thd_enum sys_tx_isolation("tx_isolation", @@ -682,6 +684,7 @@ sys_var *sys_variables[]= #endif &sys_sync_frm, &sys_table_cache_size, + &sys_table_lock_wait_timeout, &sys_table_type, &sys_thread_cache_size, &sys_time_format, @@ -972,6 +975,7 @@ struct show_var_st init_vars[]= { {"system_time_zone", system_time_zone, SHOW_CHAR}, #endif {"table_cache", (char*) &table_cache_size, SHOW_LONG}, + {"table_lock_wait_timeout", (char*) &table_lock_wait_timeout, SHOW_LONG }, {sys_table_type.name, (char*) &sys_table_type, SHOW_SYS}, {sys_thread_cache_size.name,(char*) &sys_thread_cache_size, SHOW_SYS}, #ifdef HAVE_THR_SETCONCURRENCY diff --git a/sql/sql_class.cc b/sql/sql_class.cc index d0ac1a16f6b..89d5b543dfc 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -173,6 +173,7 @@ Open_tables_state::Open_tables_state() THD::THD() :Statement(CONVENTIONAL_EXECUTION, 0, ALLOC_ROOT_MIN_BLOCK_SIZE, 0), Open_tables_state(), + lock_id(&main_lock_id), user_time(0), global_read_lock(0), is_fatal_error(0), rand_used(0), time_zone_used(0), last_insert_id_used(0), insert_id_used(0), clear_next_insert_id(0), @@ -265,6 +266,8 @@ THD::THD() tablespace_op=FALSE; ulong tmp=sql_rnd_with_mutex(); randominit(&rand, tmp + (ulong) &rand, tmp + (ulong) ::query_id); + thr_lock_info_init(&lock_info); /* safety: will be reset after start */ + thr_lock_owner_init(&main_lock_id, &lock_info); } @@ -406,6 +409,8 @@ THD::~THD() net_end(&net); } #endif + stmt_map.destroy(); /* close all prepared statements */ + DBUG_ASSERT(lock_info.n_cursors == 0); if (!cleanup_done) cleanup(); @@ -518,6 +523,7 @@ bool THD::store_globals() if this is the slave SQL thread. */ variables.pseudo_thread_id= thread_id; + thr_lock_info_init(&lock_info); return 0; } @@ -1563,6 +1569,12 @@ void Statement::restore_backup_statement(Statement *stmt, Statement *backup) } +void Statement::close_cursor() +{ + DBUG_ASSERT("Statement::close_cursor()" == "not implemented"); +} + + void THD::end_statement() { /* Cleanup SQL processing state to resuse this statement in next query. */ @@ -1683,6 +1695,14 @@ int Statement_map::insert(Statement *statement) } +void Statement_map::close_transient_cursors() +{ + Statement *stmt; + while ((stmt= transient_cursor_list.head())) + stmt->close_cursor(); /* deletes itself from the list */ +} + + bool select_dumpvar::send_data(List<Item> &items) { List_iterator_fast<Item_func_set_user_var> li(vars); diff --git a/sql/sql_class.h b/sql/sql_class.h index 6c4315b95fa..d6847f5fb35 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -756,7 +756,7 @@ class Cursor; be used explicitly. */ -class Statement: public Query_arena +class Statement: public ilink, public Query_arena { Statement(const Statement &rhs); /* not implemented: */ Statement &operator=(const Statement &rhs); /* non-copyable */ @@ -833,6 +833,8 @@ public: void restore_backup_statement(Statement *stmt, Statement *backup); /* return class type */ virtual Type type() const; + /* Close the cursor open for this statement, if there is one */ + virtual void close_cursor(); }; @@ -884,15 +886,25 @@ public: } hash_delete(&st_hash, (byte *) statement); } + void add_transient_cursor(Statement *stmt) + { transient_cursor_list.append(stmt); } + void erase_transient_cursor(Statement *stmt) { stmt->unlink(); } + /* + Close all cursors of this connection that use tables of a storage + engine that has transaction-specific state and therefore can not + survive COMMIT or ROLLBACK. Currently all but MyISAM cursors are closed. + */ + void close_transient_cursors(); /* Erase all statements (calls Statement destructor) */ void reset() { my_hash_reset(&names_hash); my_hash_reset(&st_hash); + transient_cursor_list.empty(); last_found_statement= 0; } - ~Statement_map() + void destroy() { hash_free(&names_hash); hash_free(&st_hash); @@ -900,6 +912,7 @@ public: private: HASH st_hash; HASH names_hash; + I_List<Statement> transient_cursor_list; Statement *last_found_statement; }; @@ -1017,8 +1030,7 @@ public: a thread/connection descriptor */ -class THD :public ilink, - public Statement, +class THD :public Statement, public Open_tables_state { public: @@ -1044,6 +1056,10 @@ public: struct rand_struct rand; // used for authentication struct system_variables variables; // Changeable local variables struct system_status_var status_var; // Per thread statistic vars + THR_LOCK_INFO lock_info; // Locking info of this thread + THR_LOCK_OWNER main_lock_id; // To use for conventional queries + THR_LOCK_OWNER *lock_id; // If not main_lock_id, points to + // the lock_id of a cursor. pthread_mutex_t LOCK_delete; // Locked before thd is deleted /* all prepared statements and cursors of this connection */ Statement_map stmt_map; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index aa47b506f73..b0f93463a8e 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2020,6 +2020,7 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) DBUG_VOID_RETURN; /* If lex->result is set, mysql_execute_command will use it */ stmt->lex->result= &cursor->result; + thd->lock_id= &cursor->lock_id; } } #ifndef EMBEDDED_LIBRARY @@ -2069,6 +2070,9 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) Cursor::open is buried deep in JOIN::exec of the top level join. */ cursor->init_from_thd(thd); + + if (cursor->close_at_commit) + thd->stmt_map.add_transient_cursor(stmt); } else { @@ -2078,6 +2082,7 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) } thd->set_statement(&stmt_backup); + thd->lock_id= &thd->main_lock_id; thd->current_arena= thd; DBUG_VOID_RETURN; @@ -2252,6 +2257,8 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length) the previous calls. */ free_root(cursor->mem_root, MYF(0)); + if (cursor->close_at_commit) + thd->stmt_map.erase_transient_cursor(stmt); } thd->restore_backup_statement(stmt, &stmt_backup); @@ -2291,14 +2298,6 @@ void mysql_stmt_reset(THD *thd, char *packet) DBUG_VOID_RETURN; stmt->close_cursor(); /* will reset statement params */ - cursor= stmt->cursor; - if (cursor && cursor->is_open()) - { - thd->change_list= cursor->change_list; - cursor->close(FALSE); - cleanup_stmt_and_thd_after_use(stmt, thd); - free_root(cursor->mem_root, MYF(0)); - } stmt->state= Query_arena::PREPARED; @@ -2478,6 +2477,8 @@ void Prepared_statement::close_cursor() cursor->close(FALSE); cleanup_stmt_and_thd_after_use(this, thd); free_root(cursor->mem_root, MYF(0)); + if (cursor->close_at_commit) + thd->stmt_map.erase_transient_cursor(this); } /* Clear parameters from data which could be set by diff --git a/sql/sql_select.cc b/sql/sql_select.cc index b333e369dc4..6147a1b62a5 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1702,10 +1702,12 @@ JOIN::destroy() Cursor::Cursor(THD *thd) :Query_arena(&main_mem_root, INITIALIZED), - join(0), unit(0) + join(0), unit(0), + close_at_commit(FALSE) { /* We will overwrite it at open anyway. */ init_sql_alloc(&main_mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0); + thr_lock_owner_init(&lock_id, &thd->lock_info); } @@ -1739,6 +1741,21 @@ Cursor::init_from_thd(THD *thd) free_list= thd->free_list; change_list= thd->change_list; reset_thd(thd); + /* Now we have an active cursor and can cause a deadlock */ + thd->lock_info.n_cursors++; + + close_at_commit= FALSE; /* reset in case we're reusing the cursor */ + for (TABLE *table= open_tables; table; table= table->next) + { + const handlerton *ht= table->file->ht; + if (ht) + close_at_commit|= (ht->flags & HTON_CLOSE_CURSORS_AT_COMMIT); + else + { + close_at_commit= TRUE; /* handler status is unknown */ + break; + } + } /* XXX: thd->locked_tables is not changed. What problems can we have with it if cursor is open? @@ -1907,6 +1924,7 @@ Cursor::close(bool is_active) thd->derived_tables= tmp_derived_tables; thd->lock= tmp_lock; } + thd->lock_info.n_cursors--; /* Decrease the number of active cursors */ join= 0; unit= 0; free_items(); diff --git a/sql/sql_select.h b/sql/sql_select.h index 9285e33be33..7f6d661a4de 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -392,6 +392,8 @@ class Cursor: public Sql_alloc, public Query_arena public: Item_change_list change_list; select_send result; + THR_LOCK_OWNER lock_id; + my_bool close_at_commit; /* Temporary implementation as now we replace THD state by value */ /* Save THD state into cursor */ diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 97138e564a4..ffd99915168 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -55,6 +55,7 @@ static char current_db[]= "client_test_db"; static unsigned int test_count= 0; static unsigned int opt_count= 0; static unsigned int iter_count= 0; +static my_bool have_innodb= FALSE; static const char *opt_basedir= "./"; @@ -220,6 +221,28 @@ static void print_st_error(MYSQL_STMT *stmt, const char *msg) } } +/* Check if the connection has InnoDB tables */ + +static my_bool check_have_innodb(MYSQL *conn) +{ + MYSQL_RES *res; + MYSQL_ROW row; + int rc; + my_bool result; + + rc= mysql_query(conn, "show variables like 'have_innodb'"); + myquery(rc); + res= mysql_use_result(conn); + DIE_UNLESS(res); + + row= mysql_fetch_row(res); + DIE_UNLESS(row); + + result= strcmp(row[1], "YES") == 0; + mysql_free_result(res); + return result; +} + /* This is to be what mysql_query() is for mysql_real_query(), for @@ -290,6 +313,7 @@ static void client_connect(ulong flag) strxmov(query, "USE ", current_db, NullS); rc= mysql_query(mysql, query); myquery(rc); + have_innodb= check_have_innodb(mysql); if (!opt_silent) fprintf(stdout, " OK"); @@ -13749,6 +13773,110 @@ static void test_bug11037() myquery(rc); } +/* Bug#10760: cursors, crash in a fetch after rollback. */ + +static void test_bug10760() +{ + MYSQL_STMT *stmt; + MYSQL_BIND bind[1]; + int rc; + const char *stmt_text; + char id_buf[20]; + ulong id_len; + int i= 0; + ulong type; + + myheader("test_bug10760"); + + mysql_query(mysql, "drop table if exists t1, t2"); + + /* create tables */ + rc= mysql_query(mysql, "create table t1 (id integer not null primary key)" + " engine=MyISAM"); + myquery(rc); + for (; i < 42; ++i) + { + char buf[100]; + sprintf(buf, "insert into t1 (id) values (%d)", i+1); + rc= mysql_query(mysql, buf); + myquery(rc); + } + mysql_autocommit(mysql, FALSE); + /* create statement */ + stmt= mysql_stmt_init(mysql); + type= (ulong) CURSOR_TYPE_READ_ONLY; + mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + + /* + 1: check that a deadlock within the same connection + is resolved and an error is returned. The deadlock is modelled + as follows: + con1: open cursor for select * from t1; + con1: insert into t1 (id) values (1) + */ + stmt_text= "select id from t1 order by 1"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_execute(stmt, rc); + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + rc= mysql_query(mysql, "update t1 set id=id+100"); + DIE_UNLESS(rc); + if (!opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + /* + 2: check that MyISAM tables used in cursors survive + COMMIT/ROLLBACK. + */ + rc= mysql_rollback(mysql); /* should not close the cursor */ + myquery(rc); + rc= mysql_stmt_fetch(stmt); + check_execute(stmt, rc); + + /* + 3: check that cursors to InnoDB tables are closed (for now) by + COMMIT/ROLLBACK. + */ + if (! have_innodb) + { + if (!opt_silent) + printf("Testing that cursors are closed at COMMIT/ROLLBACK requires " + "InnoDB.\n"); + } + else + { + stmt_text= "select id from t1 order by 1"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_execute(stmt, rc); + + rc= mysql_query(mysql, "alter table t1 engine=InnoDB"); + myquery(rc); + + bzero(bind, sizeof(bind)); + bind[0].buffer_type= MYSQL_TYPE_STRING; + bind[0].buffer= (void*) id_buf; + bind[0].buffer_length= sizeof(id_buf); + bind[0].length= &id_len; + check_execute(stmt, rc); + mysql_stmt_bind_result(stmt, bind); + + rc= mysql_stmt_execute(stmt); + rc= mysql_stmt_fetch(stmt); + DIE_UNLESS(rc == 0); + if (!opt_silent) + printf("Fetched row %s\n", id_buf); + rc= mysql_rollback(mysql); /* should close the cursor */ + myquery(rc); + rc= mysql_stmt_fetch(stmt); + DIE_UNLESS(rc); + if (!opt_silent) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + } + + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table t1"); + myquery(rc); + mysql_autocommit(mysql, TRUE); /* restore default */ +} /* Read and parse arguments and MySQL options from my.cnf @@ -13994,6 +14122,7 @@ static struct my_tests_st my_tests[]= { { "test_bug9735", test_bug9735 }, { "test_bug11183", test_bug11183 }, { "test_bug11037", test_bug11037 }, + { "test_bug10760", test_bug10760 }, { 0, 0 } }; |