summaryrefslogtreecommitdiff
path: root/sql/lock.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/lock.cc')
-rw-r--r--sql/lock.cc230
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;
+}