diff options
Diffstat (limited to 'sql/lock.cc')
-rw-r--r-- | sql/lock.cc | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/sql/lock.cc b/sql/lock.cc new file mode 100644 index 00000000000..b65ae5ddc3e --- /dev/null +++ b/sql/lock.cc @@ -0,0 +1,386 @@ +/* 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 */ + + +/* locking functions for mysql */ +/* + Because of the new concurrent inserts, we must first get external locks + 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. + +TODO: + Change to use my_malloc() ONLY when using LOCK TABLES command or when + we are forced to use mysql_lock_merge. +*/ + +#include "mysql_priv.h" +#include <hash.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 unlock_external(THD *thd, TABLE **table,uint count); + + +MYSQL_LOCK *mysql_lock_tables(THD *thd,TABLE **tables,uint count) +{ + MYSQL_LOCK *sql_lock; + TABLE *write_lock_used; + DBUG_ENTER("mysql_lock_tables"); + + for (;;) + { + if (!(sql_lock = get_lock_data(thd,tables,count, 0,&write_lock_used))) + break; + + if (global_read_lock && write_lock_used) + { + /* + 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 + { + 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); + pthread_mutex_lock(&thd->mysys_var->mutex); + thd->mysys_var->current_mutex= &LOCK_open; + thd->mysys_var->current_cond= &COND_refresh; + thd->proc_info="Waiting for table"; + pthread_mutex_unlock(&thd->mysys_var->mutex); + + 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) + { + my_free((gptr) sql_lock,MYF(0)); + goto retry; + } + } + + thd->proc_info="System lock"; + if (lock_external(tables,count)) + { + my_free((gptr) sql_lock,MYF(0)); + sql_lock=0; + thd->proc_info=0; + break; + } + thd->proc_info=0; + thd->locked=1; + if (thr_multi_lock(sql_lock->locks,sql_lock->lock_count)) + { + thd->some_tables_deleted=1; // Try again + sql_lock->lock_count=0; // Locks are alread freed + } + else if (!thd->some_tables_deleted) + { + thd->locked=0; + break; + } + + /* some table was altered or deleted. reopen tables marked deleted */ + mysql_unlock_tables(thd,sql_lock); + thd->locked=0; +retry: + sql_lock=0; + if (wait_for_tables(thd)) + break; // Couldn't open tables + } + if (thd->killed) + { + my_error(ER_SERVER_SHUTDOWN,MYF(0)); + if (sql_lock) + { + mysql_unlock_tables(thd,sql_lock); + sql_lock=0; + } + } + DBUG_RETURN (sql_lock); +} + + +static int lock_external(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++) + { + lock_type=F_WRLCK; /* Lock exclusive */ + if ((*tables)->db_stat & HA_READ_ONLY || + ((*tables)->reginfo.lock_type >= TL_READ && + (*tables)->reginfo.lock_type <= TL_READ_NO_INSERT)) + lock_type=F_RDLCK; + + if ((error=(*tables)->file->external_lock(thd,lock_type))) + { + for ( ; i-- ; tables--) + { + (*tables)->file->external_lock(thd, F_UNLCK); + (*tables)->current_lock=F_UNLCK; + } + my_error(ER_CANT_LOCK,MYF(ME_BELL+ME_OLDWIN+ME_WAITTANG),error); + DBUG_RETURN(error); + } + else + { + (*tables)->db_stat &= ~ HA_BLOCK_LOCK; + (*tables)->current_lock= lock_type; + } + } + DBUG_RETURN(0); +} + + +void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock) +{ + DBUG_ENTER("mysql_unlock_tables"); + thr_multi_unlock(sql_lock->locks,sql_lock->lock_count); + VOID(unlock_external(thd,sql_lock->table,sql_lock->table_count)); + my_free((gptr) sql_lock,MYF(0)); + DBUG_VOID_RETURN; +} + +/* + Unlock some of the tables locked by mysql_lock_tables + This will work even if get_lock_data fails (next unlock will free all) + */ + +void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count) +{ + MYSQL_LOCK *sql_lock; + TABLE *write_lock_used; + if ((sql_lock = get_lock_data(thd, table, count, 1, &write_lock_used))) + mysql_unlock_tables(thd, sql_lock); +} + + +/* +** unlock all tables locked for read. +*/ + +void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) +{ + uint i,found; + DBUG_ENTER("mysql_unlock_read_tables"); + + /* Move all write locks first */ + THR_LOCK_DATA **lock=sql_lock->locks; + for (i=found=0 ; i < sql_lock->lock_count ; i++) + { + if (sql_lock->locks[i]->type >= TL_WRITE_ALLOW_READ) + { + swap(THR_LOCK_DATA *,*lock,sql_lock->locks[i]); + lock++; + found++; + } + } + /* unlock the read locked tables */ + if (i != found) + { + thr_multi_unlock(lock,i-found); + sql_lock->lock_count-=found; + } + + /* Then to 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++) + { + if ((uint) sql_lock->table[i]->reginfo.lock_type >= TL_WRITE_ALLOW_READ) + { + swap(TABLE *,*table,sql_lock->table[i]); + table++; + found++; + } + } + /* Unlock all read locked tables */ + if (i != found) + { + VOID(unlock_external(thd,table,i-found)); + sql_lock->table_count-=found; + } + DBUG_VOID_RETURN; +} + + + +void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table) +{ + mysql_unlock_some_tables(thd, &table,1); + if (locked) + { + reg1 uint i; + for (i=0; i < locked->table_count; i++) + { + if (locked->table[i] == table) + { + locked->table_count--; + bmove((char*) (locked->table+i), + (char*) (locked->table+i+1), + (locked->table_count-i)* sizeof(TABLE*)); + break; + } + } + THR_LOCK_DATA **prev=locked->locks; + for (i=0 ; i < locked->lock_count ; i++) + { + if (locked->locks[i]->type != TL_UNLOCK) + *prev++ = locked->locks[i]; + } + locked->lock_count=(prev - locked->locks); + } +} + +/* abort all other threads waiting to get lock in table */ + +void mysql_lock_abort(THD *thd, TABLE *table) +{ + MYSQL_LOCK *locked; + TABLE *write_lock_used; + if ((locked = get_lock_data(thd,&table,1,1,&write_lock_used))) + { + for (uint i=0; i < locked->lock_count; i++) + thr_abort_locks(locked->locks[i]->lock); + my_free((gptr) locked,MYF(0)); + } +} + + +MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b) +{ + MYSQL_LOCK *sql_lock; + DBUG_ENTER("mysql_lock_merge"); + if (!(sql_lock= (MYSQL_LOCK*) + my_malloc(sizeof(*sql_lock)+ + sizeof(THR_LOCK_DATA*)*(a->lock_count+b->lock_count)+ + sizeof(TABLE*)*(a->table_count+b->table_count),MYF(MY_WME)))) + DBUG_RETURN(0); // Fatal error + sql_lock->lock_count=a->lock_count+b->lock_count; + sql_lock->table_count=a->table_count+b->table_count; + sql_lock->locks=(THR_LOCK_DATA**) (sql_lock+1); + sql_lock->table=(TABLE**) (sql_lock->locks+sql_lock->lock_count); + memcpy(sql_lock->locks,a->locks,a->lock_count*sizeof(*a->locks)); + memcpy(sql_lock->locks+a->lock_count,b->locks, + b->lock_count*sizeof(*b->locks)); + memcpy(sql_lock->table,a->table,a->table_count*sizeof(*a->table)); + memcpy(sql_lock->table+a->table_count,b->table, + b->table_count*sizeof(*b->table)); + my_free((gptr) a,MYF(0)); + my_free((gptr) b,MYF(0)); + DBUG_RETURN(sql_lock); +} + + + /* unlock a set of external */ + +static int unlock_external(THD *thd, TABLE **table,uint count) +{ + int error,error_code; + DBUG_ENTER("unlock_external"); + + error_code=0; + for (; count-- ; table++) + { + if ((*table)->current_lock != F_UNLCK) + { + (*table)->current_lock = F_UNLCK; + if ((error=(*table)->file->external_lock(thd, F_UNLCK))) + error_code=error; + } + } + if (error_code) + my_error(ER_CANT_LOCK,MYF(ME_BELL+ME_OLDWIN+ME_WAITTANG),error_code); + DBUG_RETURN(error_code); +} + + +/* +** Get lock structures from table structs and initialize locks +*/ + + +static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, + bool get_old_locks, TABLE **write_lock_used) +{ + uint i,tables,lock_count; + MYSQL_LOCK *sql_lock; + THR_LOCK_DATA **locks; + TABLE **to; + + *write_lock_used=0; + for (i=tables=lock_count=0 ; i < count ; i++) + { + if (!table_ptr[i]->tmp_table) + { + tables+=table_ptr[i]->file->lock_count(); + lock_count++; + } + } + + if (!(sql_lock= (MYSQL_LOCK*) + my_malloc(sizeof(*sql_lock)+ + sizeof(THR_LOCK_DATA*)*tables+sizeof(table_ptr)*lock_count, + MYF(0)))) + return 0; + locks=sql_lock->locks=(THR_LOCK_DATA**) (sql_lock+1); + to=sql_lock->table=(TABLE**) (locks+tables); + sql_lock->table_count=lock_count; + sql_lock->lock_count=tables; + + for (i=0 ; i < count ; i++) + { + TABLE *table; + if ((table=table_ptr[i])->tmp_table) + continue; + *to++=table; + enum thr_lock_type lock_type= table->reginfo.lock_type; + if (lock_type >= TL_WRITE_ALLOW_WRITE) + { + *write_lock_used=table; + if (table->db_stat & HA_READ_ONLY) + { + my_error(ER_OPEN_AS_READONLY,MYF(0),table->table_name); + my_free((gptr) sql_lock,MYF(0)); + return 0; + } + } + locks=table->file->store_lock(thd, locks, get_old_locks ? TL_IGNORE : + lock_type); + } + return sql_lock; +} |