summaryrefslogtreecommitdiff
path: root/mysys/thr_lock.c
diff options
context:
space:
mode:
authorDmitry Lenev <dlenev@mysql.com>2009-10-26 22:38:03 +0300
committerDmitry Lenev <dlenev@mysql.com>2009-10-26 22:38:03 +0300
commit86c23fa70876143a35e972bb3af58fc029eb5f5d (patch)
treead7fc131e57406b9157365a5324395ce2ad1e07e /mysys/thr_lock.c
parent90e2ad95959870e537d40040d926f55333182722 (diff)
downloadmariadb-git-86c23fa70876143a35e972bb3af58fc029eb5f5d.tar.gz
Fix for bug #45143 "All connections hang on concurrent ALTER TABLE".
Concurrent execution of statements which require non-table-level write locks on several instances of the same table (such as SELECT ... FOR UPDATE which uses same InnoDB table twice or a DML statement which invokes trigger which tries to update same InnoDB table directly and through stored function) and statements which required table-level locks on this table (e.g. LOCK TABLE ... WRITE, ALTER TABLE, ...) might have resulted in a deadlock. The problem occured when a thread tried to acquire write lock (TL_WRITE_ALLOW_WRITE) on the table but had to wait since there was a pending write lock (TL_WRITE, TL_WRITE_ALLOW_READ) on this table and we failed to detect that this thread already had another instance of write lock on it (so in fact we were trying to acquire recursive lock) because there was also another thread holding write lock on the table (also TL_WRITE_ALLOW_WRITE). When the latter thread released its lock neither the first thread nor the thread trying to acquire TL_WRITE/TL_WRITE_ALLOW_READ were woken up (as table was still write locked by the first thread) so we ended up with a deadlock. This patch solves this problem by ensuring that thread which already has write lock on the table won't wait when it tries to acquire second write lock on the same table. mysql-test/r/lock_sync.result: Added test case for bug #45143 "All connections hang on concurrent ALTER TABLE". mysql-test/t/lock_sync.test: Added test case for bug #45143 "All connections hang on concurrent ALTER TABLE". mysys/thr_lock.c: Ensured that thread can acquire write lock on the table without waiting if it already has write lock on it even if there are other threads holding write locks on this table (this is normal situation for, e.g., TL_WRITE_ALLOW_WRITE type of lock). Adjusted comments to better explain why it is OK to do so and added asserts to prevent introduction of scenarios in which this can cause problems.
Diffstat (limited to 'mysys/thr_lock.c')
-rw-r--r--mysys/thr_lock.c41
1 files changed, 32 insertions, 9 deletions
diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c
index 0e0e93cf220..4bb818b1b30 100644
--- a/mysys/thr_lock.c
+++ b/mysys/thr_lock.c
@@ -362,7 +362,7 @@ void thr_lock_data_init(THR_LOCK *lock,THR_LOCK_DATA *data, void *param)
static inline my_bool
-have_old_read_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner)
+has_old_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner)
{
for ( ; data ; data=data->next)
{
@@ -572,7 +572,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
else if (!lock->write_wait.data ||
lock->write_wait.data->type <= TL_WRITE_LOW_PRIORITY ||
lock_type == TL_READ_HIGH_PRIORITY ||
- have_old_read_lock(lock->read.data, data->owner))
+ has_old_lock(lock->read.data, data->owner)) /* Has old read lock */
{ /* No important write-locks */
(*lock->read.last)=data; /* Add to running FIFO */
data->prev=lock->read.last;
@@ -642,14 +642,36 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
}
/*
- The following test will not work if the old lock was a
- TL_WRITE_ALLOW_WRITE, TL_WRITE_ALLOW_READ or TL_WRITE_DELAYED in
- the same thread, but this will never happen within MySQL.
+ The idea is to allow us to get a lock at once if we already have
+ a write lock or if there is no pending write locks and if all
+ write locks are of TL_WRITE_ALLOW_WRITE type.
+
+ Note that, since lock requests for the same table are sorted in
+ such way that requests with higher thr_lock_type value come first,
+ lock being requested usually has equal or "weaker" type than one
+ which thread might have already acquired.
+ The exceptions are situations when:
+ - old lock type is TL_WRITE_ALLOW_READ and new lock type is
+ TL_WRITE_ALLOW_WRITE
+ - when old lock type is TL_WRITE_DELAYED
+ But these should never happen within MySQL.
+ Therefore it is OK to allow acquiring write lock on the table if
+ this thread already holds some write lock on it.
+
+ (INSERT INTO t1 VALUES (f1()), where f1() is stored function which
+ tries to update t1, is an example of statement which requests two
+ different types of write lock on the same table).
*/
- if (thr_lock_owner_equal(data->owner, lock->write.data->owner) ||
- (lock_type == TL_WRITE_ALLOW_WRITE &&
- !lock->write_wait.data &&
- lock->write.data->type == TL_WRITE_ALLOW_WRITE))
+ DBUG_ASSERT(! has_old_lock(lock->write.data, data->owner) ||
+ (lock_type <= lock->write.data->type &&
+ ! ((lock_type < TL_WRITE_ALLOW_READ &&
+ lock->write.data->type == TL_WRITE_ALLOW_READ) ||
+ lock->write.data->type == TL_WRITE_DELAYED)));
+
+ if ((lock_type == TL_WRITE_ALLOW_WRITE &&
+ ! lock->write_wait.data &&
+ lock->write.data->type == TL_WRITE_ALLOW_WRITE) ||
+ has_old_lock(lock->write.data, data->owner))
{
/*
We have already got a write lock or all locks are
@@ -998,6 +1020,7 @@ thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner)
thr_multi_unlock(data,(uint) (pos-data));
DBUG_RETURN(result);
}
+ DEBUG_SYNC_C("thr_multi_lock_after_thr_lock");
#ifdef MAIN
printf("Thread: %s Got lock: 0x%lx type: %d\n",my_thread_name(),
(long) pos[0]->lock, pos[0]->type); fflush(stdout);