diff options
Diffstat (limited to 'sql/lock.cc')
-rw-r--r-- | sql/lock.cc | 230 |
1 files changed, 192 insertions, 38 deletions
diff --git a/sql/lock.cc b/sql/lock.cc index 84d10d61366..4c84bbb6e69 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -1,15 +1,15 @@ /* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB - + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This program 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 General Public License for more details. - + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ @@ -21,6 +21,46 @@ before getting internal locks. If we do it in the other order, the status information is not up to date when called from the lock handler. + GENERAL DESCRIPTION OF LOCKING + + When not using LOCK TABLES: + + - For each SQL statement mysql_lock_tables() is called for all involved + tables. + - mysql_lock_tables() will call + table_handler->external_lock(thd,locktype) for each table. + This is followed by a call to thr_multi_lock() for all tables. + + - When statement is done, we call mysql_unlock_tables(). + This will call thr_multi_unlock() followed by + table_handler->external_lock(thd, F_UNLCK) for each table. + + - Note that mysql_unlock_tables() may be called several times as + MySQL in some cases can free some tables earlier than others. + + - The above is true both for normal and temporary tables. + + - Temporary non transactional tables are never passed to thr_multi_lock() + and we never call external_lock(thd, F_UNLOCK) on these. + + When using LOCK TABLES: + + - LOCK TABLE will call mysql_lock_tables() for all tables. + mysql_lock_tables() will call + table_handler->external_lock(thd,locktype) for each table. + This is followed by a call to thr_multi_lock() for all tables. + + - For each statement, we will call table_handler->start_stmt(THD) + to inform the table handler that we are using the table. + + The tables used can only be tables used in LOCK TABLES or a + temporary table. + + - When statement is done, we will call ha_commit_stmt(thd); + + - When calling UNLOCK TABLES we call mysql_unlock_tables() for all + tables used in LOCK TABLES + TODO: Change to use my_malloc() ONLY when using LOCK TABLES command or when we are forced to use mysql_lock_merge. @@ -28,12 +68,13 @@ TODO: #include "mysql_priv.h" #include <hash.h> +#include <assert.h> extern HASH open_cache; static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table,uint count, bool unlock, TABLE **write_locked); -static int lock_external(TABLE **table,uint count); +static int lock_external(THD *thd, TABLE **table,uint count); static int unlock_external(THD *thd, TABLE **table,uint count); static void print_lock_error(int error); @@ -55,33 +96,13 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd,TABLE **tables,uint count) Someone has issued LOCK ALL TABLES FOR READ and we want a write lock Wait until the lock is gone */ - if (thd->global_read_lock) // This thread had the read locks + if (wait_if_global_read_lock(thd, 1)) { - my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE,MYF(0), - write_lock_used->table_name); my_free((gptr) sql_lock,MYF(0)); sql_lock=0; break; } - - pthread_mutex_lock(&LOCK_open); - thd->mysys_var->current_mutex= &LOCK_open; - thd->mysys_var->current_cond= &COND_refresh; - thd->proc_info="Waiting for table"; - - while (global_read_lock && ! thd->killed && - thd->version == refresh_version) - { - (void) pthread_cond_wait(&COND_refresh,&LOCK_open); - } - pthread_mutex_unlock(&LOCK_open); - pthread_mutex_lock(&thd->mysys_var->mutex); - thd->mysys_var->current_mutex= 0; - thd->mysys_var->current_cond= 0; - thd->proc_info= 0; - pthread_mutex_unlock(&thd->mysys_var->mutex); - - if (thd->version != refresh_version || thd->killed) + if (thd->version != refresh_version) { my_free((gptr) sql_lock,MYF(0)); goto retry; @@ -89,14 +110,14 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd,TABLE **tables,uint count) } thd->proc_info="System lock"; - if (lock_external(tables,count)) + if (lock_external(thd, tables, count)) { my_free((gptr) sql_lock,MYF(0)); sql_lock=0; thd->proc_info=0; break; } - thd->proc_info=0; + thd->proc_info="Table lock"; thd->locked=1; if (thr_multi_lock(sql_lock->locks,sql_lock->lock_count)) { @@ -115,6 +136,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd,TABLE **tables,uint count) thd->locked=0; break; } + thd->proc_info=0; /* some table was altered or deleted. reopen tables marked deleted */ mysql_unlock_tables(thd,sql_lock); @@ -124,6 +146,7 @@ retry: if (wait_for_tables(thd)) break; // Couldn't open tables } + thd->proc_info=0; if (thd->killed) { my_error(ER_SERVER_SHUTDOWN,MYF(0)); @@ -138,15 +161,15 @@ retry: } -static int lock_external(TABLE **tables,uint count) +static int lock_external(THD *thd, TABLE **tables, uint count) { reg1 uint i; int lock_type,error; - THD *thd=current_thd; DBUG_ENTER("lock_external"); for (i=1 ; i <= count ; i++, tables++) { + DBUG_ASSERT((*tables)->reginfo.lock_type >= TL_READ); lock_type=F_WRLCK; /* Lock exclusive */ if ((*tables)->db_stat & HA_READ_ONLY || ((*tables)->reginfo.lock_type >= TL_READ && @@ -155,7 +178,7 @@ static int lock_external(TABLE **tables,uint count) if ((error=(*tables)->file->external_lock(thd,lock_type))) { - for ( ; i-- ; tables--) + for (; i-- ; tables--) { (*tables)->file->external_lock(thd, F_UNLCK); (*tables)->current_lock=F_UNLCK; @@ -225,7 +248,7 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) sql_lock->lock_count= found; } - /* Then to the same for the external locks */ + /* Then do the same for the external locks */ /* Move all write locked tables first */ TABLE **table=sql_lock->table; for (i=found=0 ; i < sql_lock->table_count ; i++) @@ -401,6 +424,36 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, *****************************************************************************/ /* + Lock and wait for the named lock. + Returns 0 on ok +*/ + +int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list) +{ + int lock_retcode; + int error= -1; + DBUG_ENTER("lock_and_wait_for_table_name"); + + if (wait_if_global_read_lock(thd,0)) + DBUG_RETURN(1); + VOID(pthread_mutex_lock(&LOCK_open)); + if ((lock_retcode = lock_table_name(thd, table_list)) < 0) + goto end; + if (lock_retcode && wait_for_locked_table_names(thd, table_list)) + { + unlock_table_name(thd, table_list); + goto end; + } + error=0; + +end: + pthread_mutex_unlock(&LOCK_open); + start_waiting_global_read_lock(thd); + DBUG_RETURN(error); +} + + +/* Put a not open table with an old refresh version in the table cache. This will force any other threads that uses the table to release it as soon as possible. @@ -411,13 +464,13 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, > 0 table locked, but someone is using it */ - int lock_table_name(THD *thd, TABLE_LIST *table_list) { TABLE *table; char key[MAX_DBKEY_LENGTH]; uint key_length; DBUG_ENTER("lock_table_name"); + safe_mutex_assert_owner(&LOCK_open); key_length=(uint) (strmov(strmov(key,table_list->db)+1,table_list->real_name) -key)+ 1; @@ -429,10 +482,11 @@ int lock_table_name(THD *thd, TABLE_LIST *table_list) if (table->in_use == thd) DBUG_RETURN(0); - /* Create a table entry with the right key and with an old refresh version */ - /* Note that we must use my_malloc() here as this is freed by the table - cache */ - + /* + Create a table entry with the right key and with an old refresh version + Note that we must use my_malloc() here as this is freed by the table + cache + */ if (!(table= (TABLE*) my_malloc(sizeof(*table)+key_length, MYF(MY_WME | MY_ZEROFILL)))) DBUG_RETURN(-1); @@ -463,7 +517,7 @@ void unlock_table_name(THD *thd, TABLE_LIST *table_list) static bool locked_named_table(THD *thd, TABLE_LIST *table_list) { - for ( ; table_list ; table_list=table_list->next) + for (; table_list ; table_list=table_list->next) { if (table_list->table && table_is_used(table_list->table,0)) return 1; @@ -476,6 +530,7 @@ bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list) { bool result=0; DBUG_ENTER("wait_for_locked_table_names"); + safe_mutex_assert_owner(&LOCK_open); while (locked_named_table(thd,table_list)) { @@ -510,3 +565,102 @@ static void print_lock_error(int error) DBUG_VOID_RETURN; } + +/**************************************************************************** + Handling of global read locks + + The global locks are handled through the global variables: + global_read_lock + waiting_for_read_lock + protect_against_global_read_lock +****************************************************************************/ + +volatile uint global_read_lock=0; +static volatile uint protect_against_global_read_lock=0; +static volatile uint waiting_for_read_lock=0; + +bool lock_global_read_lock(THD *thd) +{ + DBUG_ENTER("lock_global_read_lock"); + + if (!thd->global_read_lock) + { + (void) pthread_mutex_lock(&LOCK_open); + const char *old_message=thd->enter_cond(&COND_refresh, &LOCK_open, + "Waiting to get readlock"); + DBUG_PRINT("info", + ("waiting_for: %d protect_against: %d", + waiting_for_read_lock, protect_against_global_read_lock)); + + waiting_for_read_lock++; + while (protect_against_global_read_lock && !thd->killed) + pthread_cond_wait(&COND_refresh, &LOCK_open); + waiting_for_read_lock--; + thd->exit_cond(old_message); + if (thd->killed) + { + (void) pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(1); + } + thd->global_read_lock=1; + global_read_lock++; + (void) pthread_mutex_unlock(&LOCK_open); + } + DBUG_RETURN(0); +} + +void unlock_global_read_lock(THD *thd) +{ + uint tmp; + thd->global_read_lock=0; + pthread_mutex_lock(&LOCK_open); + tmp= --global_read_lock; + pthread_mutex_unlock(&LOCK_open); + /* Send the signal outside the mutex to avoid a context switch */ + if (!tmp) + pthread_cond_broadcast(&COND_refresh); +} + + +bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh) +{ + const char *old_message; + bool result=0; + DBUG_ENTER("wait_if_global_read_lock"); + + (void) pthread_mutex_lock(&LOCK_open); + if (global_read_lock) + { + if (thd->global_read_lock) // This thread had the read locks + { + my_error(ER_CANT_UPDATE_WITH_READLOCK,MYF(0)); + (void) pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(1); + } + old_message=thd->enter_cond(&COND_refresh, &LOCK_open, + "Waiting for release of readlock"); + while (global_read_lock && ! thd->killed && + (!abort_on_refresh || thd->version == refresh_version)) + (void) pthread_cond_wait(&COND_refresh,&LOCK_open); + if (thd->killed) + result=1; + thd->exit_cond(old_message); + } + if (!abort_on_refresh && !result) + protect_against_global_read_lock++; + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(result); +} + + +void start_waiting_global_read_lock(THD *thd) +{ + bool tmp; + DBUG_ENTER("start_waiting_global_read_lock"); + (void) pthread_mutex_lock(&LOCK_open); + tmp= (!--protect_against_global_read_lock && waiting_for_read_lock); + (void) pthread_mutex_unlock(&LOCK_open); + if (tmp) + pthread_cond_broadcast(&COND_refresh); + DBUG_VOID_RETURN; +} |