diff options
Diffstat (limited to 'sql/lock.cc')
-rw-r--r-- | sql/lock.cc | 325 |
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; } |