Commit 2b625ac3 authored by Michael Widenius's avatar Michael Widenius

Fixed lp:933719, "Assertion open_tables == 0 ... " in THD::restore_backup_open_tables_state.

This also fixes a (not likely) crashing bug when forcing a thread that was doing a table lock to re-open it's files, for example by creating a trigger.


mysys/thr_lock.c:
  Added more checking to find wrong locks.
  Removed one, not needed, parameter to thr_lock
sql/lock.cc:
  Fixed mysql_lock_tables() to retry with new sql_lock if lock fails. This was needed as table may be closed and reopened between retry's and then the old sql_lock will point to stale data.
sql/mysql_priv.h:
  Updated prototype
sql/sql_base.cc:
  Ensure that all tables are closed if opening of system table failes; This fixes the assert in THD::restore_backup_open_tables_state
sql/sql_handler.cc:
  Updated variable type
parent f93da174
...@@ -168,7 +168,8 @@ thr_lock_owner_equal(THR_LOCK_OWNER *rhs, THR_LOCK_OWNER *lhs) ...@@ -168,7 +168,8 @@ thr_lock_owner_equal(THR_LOCK_OWNER *rhs, THR_LOCK_OWNER *lhs)
static uint found_errors=0; static uint found_errors=0;
static int check_lock(struct st_lock_list *list, const char* lock_type, static int check_lock(struct st_lock_list *list, const char* lock_type,
const char *where, my_bool same_owner, my_bool no_cond) const char *where, my_bool same_owner, my_bool no_cond,
my_bool read_lock)
{ {
THR_LOCK_DATA *data,**prev; THR_LOCK_DATA *data,**prev;
uint count=0; uint count=0;
...@@ -181,6 +182,23 @@ static int check_lock(struct st_lock_list *list, const char* lock_type, ...@@ -181,6 +182,23 @@ static int check_lock(struct st_lock_list *list, const char* lock_type,
for (data=list->data; data && count++ < MAX_LOCKS ; data=data->next) for (data=list->data; data && count++ < MAX_LOCKS ; data=data->next)
{ {
if (data->type == TL_UNLOCK)
{
fprintf(stderr,
"Warning: Found unlocked lock at %s: %s\n",
lock_type, where);
return 1;
}
if ((read_lock && data->type > TL_READ_NO_INSERT) ||
(!read_lock && data->type <= TL_READ_NO_INSERT))
{
fprintf(stderr,
"Warning: Found %s lock in %s queue at %s: %s\n",
read_lock ? "write" : "read",
read_lock ? "read" : "write",
lock_type, where);
return 1;
}
if (data->type != last_lock_type) if (data->type != last_lock_type)
last_lock_type=TL_IGNORE; last_lock_type=TL_IGNORE;
if (data->prev != prev) if (data->prev != prev)
...@@ -237,11 +255,14 @@ static void check_locks(THR_LOCK *lock, const char *where, ...@@ -237,11 +255,14 @@ static void check_locks(THR_LOCK *lock, const char *where,
if (found_errors < MAX_FOUND_ERRORS) if (found_errors < MAX_FOUND_ERRORS)
{ {
if (check_lock(&lock->write,"write",where,1,1) | if (check_lock(&lock->write,"write",where,1,1,0) |
check_lock(&lock->write_wait,"write_wait",where,0,0) | check_lock(&lock->write_wait,"write_wait",where,0,0,0) |
check_lock(&lock->read,"read",where,0,1) | check_lock(&lock->read,"read",where,0,1,1) |
check_lock(&lock->read_wait,"read_wait",where,0,0)) check_lock(&lock->read_wait,"read_wait",where,0,0,1))
{
DBUG_ASSERT(my_assert_on_error == 0);
found_errors++; found_errors++;
}
if (found_errors < MAX_FOUND_ERRORS) if (found_errors < MAX_FOUND_ERRORS)
{ {
...@@ -592,18 +613,17 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, ...@@ -592,18 +613,17 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
static enum enum_thr_lock_result static enum enum_thr_lock_result
thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner)
enum thr_lock_type lock_type)
{ {
THR_LOCK *lock=data->lock; THR_LOCK *lock=data->lock;
enum enum_thr_lock_result result= THR_LOCK_SUCCESS; enum enum_thr_lock_result result= THR_LOCK_SUCCESS;
struct st_lock_list *wait_queue; struct st_lock_list *wait_queue;
THR_LOCK_DATA *lock_owner; THR_LOCK_DATA *lock_owner;
enum thr_lock_type lock_type= data->type;
DBUG_ENTER("thr_lock"); DBUG_ENTER("thr_lock");
data->next=0; data->next=0;
data->cond=0; /* safety */ data->cond=0; /* safety */
data->type=lock_type;
data->owner= owner; /* Must be reset ! */ data->owner= owner; /* Must be reset ! */
data->priority&= ~THR_LOCK_LATE_PRIV; data->priority&= ~THR_LOCK_LATE_PRIV;
VOID(pthread_mutex_lock(&lock->mutex)); VOID(pthread_mutex_lock(&lock->mutex));
...@@ -912,9 +932,7 @@ void thr_unlock(THR_LOCK_DATA *data, uint unlock_flags) ...@@ -912,9 +932,7 @@ void thr_unlock(THR_LOCK_DATA *data, uint unlock_flags)
if (lock_type == TL_READ_NO_INSERT) if (lock_type == TL_READ_NO_INSERT)
lock->read_no_write_count--; lock->read_no_write_count--;
data->type=TL_UNLOCK; /* Mark unlocked */ data->type=TL_UNLOCK; /* Mark unlocked */
check_locks(lock,"after releasing lock", lock_type, 1);
wake_up_waiters(lock); wake_up_waiters(lock);
check_locks(lock,"end of thr_unlock", lock_type, 1);
pthread_mutex_unlock(&lock->mutex); pthread_mutex_unlock(&lock->mutex);
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
...@@ -934,6 +952,7 @@ static void wake_up_waiters(THR_LOCK *lock) ...@@ -934,6 +952,7 @@ static void wake_up_waiters(THR_LOCK *lock)
enum thr_lock_type lock_type; enum thr_lock_type lock_type;
DBUG_ENTER("wake_up_waiters"); DBUG_ENTER("wake_up_waiters");
check_locks(lock, "before waking up waiters", TL_UNLOCK, 1);
if (!lock->write.data) /* If no active write locks */ if (!lock->write.data) /* If no active write locks */
{ {
data=lock->write_wait.data; data=lock->write_wait.data;
...@@ -1087,7 +1106,7 @@ thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner) ...@@ -1087,7 +1106,7 @@ thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner)
/* lock everything */ /* lock everything */
for (pos=data,end=data+count; pos < end ; pos++) for (pos=data,end=data+count; pos < end ; pos++)
{ {
enum enum_thr_lock_result result= thr_lock(*pos, owner, (*pos)->type); enum enum_thr_lock_result result= thr_lock(*pos, owner);
if (result != THR_LOCK_SUCCESS) if (result != THR_LOCK_SUCCESS)
{ /* Aborted */ { /* Aborted */
thr_multi_unlock(data,(uint) (pos-data), 0); thr_multi_unlock(data,(uint) (pos-data), 0);
......
...@@ -195,31 +195,33 @@ int mysql_lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags) ...@@ -195,31 +195,33 @@ int mysql_lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags)
MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
uint flags, bool *need_reopen) uint flags, bool *need_reopen)
{ {
TABLE *write_lock_used;
MYSQL_LOCK *sql_lock; MYSQL_LOCK *sql_lock;
TABLE *write_lock_used;
int rc;
DBUG_ENTER("mysql_lock_tables(tables)"); DBUG_ENTER("mysql_lock_tables(tables)");
*need_reopen= FALSE; *need_reopen= FALSE;
if (mysql_lock_tables_check(thd, tables, count, flags)) if (mysql_lock_tables_check(thd, tables, count, flags))
DBUG_RETURN(NULL); DBUG_RETURN (NULL);
for (;;)
{
if (!(sql_lock= get_lock_data(thd, tables, count, GET_LOCK_STORE_LOCKS, if (!(sql_lock= get_lock_data(thd, tables, count, GET_LOCK_STORE_LOCKS,
&write_lock_used)) || &write_lock_used)) ||
! sql_lock->table_count) !sql_lock->table_count)
DBUG_RETURN(sql_lock); break;
rc= mysql_lock_tables(thd, sql_lock, write_lock_used != 0, flags,
if (mysql_lock_tables(thd, sql_lock, write_lock_used != 0, flags, need_reopen);
need_reopen)) if (!rc)
{ break; // Got lock
/* Clear the lock type of all lock data to avoid reusage. */
reset_lock_data(sql_lock, 1);
my_free(sql_lock, MYF(0)); my_free(sql_lock, MYF(0));
sql_lock= 0; if (rc > 0)
DBUG_RETURN(0); // Failed
} }
DBUG_RETURN(sql_lock); DBUG_RETURN(sql_lock);
} }
/** /**
Lock a table based on a MYSQL_LOCK structure. Lock a table based on a MYSQL_LOCK structure.
...@@ -233,20 +235,19 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, ...@@ -233,20 +235,19 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
or deleted and should be reopened by caller. or deleted and should be reopened by caller.
@return 0 ok @return 0 ok
@return 1 error @return 1 fatal error
@return -1 retry
*/ */
bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, int mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock,
bool write_lock_used, bool write_lock_used,
uint flags, bool *need_reopen) uint flags, bool *need_reopen)
{ {
int res= 0;
int rc; int rc;
bool error= 1;
DBUG_ENTER("mysql_lock_tables(sql_lock)"); DBUG_ENTER("mysql_lock_tables(sql_lock)");
*need_reopen= FALSE; *need_reopen= FALSE;
for (;;)
{
if (write_lock_used && !(flags & MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK)) if (write_lock_used && !(flags & MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK))
{ {
if (global_read_lock) if (global_read_lock)
...@@ -256,10 +257,18 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, ...@@ -256,10 +257,18 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock,
Wait until the lock is gone Wait until the lock is gone
*/ */
if (wait_if_global_read_lock(thd, 1, 1)) if (wait_if_global_read_lock(thd, 1, 1))
break; {
/* Clear the lock type of all lock data to avoid reusage. */
reset_lock_data(sql_lock, 1);
DBUG_RETURN(1); // Fatal error
}
if (thd->version != refresh_version) if (thd->version != refresh_version)
{
/* Clear the lock type of all lock data to avoid reusage. */
reset_lock_data(sql_lock, 1);
goto retry; goto retry;
} }
}
if (opt_readonly && if (opt_readonly &&
!(thd->security_ctx->master_access & SUPER_ACL) && !(thd->security_ctx->master_access & SUPER_ACL) &&
...@@ -270,14 +279,20 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, ...@@ -270,14 +279,20 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock,
We do not wait for READ_ONLY=0, and fail. We do not wait for READ_ONLY=0, and fail.
*/ */
my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only"); my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
break; reset_lock_data(sql_lock, 1);
DBUG_RETURN(1); // Fatal error
} }
} }
thd_proc_info(thd, "System lock"); thd_proc_info(thd, "System lock");
if (lock_external(thd, sql_lock->table, sql_lock->table_count)) if (lock_external(thd, sql_lock->table, sql_lock->table_count))
break; {
/* Clear the lock type of all lock data to avoid reusage. */
res= 1; // Fatal error
goto end;
}
thd_proc_info(thd, "Table lock"); thd_proc_info(thd, "Table lock");
DBUG_PRINT("info", ("thd->proc_info %s", thd->proc_info));
/* Copy the lock data array. thr_multi_lock() reorders its contens. */ /* Copy the lock data array. thr_multi_lock() reorders its contens. */
memcpy(sql_lock->locks + sql_lock->lock_count, sql_lock->locks, memcpy(sql_lock->locks + sql_lock->lock_count, sql_lock->locks,
sql_lock->lock_count * sizeof(*sql_lock->locks)); sql_lock->lock_count * sizeof(*sql_lock->locks));
...@@ -286,17 +301,28 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, ...@@ -286,17 +301,28 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock,
sql_lock->lock_count, sql_lock->lock_count,
sql_lock->lock_count, sql_lock->lock_count,
thd->lock_id)]; thd->lock_id)];
if (rc) /* Locking failed */ if (rc) // Locking failed
{ {
if (sql_lock->table_count)
VOID(unlock_external(thd, sql_lock->table, sql_lock->table_count)); VOID(unlock_external(thd, sql_lock->table, sql_lock->table_count));
/*
reset_lock_data is required here. If thr_multi_lock fails it
resets lock type for tables, which were locked before (and
including) one that caused error. Lock type for other tables
preserved.
*/
reset_lock_data(sql_lock, 0);
if (rc > 1) if (rc > 1)
{ {
/* a timeout or a deadlock */
my_error(rc, MYF(0)); my_error(rc, MYF(0));
break; DBUG_RETURN(1);
} }
/* We where aborted and should try again from upper level*/ DBUG_ASSERT(rc == 1); // Timeout
thd->some_tables_deleted= 1; thd->some_tables_deleted= 1; // Reopen tables
sql_lock->lock_count= 0; // Locks are already freed
/* Retry */
} }
else else
{ {
...@@ -304,16 +330,12 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, ...@@ -304,16 +330,12 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock,
Lock worked. Now check that nothing happend while we where waiting Lock worked. Now check that nothing happend while we where waiting
to get the lock that would require us to free it. to get the lock that would require us to free it.
*/ */
error= 0;
if (!thd->some_tables_deleted || (flags & MYSQL_LOCK_IGNORE_FLUSH)) if (!thd->some_tables_deleted || (flags & MYSQL_LOCK_IGNORE_FLUSH))
{ {
/* res= 0;
Table was not signaled for deletion or we don't care if it was. goto end; /* Lock was not aborted. Return to upper level */
Return with table as locked.
*/
break;
} }
else if (!thd->open_tables && !(flags & MYSQL_LOCK_NOT_TEMPORARY)) if (!thd->open_tables && !(flags & MYSQL_LOCK_NOT_TEMPORARY))
{ {
/* /*
Only using temporary tables, no need to unlock. Only using temporary tables, no need to unlock.
...@@ -325,27 +347,37 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, ...@@ -325,27 +347,37 @@ bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock,
one when we are only using temporary tables. one when we are only using temporary tables.
*/ */
thd->some_tables_deleted=0; thd->some_tables_deleted=0;
break; goto end;
} }
/* Free lock and retry */
}
/* some table was altered or deleted. reopen tables marked deleted */ /* some table was altered or deleted. reopen tables marked deleted */
error= 1;
mysql_unlock_tables(thd, sql_lock, 0); mysql_unlock_tables(thd, sql_lock, 0);
}
retry: retry:
res= -1; // Retry
if (flags & MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN) if (flags & MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN)
{ {
*need_reopen= TRUE; *need_reopen= TRUE; // Upper level will retry
break; DBUG_RETURN(1); // Fatal error
} }
if (wait_for_tables(thd)) if (wait_for_tables(thd))
break; // Couldn't open tables res= 1; // Couldn't open tables
reset_lock_data(sql_lock, 0); // Set org locks and retry
}
end:
thd_proc_info(thd, 0); thd_proc_info(thd, 0);
if (thd->killed)
{
thd->send_kill_message();
if (res == 0)
mysql_unlock_tables(thd,sql_lock,0);
else
reset_lock_data(sql_lock, 1);
res= 1; // Fatal
}
thd->set_time_after_lock(); thd->set_time_after_lock();
DBUG_RETURN(error); DBUG_RETURN(res);
} }
......
...@@ -2347,7 +2347,7 @@ extern struct st_VioSSLFd * ssl_acceptor_fd; ...@@ -2347,7 +2347,7 @@ extern struct st_VioSSLFd * ssl_acceptor_fd;
MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count,
uint flags, bool *need_reopen); uint flags, bool *need_reopen);
bool mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock, int mysql_lock_tables(THD *thd, MYSQL_LOCK *sql_lock,
bool write_lock_used, bool write_lock_used,
uint flags, bool *need_reopen); uint flags, bool *need_reopen);
......
...@@ -9606,12 +9606,11 @@ open_performance_schema_table(THD *thd, TABLE_LIST *one_table, ...@@ -9606,12 +9606,11 @@ open_performance_schema_table(THD *thd, TABLE_LIST *one_table,
else else
{ {
/* /*
If error in mysql_lock_tables(), open_ltable doesn't close the This can happen during a thd->kill or while we are trying to log
table. Thread kill during mysql_lock_tables() is such error. But data for a stored procedure/trigger and someone causes the table
open tables cannot be accepted when restoring the open tables to be flushed (for example by creating a new trigger for the
state. table)
*/ */
if (thd->killed)
close_thread_tables(thd); close_thread_tables(thd);
thd->restore_backup_open_tables_state(backup); thd->restore_backup_open_tables_state(backup);
} }
......
...@@ -676,7 +676,7 @@ retry: ...@@ -676,7 +676,7 @@ retry:
/* save open_tables state */ /* save open_tables state */
if (handler->lock->lock_count > 0) if (handler->lock->lock_count > 0)
{ {
bool lock_error; int lock_error;
handler->lock->locks[0]->type= handler->lock->locks[0]->org_type; handler->lock->locks[0]->type= handler->lock->locks[0]->org_type;
lock_error= mysql_lock_tables(thd, handler->lock, 0, lock_error= mysql_lock_tables(thd, handler->lock, 0,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment