summaryrefslogtreecommitdiff
path: root/sql/lock.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/lock.cc')
-rw-r--r--sql/lock.cc325
1 files changed, 276 insertions, 49 deletions
diff --git a/sql/lock.cc b/sql/lock.cc
index f730ac56d35..c401cbda05c 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -92,6 +92,7 @@ static void print_lock_error(int error, const char *);
count The number of tables to lock.
flags Options:
MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK Ignore a global read lock
+ MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY Ignore SET GLOBAL READ_ONLY
MYSQL_LOCK_IGNORE_FLUSH Ignore a flush tables.
MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN Instead of reopening altered
or dropped tables by itself,
@@ -110,16 +111,90 @@ static void print_lock_error(int error, const char *);
static int thr_lock_errno_to_mysql[]=
{ 0, 1, ER_LOCK_WAIT_TIMEOUT, ER_LOCK_DEADLOCK };
+/**
+ Perform semantic checks for mysql_lock_tables.
+ @param thd The current thread
+ @param tables The tables to lock
+ @param count The number of tables to lock
+ @param flags Lock flags
+ @return 0 if all the check passed, non zero if a check failed.
+*/
+int mysql_lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags)
+{
+ bool log_table_write_query;
+ uint system_count;
+ uint i;
+
+ DBUG_ENTER("mysql_lock_tables_check");
+
+ system_count= 0;
+ log_table_write_query= (is_log_table_write_query(thd->lex->sql_command)
+ || ((flags & MYSQL_LOCK_PERF_SCHEMA) != 0));
+
+ for (i=0 ; i<count; i++)
+ {
+ TABLE *t= tables[i];
+
+ /* Protect against 'fake' partially initialized TABLE_SHARE */
+ DBUG_ASSERT(t->s->table_category != TABLE_UNKNOWN_CATEGORY);
+
+ /*
+ Table I/O to performance schema tables is performed
+ only internally by the server implementation.
+ When a user is requesting a lock, the following
+ constraints are enforced:
+ */
+ if (t->s->require_write_privileges() &&
+ ! log_table_write_query)
+ {
+ /*
+ A user should not be able to prevent writes,
+ or hold any type of lock in a session,
+ since this would be a DOS attack.
+ */
+ if ((t->reginfo.lock_type >= TL_READ_NO_INSERT)
+ || (thd->lex->sql_command == SQLCOM_LOCK_TABLES))
+ {
+ my_error(ER_CANT_LOCK_LOG_TABLE, MYF(0));
+ DBUG_RETURN(1);
+ }
+ }
+
+ if ((t->s->table_category == TABLE_CATEGORY_SYSTEM) &&
+ (t->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE))
+ {
+ system_count++;
+ }
+ }
+
+ /*
+ Locking of system tables is restricted:
+ locking a mix of system and non-system tables in the same lock
+ is prohibited, to prevent contention.
+ */
+ if ((system_count > 0) && (system_count < count))
+ {
+ my_error(ER_WRONG_LOCK_OF_SYSTEM_TABLE, MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ DBUG_RETURN(0);
+}
+
MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
uint flags, bool *need_reopen)
{
MYSQL_LOCK *sql_lock;
TABLE *write_lock_used;
int rc;
+
DBUG_ENTER("mysql_lock_tables");
*need_reopen= FALSE;
+ if (mysql_lock_tables_check(thd, tables, count, flags))
+ DBUG_RETURN (NULL);
+
for (;;)
{
if (! (sql_lock= get_lock_data(thd, tables, count, GET_LOCK_STORE_LOCKS,
@@ -137,7 +212,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
{
/* Clear the lock type of all lock data to avoid reusage. */
reset_lock_data(sql_lock);
- my_free((gptr) sql_lock,MYF(0));
+ my_free((uchar*) sql_lock,MYF(0));
sql_lock=0;
break;
}
@@ -145,22 +220,41 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
{
/* Clear the lock type of all lock data to avoid reusage. */
reset_lock_data(sql_lock);
- my_free((gptr) sql_lock,MYF(0));
+ my_free((uchar*) sql_lock,MYF(0));
goto retry;
}
}
+ if (!(flags & MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY) &&
+ write_lock_used &&
+ opt_readonly &&
+ !(thd->security_ctx->master_access & SUPER_ACL) &&
+ !thd->slave_thread)
+ {
+ /*
+ Someone has issued SET GLOBAL READ_ONLY=1 and we want a write lock.
+ We do not wait for READ_ONLY=0, and fail.
+ */
+ reset_lock_data(sql_lock);
+ my_free((uchar*) sql_lock, MYF(0));
+ sql_lock=0;
+ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
+ break;
+ }
+
thd->proc_info="System lock";
+ DBUG_PRINT("info", ("thd->proc_info %s", thd->proc_info));
if (sql_lock->table_count && lock_external(thd, sql_lock->table,
sql_lock->table_count))
{
/* Clear the lock type of all lock data to avoid reusage. */
reset_lock_data(sql_lock);
- my_free((gptr) sql_lock,MYF(0));
+ my_free((uchar*) sql_lock,MYF(0));
sql_lock=0;
break;
}
thd->proc_info="Table lock";
+ DBUG_PRINT("info", ("thd->proc_info %s", thd->proc_info));
thd->locked=1;
/* Copy the lock data array. thr_multi_lock() reorders its contens. */
memcpy(sql_lock->locks + sql_lock->lock_count, sql_lock->locks,
@@ -173,7 +267,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
if (rc > 1) /* a timeout or a deadlock */
{
my_error(rc, MYF(0));
- my_free((gptr) sql_lock,MYF(0));
+ my_free((uchar*) sql_lock,MYF(0));
sql_lock= 0;
break;
}
@@ -238,6 +332,7 @@ static int lock_external(THD *thd, TABLE **tables, uint count)
int lock_type,error;
DBUG_ENTER("lock_external");
+ DBUG_PRINT("info", ("count %d", count));
for (i=1 ; i <= count ; i++, tables++)
{
DBUG_ASSERT((*tables)->reginfo.lock_type >= TL_READ);
@@ -247,7 +342,7 @@ static int lock_external(THD *thd, TABLE **tables, uint count)
(*tables)->reginfo.lock_type <= TL_READ_NO_INSERT))
lock_type=F_RDLCK;
- if ((error= (*tables)->file->ha_external_lock(thd,lock_type)))
+ if ((error=(*tables)->file->ha_external_lock(thd,lock_type)))
{
print_lock_error(error, (*tables)->file->table_type());
for (; i-- ; tables--)
@@ -274,7 +369,7 @@ void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock)
thr_multi_unlock(sql_lock->locks,sql_lock->lock_count);
if (sql_lock->table_count)
VOID(unlock_external(thd,sql_lock->table,sql_lock->table_count));
- my_free((gptr) sql_lock,MYF(0));
+ my_free((uchar*) sql_lock,MYF(0));
DBUG_VOID_RETURN;
}
@@ -435,19 +530,39 @@ void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table,
}
}
+/* Downgrade all locks on a table to new WRITE level from WRITE_ONLY */
+
+void mysql_lock_downgrade_write(THD *thd, TABLE *table,
+ thr_lock_type new_lock_type)
+{
+ MYSQL_LOCK *locked;
+ TABLE *write_lock_used;
+ if ((locked = get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK,
+ &write_lock_used)))
+ {
+ for (uint i=0; i < locked->lock_count; i++)
+ thr_downgrade_write_lock(locked->locks[i], new_lock_type);
+ my_free((uchar*) locked,MYF(0));
+ }
+}
+
+
/* abort all other threads waiting to get lock in table */
-void mysql_lock_abort(THD *thd, TABLE *table)
+void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock)
{
MYSQL_LOCK *locked;
TABLE *write_lock_used;
+ DBUG_ENTER("mysql_lock_abort");
+
if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK,
&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));
+ thr_abort_locks(locked->locks[i]->lock, upgrade_lock);
+ my_free((uchar*) locked,MYF(0));
}
+ DBUG_VOID_RETURN;
}
@@ -477,10 +592,10 @@ bool mysql_lock_abort_for_thread(THD *thd, TABLE *table)
for (uint i=0; i < locked->lock_count; i++)
{
if (thr_abort_locks_for_thread(locked->locks[i]->lock,
- table->in_use->real_id))
+ table->in_use->thread_id))
result= TRUE;
}
- my_free((gptr) locked,MYF(0));
+ my_free((uchar*) locked,MYF(0));
}
DBUG_RETURN(result);
}
@@ -522,8 +637,8 @@ MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b)
}
/* Delete old, not needed locks */
- my_free((gptr) a,MYF(0));
- my_free((gptr) b,MYF(0));
+ my_free((uchar*) a,MYF(0));
+ my_free((uchar*) b,MYF(0));
DBUG_RETURN(sql_lock);
}
@@ -646,7 +761,7 @@ static int unlock_external(THD *thd, TABLE **table,uint count)
if ((*table)->current_lock != F_UNLCK)
{
(*table)->current_lock = F_UNLCK;
- if ((error= (*table)->file->ha_external_lock(thd, F_UNLCK)))
+ if ((error=(*table)->file->ha_external_lock(thd, F_UNLCK)))
{
error_code=error;
print_lock_error(error_code, (*table)->file->table_type());
@@ -682,27 +797,19 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count,
TABLE **to, **table_buf;
DBUG_ENTER("get_lock_data");
+ DBUG_ASSERT((flags == GET_LOCK_UNLOCK) || (flags == GET_LOCK_STORE_LOCKS));
+
+ DBUG_PRINT("info", ("count %d", count));
*write_lock_used=0;
for (i=tables=lock_count=0 ; i < count ; i++)
{
- if (table_ptr[i]->s->tmp_table != NON_TRANSACTIONAL_TMP_TABLE)
+ TABLE *t= table_ptr[i];
+
+ if (t->s->tmp_table != NON_TRANSACTIONAL_TMP_TABLE)
{
- tables+=table_ptr[i]->file->lock_count();
+ tables+= t->file->lock_count();
lock_count++;
}
- /*
- To be able to open and lock for reading system tables like 'mysql.proc',
- when we already have some tables opened and locked, and avoid deadlocks
- we have to disallow write-locking of these tables with any other tables.
- */
- if (table_ptr[i]->s->system_table &&
- table_ptr[i]->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE &&
- count != 1)
- {
- my_error(ER_WRONG_LOCK_OF_SYSTEM_TABLE, MYF(0), table_ptr[i]->s->db,
- table_ptr[i]->s->table_name);
- return 0;
- }
}
/*
@@ -721,6 +828,8 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count,
to= table_buf= sql_lock->table= (TABLE**) (locks + tables * 2);
sql_lock->table_count=lock_count;
sql_lock->lock_count=tables;
+ DBUG_PRINT("info", ("sql_lock->table_count %d sql_lock->lock_count %d",
+ sql_lock->table_count, sql_lock->lock_count));
for (i=0 ; i < count ; i++)
{
@@ -739,7 +848,7 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count,
/* Clear the lock type of the lock data that are stored already. */
sql_lock->lock_count= locks - sql_lock->locks;
reset_lock_data(sql_lock);
- my_free((gptr) sql_lock,MYF(0));
+ my_free((uchar*) sql_lock,MYF(0));
DBUG_RETURN(0);
}
}
@@ -836,7 +945,7 @@ int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list)
if (wait_if_global_read_lock(thd, 0, 1))
DBUG_RETURN(1);
VOID(pthread_mutex_lock(&LOCK_open));
- if ((lock_retcode = lock_table_name(thd, table_list)) < 0)
+ if ((lock_retcode = lock_table_name(thd, table_list, TRUE)) < 0)
goto end;
if (lock_retcode && wait_for_locked_table_names(thd, table_list))
{
@@ -859,6 +968,7 @@ end:
lock_table_name()
thd Thread handler
table_list Lock first table in this list
+ check_in_use Do we need to check if table already in use by us
WARNING
If you are going to update the table, you should use
@@ -878,7 +988,7 @@ end:
> 0 table locked, but someone is using it
*/
-int lock_table_name(THD *thd, TABLE_LIST *table_list)
+int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use)
{
TABLE *table;
char key[MAX_DBKEY_LENGTH];
@@ -888,26 +998,35 @@ int lock_table_name(THD *thd, TABLE_LIST *table_list)
DBUG_ENTER("lock_table_name");
DBUG_PRINT("enter",("db: %s name: %s", db, table_list->table_name));
- safe_mutex_assert_owner(&LOCK_open);
-
- key_length= (uint)(strmov(strmov(key, db) + 1, table_list->table_name) -
- key) + 1;
+ key_length= create_table_def_key(thd, key, table_list, 0);
- /* Only insert the table if we haven't insert it already */
- for (table=(TABLE*) hash_first(&open_cache, (byte*)key, key_length, &state);
- table ;
- table = (TABLE*) hash_next(&open_cache, (byte*)key, key_length, &state))
- if (table->in_use == thd)
- DBUG_RETURN(0);
+ if (check_in_use)
+ {
+ /* Only insert the table if we haven't insert it already */
+ for (table=(TABLE*) hash_first(&open_cache, (uchar*)key,
+ key_length, &state);
+ table ;
+ table = (TABLE*) hash_next(&open_cache,(uchar*) key,
+ key_length, &state))
+ {
+ if (table->in_use == thd)
+ {
+ DBUG_PRINT("info", ("Table is in use"));
+ table->s->version= 0; // Ensure no one can use this
+ table->locked_by_name= 1;
+ DBUG_RETURN(0);
+ }
+ }
+ }
if (!(table= table_cache_insert_placeholder(thd, key, key_length)))
DBUG_RETURN(-1);
- table_list->table= table;
+ table_list->table=table;
/* Return 1 if table is in use */
DBUG_RETURN(test(remove_table_from_cache(thd, db, table_list->table_name,
- RTFC_NO_FLAG)));
+ check_in_use ? RTFC_NO_FLAG : RTFC_WAIT_OTHER_THREAD_FLAG)));
}
@@ -915,7 +1034,7 @@ void unlock_table_name(THD *thd, TABLE_LIST *table_list)
{
if (table_list->table)
{
- hash_delete(&open_cache, (byte*) table_list->table);
+ hash_delete(&open_cache, (uchar*) table_list->table);
broadcast_refresh();
}
}
@@ -925,8 +1044,17 @@ static bool locked_named_table(THD *thd, TABLE_LIST *table_list)
{
for (; table_list ; table_list=table_list->next_local)
{
- if (table_list->table && table_is_used(table_list->table,0))
- return 1;
+ TABLE *table= table_list->table;
+ if (table)
+ {
+ TABLE *save_next= table->next;
+ bool result;
+ table->next= 0;
+ result= table_is_used(table_list->table, 0);
+ table->next= save_next;
+ if (result)
+ return 1;
+ }
}
return 0; // All tables are locked
}
@@ -936,6 +1064,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))
@@ -945,7 +1074,7 @@ bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list)
result=1;
break;
}
- wait_for_refresh(thd);
+ wait_for_condition(thd, &LOCK_open, &COND_refresh);
pthread_mutex_lock(&LOCK_open);
}
DBUG_RETURN(result);
@@ -980,7 +1109,7 @@ bool lock_table_names(THD *thd, TABLE_LIST *table_list)
for (lock_table= table_list; lock_table; lock_table= lock_table->next_local)
{
int got_lock;
- if ((got_lock=lock_table_name(thd,lock_table)) < 0)
+ if ((got_lock=lock_table_name(thd,lock_table, TRUE)) < 0)
goto end; // Fatal error
if (got_lock)
got_all_locks=0; // Someone is using table
@@ -997,6 +1126,102 @@ end:
}
+/**
+ @brief Lock all tables in list with an exclusive table name lock.
+
+ @param thd Thread handle.
+ @param table_list Names of tables to lock.
+
+ @note This function needs to be protected by LOCK_open. If we're
+ under LOCK TABLES, this function does not work as advertised. Namely,
+ it does not exclude other threads from using this table and does not
+ put an exclusive name lock on this table into the table cache.
+
+ @see lock_table_names
+ @see unlock_table_names
+
+ @retval TRUE An error occured.
+ @retval FALSE Name lock successfully acquired.
+*/
+
+bool lock_table_names_exclusively(THD *thd, TABLE_LIST *table_list)
+{
+ if (lock_table_names(thd, table_list))
+ return TRUE;
+
+ /*
+ Upgrade the table name locks from semi-exclusive to exclusive locks.
+ */
+ for (TABLE_LIST *table= table_list; table; table= table->next_global)
+ {
+ if (table->table)
+ table->table->open_placeholder= 1;
+ }
+ return FALSE;
+}
+
+
+/**
+ @brief Test is 'table' is protected by an exclusive name lock.
+
+ @param[in] thd The current thread handler
+ @param[in] table Table container containing the single table to be tested
+
+ @note Needs to be protected by LOCK_open mutex.
+
+ @return Error status code
+ @retval TRUE Table is protected
+ @retval FALSE Table is not protected
+*/
+
+bool
+is_table_name_exclusively_locked_by_this_thread(THD *thd,
+ TABLE_LIST *table_list)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+
+ key_length= create_table_def_key(thd, key, table_list, 0);
+
+ return is_table_name_exclusively_locked_by_this_thread(thd, (uchar *)key,
+ key_length);
+}
+
+
+/**
+ @brief Test is 'table key' is protected by an exclusive name lock.
+
+ @param[in] thd The current thread handler.
+ @param[in] table Table container containing the single table to be tested.
+
+ @note Needs to be protected by LOCK_open mutex
+
+ @retval TRUE Table is protected
+ @retval FALSE Table is not protected
+ */
+
+bool
+is_table_name_exclusively_locked_by_this_thread(THD *thd, uchar *key,
+ int key_length)
+{
+ HASH_SEARCH_STATE state;
+ TABLE *table;
+
+ for (table= (TABLE*) hash_first(&open_cache, key,
+ key_length, &state);
+ table ;
+ table= (TABLE*) hash_next(&open_cache, key,
+ key_length, &state))
+ {
+ if (table->in_use == thd &&
+ table->open_placeholder == 1 &&
+ table->s->version == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
/*
Unlock all tables in list with a name lock
@@ -1020,11 +1245,13 @@ end:
void unlock_table_names(THD *thd, TABLE_LIST *table_list,
TABLE_LIST *last_table)
{
+ DBUG_ENTER("unlock_table_names");
for (TABLE_LIST *table= table_list;
table != last_table;
table= table->next_local)
unlock_table_name(thd,table);
broadcast_refresh();
+ DBUG_VOID_RETURN;
}