mariadb/sql/lock.cc
bk@work.mysql.com f4c589ff6c Import changeset
2000-07-31 21:29:14 +02:00

386 lines
10 KiB
C++

/* 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;
}