summaryrefslogtreecommitdiff
path: root/sql/lock.cc
diff options
context:
space:
mode:
authorunknown <guilhem@mysql.com>2004-08-20 16:35:23 +0200
committerunknown <guilhem@mysql.com>2004-08-20 16:35:23 +0200
commitcd8054d4318077827bcf20e640af6fcddf1d9525 (patch)
tree9d89f6beb28a1d747c302488d23cb8d5bfca7507 /sql/lock.cc
parentf758ada4bcfdf9b22b1603bc273b5e2a6436037b (diff)
downloadmariadb-git-cd8054d4318077827bcf20e640af6fcddf1d9525.tar.gz
Making FLUSH TABLES WITH READ LOCK block COMMITs of existing transactions,
in a deadlock-free manner. This splits locking the global read lock in two steps. This fixes a consequence of this bug, known as: BUG#4953 'mysqldump --master-data may report incorrect binlog position if using InnoDB' And a test. sql/handler.cc: making COMMIT wait if FLUSH TABLES WITH READ LOCK happened. sql/lock.cc: an additional stage so that FLUSH TABLES WITH READ LOCK blocks COMMIT: make_global_read_lock_block_commit(): taking the global read lock is TWO steps (2nd step is optional; without it, COMMIT of existing transactions will be allowed): lock_global_read_lock() THEN make_global_read_lock_block_commit(). sql/mysql_priv.h: new argument to wait_if_global_read_lock() sql/sql_class.h: THD::global_read_lock now an uint to reflect the 2 steps of global read lock (does not block COMMIT / does) sql/sql_db.cc: update for new prototype sql/sql_parse.cc: implementing the two steps of global read lock so that FLUSH TABLES WITH READ LOCK can block COMMIT without deadlocking with COMMITs.
Diffstat (limited to 'sql/lock.cc')
-rw-r--r--sql/lock.cc54
1 files changed, 47 insertions, 7 deletions
diff --git a/sql/lock.cc b/sql/lock.cc
index 9ea1ce96175..dd2b61b65d2 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -96,7 +96,7 @@ 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 (wait_if_global_read_lock(thd, 1))
+ if (wait_if_global_read_lock(thd, 1, 1))
{
my_free((gptr) sql_lock,MYF(0));
sql_lock=0;
@@ -453,7 +453,7 @@ int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list)
int error= -1;
DBUG_ENTER("lock_and_wait_for_table_name");
- if (wait_if_global_read_lock(thd,0))
+ 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)
@@ -667,14 +667,23 @@ static void print_lock_error(int error)
The global locks are handled through the global variables:
global_read_lock
+ global_read_lock_blocks_commit
waiting_for_read_lock
protect_against_global_read_lock
+
+ Taking the global read lock is TWO steps (2nd step is optional; without
+ it, COMMIT of existing transactions will be allowed):
+ lock_global_read_lock() THEN make_global_read_lock_block_commit().
****************************************************************************/
volatile uint global_read_lock=0;
+volatile uint global_read_lock_blocks_commit=0;
static volatile uint protect_against_global_read_lock=0;
static volatile uint waiting_for_read_lock=0;
+#define GOT_GLOBAL_READ_LOCK 1
+#define MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT 2
+
bool lock_global_read_lock(THD *thd)
{
DBUG_ENTER("lock_global_read_lock");
@@ -697,27 +706,40 @@ bool lock_global_read_lock(THD *thd)
thd->exit_cond(old_message);
DBUG_RETURN(1);
}
- thd->global_read_lock=1;
+ thd->global_read_lock= GOT_GLOBAL_READ_LOCK;
global_read_lock++;
thd->exit_cond(old_message);
}
+ /*
+ We DON'T set global_read_lock_blocks_commit now, it will be set after
+ tables are flushed (as the present function serves for FLUSH TABLES WITH
+ READ LOCK only). Doing things in this order is necessary to avoid
+ deadlocks (we must allow COMMIT until all tables are closed; we should not
+ forbid it before, or we can have a 3-thread deadlock if 2 do SELECT FOR
+ UPDATE and one does FLUSH TABLES WITH READ LOCK).
+ */
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;
+ if (thd->global_read_lock == MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT)
+ --global_read_lock_blocks_commit;
pthread_mutex_unlock(&LOCK_open);
/* Send the signal outside the mutex to avoid a context switch */
if (!tmp)
pthread_cond_broadcast(&COND_refresh);
+ thd->global_read_lock= 0;
}
+#define must_wait (global_read_lock && \
+ (is_not_commit || \
+ global_read_lock_blocks_commit))
-bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh)
+bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, bool is_not_commit)
{
const char *old_message;
bool result= 0, need_exit_cond;
@@ -725,7 +747,7 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh)
LINT_INIT(old_message);
(void) pthread_mutex_lock(&LOCK_open);
- if (need_exit_cond= (bool)global_read_lock)
+ if (need_exit_cond= must_wait)
{
if (thd->global_read_lock) // This thread had the read locks
{
@@ -735,7 +757,7 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh)
}
old_message=thd->enter_cond(&COND_refresh, &LOCK_open,
"Waiting for release of readlock");
- while (global_read_lock && ! thd->killed &&
+ while (must_wait && ! thd->killed &&
(!abort_on_refresh || thd->version == refresh_version))
(void) pthread_cond_wait(&COND_refresh,&LOCK_open);
if (thd->killed)
@@ -762,3 +784,21 @@ void start_waiting_global_read_lock(THD *thd)
pthread_cond_broadcast(&COND_refresh);
DBUG_VOID_RETURN;
}
+
+
+void make_global_read_lock_block_commit(THD *thd)
+{
+ /*
+ If we didn't succeed lock_global_read_lock(), or if we already suceeded
+ make_global_read_lock_block_commit(), do nothing.
+ */
+ if (thd->global_read_lock != GOT_GLOBAL_READ_LOCK)
+ return;
+ pthread_mutex_lock(&LOCK_open);
+ /* increment this BEFORE waiting on cond (otherwise race cond) */
+ global_read_lock_blocks_commit++;
+ while (protect_against_global_read_lock)
+ pthread_cond_wait(&COND_refresh, &LOCK_open);
+ pthread_mutex_unlock(&LOCK_open);
+ thd->global_read_lock= MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT;
+}