MDEV-3917 multiple use locks (GET_LOCK) in one connection.
The patch contributed by Konstantin Osipov applied. Native comments: Implement multiple user-level locks per connection. GET_LOCK() function in MySQL allows a connection to hold at most one user level lock. Taking a new lock automatically releases the old lock, if any. The limit of one lock per session existed since early versions of MySQL didn't have a deadlock detector for SQL locks. MDL patches in MySQL 5.5 added a deadlock detector, so starting from 5.5 it became possible to take multiple locks in any order -- a deadlock, should it occur, would be detected and an error returned to the client which closed the wait chain. This is exactly what is done in this patch: ULLs are moved to use MDL subsystem.
This commit is contained in:
parent
ff3407a111
commit
1a600125ff
@ -338,6 +338,227 @@ set optimizer_switch=@optimizer_switch_save;
|
||||
drop view v_merge, vm;
|
||||
drop table t1,tv;
|
||||
#
|
||||
# GET_LOCK, RELEASE_LOCK, IS_USED_LOCK functions test
|
||||
#
|
||||
# IS_USED_LOCK, IS_FREE_LOCK: the lock is not acquired
|
||||
# Note: IS_USED_LOCK returns NULL if the lock is unused
|
||||
select is_used_lock('test');
|
||||
is_used_lock('test')
|
||||
NULL
|
||||
select is_free_lock('test');
|
||||
is_free_lock('test')
|
||||
1
|
||||
# GET_LOCK returns 1 if it manages to acquire a lock
|
||||
select get_lock('test', 0);
|
||||
get_lock('test', 0)
|
||||
1
|
||||
# IS_USED_LOCK, IS_FREE_LOCK: the lock is acquired
|
||||
select is_free_lock('test');
|
||||
is_free_lock('test')
|
||||
0
|
||||
select is_used_lock('test') = connection_id();
|
||||
is_used_lock('test') = connection_id()
|
||||
1
|
||||
# -> Switching to connection 'con1'
|
||||
# IS_USED_LOCK, IS_FREE_LOCK: the lock is acquired in another
|
||||
# connection
|
||||
select is_used_lock('test') = connection_id();
|
||||
is_used_lock('test') = connection_id()
|
||||
0
|
||||
select is_free_lock('test');
|
||||
is_free_lock('test')
|
||||
0
|
||||
# GET_LOCK returns 0 if it can't acquire a lock (wait timeout)
|
||||
select get_lock('test', 0);
|
||||
get_lock('test', 0)
|
||||
0
|
||||
# RELEASE_LOCK returns 0 if the lock belongs to another connection
|
||||
select release_lock('test');
|
||||
release_lock('test')
|
||||
0
|
||||
# -> Switching to connection 'default'
|
||||
# RELEASE_LOCK returns 1 if it successfully releases a lock
|
||||
select release_lock('test');
|
||||
release_lock('test')
|
||||
1
|
||||
# RELEASE_LOCK returns NULL if it doesn't release a lock and there is no such lock
|
||||
select release_lock('test');
|
||||
release_lock('test')
|
||||
NULL
|
||||
# Test that get_lock() returns NULL if error.
|
||||
select get_lock('test', 0);
|
||||
get_lock('test', 0)
|
||||
1
|
||||
# -> Switching to connection 'con1'
|
||||
create table t1 select connection_id() as id;
|
||||
select get_lock('test', 7200);
|
||||
# -> Switching to connection 'default'
|
||||
select (@id := id) - id from t1;
|
||||
(@id := id) - id
|
||||
0
|
||||
kill query @id;
|
||||
# -> Switching to connection 'con1'
|
||||
get_lock('test', 7200)
|
||||
NULL
|
||||
# -> Switching to connection 'default'
|
||||
# GET_LOCK() works recursively
|
||||
select get_lock('test', 0);
|
||||
get_lock('test', 0)
|
||||
1
|
||||
select get_lock('test', 0);
|
||||
get_lock('test', 0)
|
||||
1
|
||||
select get_lock('test', 0);
|
||||
get_lock('test', 0)
|
||||
1
|
||||
# RELEASE_LOCK() needs to be called recursively then, too
|
||||
select release_lock('test');
|
||||
release_lock('test')
|
||||
1
|
||||
select release_lock('test');
|
||||
release_lock('test')
|
||||
1
|
||||
select release_lock('test');
|
||||
release_lock('test')
|
||||
1
|
||||
# Once the last instance of the lock is released,
|
||||
# the next call returns NULL
|
||||
select release_lock('test');
|
||||
release_lock('test')
|
||||
1
|
||||
# Multiple locks in the same session are OK
|
||||
select get_lock('test1', 0);
|
||||
get_lock('test1', 0)
|
||||
1
|
||||
select get_lock('test2', 0);
|
||||
get_lock('test2', 0)
|
||||
1
|
||||
select get_lock('test3', 0);
|
||||
get_lock('test3', 0)
|
||||
1
|
||||
select release_lock('test1');
|
||||
release_lock('test1')
|
||||
1
|
||||
select release_lock('test2');
|
||||
release_lock('test2')
|
||||
1
|
||||
select release_lock('test3');
|
||||
release_lock('test3')
|
||||
1
|
||||
# Deadlocks are detected e.g. in case of a mutual wait
|
||||
select get_lock('test1', 0);
|
||||
get_lock('test1', 0)
|
||||
1
|
||||
# -> Switching to connection 'con1'
|
||||
select get_lock('test2', 0);
|
||||
get_lock('test2', 0)
|
||||
1
|
||||
select get_lock('test1', 7200);
|
||||
# -> Switching to connection 'default'
|
||||
select get_lock('test2', 7200);
|
||||
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
|
||||
select release_lock('test1');
|
||||
release_lock('test1')
|
||||
1
|
||||
# -> Switching to connection 'con1'
|
||||
get_lock('test1', 7200)
|
||||
1
|
||||
select release_lock('test2');
|
||||
release_lock('test2')
|
||||
1
|
||||
select release_lock('test1');
|
||||
release_lock('test1')
|
||||
1
|
||||
# -> Switching to connection 'default'
|
||||
# LOCK/UNLOCK TABLES works fine with a user lock.
|
||||
lock table t1 write;
|
||||
select get_lock('test', 0);
|
||||
get_lock('test', 0)
|
||||
1
|
||||
unlock tables;
|
||||
commit;
|
||||
select release_lock('test');
|
||||
release_lock('test')
|
||||
1
|
||||
# GLOBAL READ LOCK works with fine with user locks
|
||||
select get_lock('test1', 0);
|
||||
get_lock('test1', 0)
|
||||
1
|
||||
flush tables with read lock;
|
||||
select get_lock('test2', 0);
|
||||
get_lock('test2', 0)
|
||||
1
|
||||
unlock tables;
|
||||
commit;
|
||||
select release_lock('test1');
|
||||
release_lock('test1')
|
||||
1
|
||||
select release_lock('test2');
|
||||
release_lock('test2')
|
||||
1
|
||||
# BEGIN/COMMIT/ROLLBACK don't unlock user locks.
|
||||
begin;
|
||||
select get_lock('test1', 0);
|
||||
get_lock('test1', 0)
|
||||
1
|
||||
select get_lock('test2', 0);
|
||||
get_lock('test2', 0)
|
||||
1
|
||||
select count(*) from t1;
|
||||
count(*)
|
||||
1
|
||||
rollback;
|
||||
select release_lock('test1');
|
||||
release_lock('test1')
|
||||
1
|
||||
select release_lock('test2');
|
||||
release_lock('test2')
|
||||
1
|
||||
# Deadlocks between user locks and LOCK TABLES locks
|
||||
# are detected OK.
|
||||
select get_lock('test', 0);
|
||||
get_lock('test', 0)
|
||||
1
|
||||
# -> Switching to connection 'con1'
|
||||
lock table t1 write;
|
||||
select get_lock('test', 7200);
|
||||
# -> Switching to connection 'default'
|
||||
lock table t1 read;
|
||||
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
|
||||
select release_lock('test');
|
||||
release_lock('test')
|
||||
1
|
||||
# -> Switching to connection 'con1'
|
||||
get_lock('test', 7200)
|
||||
1
|
||||
select release_lock('test');
|
||||
release_lock('test')
|
||||
1
|
||||
unlock tables;
|
||||
# cleanup
|
||||
drop table t1;
|
||||
# check too long identifier names
|
||||
select get_lock(repeat('a', 192), 0);
|
||||
get_lock(repeat('a', 192), 0)
|
||||
1
|
||||
select is_used_lock(repeat('a', 192)) = connection_id();
|
||||
is_used_lock(repeat('a', 192)) = connection_id()
|
||||
1
|
||||
select is_free_lock(repeat('a', 192));
|
||||
is_free_lock(repeat('a', 192))
|
||||
0
|
||||
select release_lock(repeat('a', 192));
|
||||
release_lock(repeat('a', 192))
|
||||
1
|
||||
select get_lock(repeat('a', 193), 0);
|
||||
ERROR 42000: Identifier name 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' is too long
|
||||
select is_used_lock(repeat('a', 193));
|
||||
ERROR 42000: Identifier name 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' is too long
|
||||
select is_free_lock(repeat('a', 193));
|
||||
ERROR 42000: Identifier name 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' is too long
|
||||
select release_lock(repeat('a', 193));
|
||||
ERROR 42000: Identifier name 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' is too long
|
||||
#
|
||||
# End of 5.5 tests
|
||||
#
|
||||
#
|
||||
|
@ -43,7 +43,7 @@ insert into t3 values(connection_id());
|
||||
send update t2 set a = a + 1 + get_lock('crash_lock%20C', 10);
|
||||
|
||||
connection master1;
|
||||
let $wait_condition= SELECT a > 1 FROM t2;
|
||||
let $wait_condition= SELECT count(*) > 0 FROM information_schema.processlist WHERE info LIKE 'update%' AND state='User lock';
|
||||
source include/wait_condition.inc;
|
||||
select (@id := id) - id from t3;
|
||||
kill @id;
|
||||
|
@ -88,7 +88,8 @@ insert into t3 select get_lock('crash_lock%20C', 1) from t2;
|
||||
connection master;
|
||||
send update t1 set n = n + get_lock('crash_lock%20C', 2);
|
||||
connection master1;
|
||||
sleep 3;
|
||||
let $wait_condition= SELECT count(*) > 0 FROM information_schema.processlist WHERE info LIKE 'update%' AND state='User lock';
|
||||
source include/wait_condition.inc;
|
||||
select (@id := id) - id from t2;
|
||||
kill @id;
|
||||
# We don't drop t3 as this is a temporary table
|
||||
|
@ -368,6 +368,183 @@ drop view v_merge, vm;
|
||||
drop table t1,tv;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # GET_LOCK, RELEASE_LOCK, IS_USED_LOCK functions test
|
||||
--echo #
|
||||
|
||||
--echo # IS_USED_LOCK, IS_FREE_LOCK: the lock is not acquired
|
||||
--echo # Note: IS_USED_LOCK returns NULL if the lock is unused
|
||||
select is_used_lock('test');
|
||||
select is_free_lock('test');
|
||||
|
||||
--echo # GET_LOCK returns 1 if it manages to acquire a lock
|
||||
select get_lock('test', 0);
|
||||
|
||||
--echo # IS_USED_LOCK, IS_FREE_LOCK: the lock is acquired
|
||||
select is_free_lock('test');
|
||||
select is_used_lock('test') = connection_id();
|
||||
|
||||
connect (con1,localhost,root,,);
|
||||
--echo # -> Switching to connection 'con1'
|
||||
connection con1;
|
||||
--echo # IS_USED_LOCK, IS_FREE_LOCK: the lock is acquired in another
|
||||
--echo # connection
|
||||
select is_used_lock('test') = connection_id();
|
||||
select is_free_lock('test');
|
||||
|
||||
--echo # GET_LOCK returns 0 if it can't acquire a lock (wait timeout)
|
||||
select get_lock('test', 0);
|
||||
|
||||
--echo # RELEASE_LOCK returns 0 if the lock belongs to another connection
|
||||
select release_lock('test');
|
||||
|
||||
--echo # -> Switching to connection 'default'
|
||||
connection default;
|
||||
|
||||
--echo # RELEASE_LOCK returns 1 if it successfully releases a lock
|
||||
select release_lock('test');
|
||||
--echo # RELEASE_LOCK returns NULL if it doesn't release a lock and there is no such lock
|
||||
select release_lock('test');
|
||||
|
||||
--echo # Test that get_lock() returns NULL if error.
|
||||
select get_lock('test', 0);
|
||||
--echo # -> Switching to connection 'con1'
|
||||
connection con1;
|
||||
create table t1 select connection_id() as id;
|
||||
send select get_lock('test', 7200);
|
||||
|
||||
--echo # -> Switching to connection 'default'
|
||||
connection default;
|
||||
let $wait_condition= SELECT count(*) > 0 FROM information_schema.processlist WHERE info LIKE 'select%' AND state='User lock';
|
||||
source include/wait_condition.inc;
|
||||
select (@id := id) - id from t1;
|
||||
kill query @id;
|
||||
|
||||
--echo # -> Switching to connection 'con1'
|
||||
connection con1;
|
||||
reap;
|
||||
|
||||
--echo # -> Switching to connection 'default'
|
||||
connection default;
|
||||
|
||||
--echo # GET_LOCK() works recursively
|
||||
select get_lock('test', 0);
|
||||
select get_lock('test', 0);
|
||||
select get_lock('test', 0);
|
||||
|
||||
--echo # RELEASE_LOCK() needs to be called recursively then, too
|
||||
select release_lock('test');
|
||||
select release_lock('test');
|
||||
select release_lock('test');
|
||||
|
||||
--echo # Once the last instance of the lock is released,
|
||||
--echo # the next call returns NULL
|
||||
select release_lock('test');
|
||||
|
||||
|
||||
--echo # Multiple locks in the same session are OK
|
||||
select get_lock('test1', 0);
|
||||
select get_lock('test2', 0);
|
||||
select get_lock('test3', 0);
|
||||
|
||||
select release_lock('test1');
|
||||
select release_lock('test2');
|
||||
select release_lock('test3');
|
||||
|
||||
--echo # Deadlocks are detected e.g. in case of a mutual wait
|
||||
select get_lock('test1', 0);
|
||||
|
||||
--echo # -> Switching to connection 'con1'
|
||||
connection con1;
|
||||
select get_lock('test2', 0);
|
||||
send select get_lock('test1', 7200);
|
||||
|
||||
--echo # -> Switching to connection 'default'
|
||||
connection default;
|
||||
let $wait_condition= SELECT count(*) > 0 FROM information_schema.processlist WHERE info LIKE 'select%' AND state='User lock';
|
||||
source include/wait_condition.inc;
|
||||
--error ER_LOCK_DEADLOCK
|
||||
select get_lock('test2', 7200);
|
||||
|
||||
select release_lock('test1');
|
||||
|
||||
--echo # -> Switching to connection 'con1'
|
||||
connection con1;
|
||||
reap;
|
||||
select release_lock('test2');
|
||||
select release_lock('test1');
|
||||
|
||||
--echo # -> Switching to connection 'default'
|
||||
connection default;
|
||||
|
||||
--echo # LOCK/UNLOCK TABLES works fine with a user lock.
|
||||
lock table t1 write;
|
||||
select get_lock('test', 0);
|
||||
unlock tables;
|
||||
commit;
|
||||
select release_lock('test');
|
||||
|
||||
--echo # GLOBAL READ LOCK works with fine with user locks
|
||||
select get_lock('test1', 0);
|
||||
flush tables with read lock;
|
||||
select get_lock('test2', 0);
|
||||
unlock tables;
|
||||
commit;
|
||||
select release_lock('test1');
|
||||
select release_lock('test2');
|
||||
|
||||
--echo # BEGIN/COMMIT/ROLLBACK don't unlock user locks.
|
||||
begin;
|
||||
select get_lock('test1', 0);
|
||||
select get_lock('test2', 0);
|
||||
select count(*) from t1;
|
||||
rollback;
|
||||
select release_lock('test1');
|
||||
select release_lock('test2');
|
||||
|
||||
--echo # Deadlocks between user locks and LOCK TABLES locks
|
||||
--echo # are detected OK.
|
||||
select get_lock('test', 0);
|
||||
|
||||
--echo # -> Switching to connection 'con1'
|
||||
connection con1;
|
||||
lock table t1 write;
|
||||
send select get_lock('test', 7200);
|
||||
|
||||
--echo # -> Switching to connection 'default'
|
||||
connection default;
|
||||
let $wait_condition= SELECT count(*) > 0 FROM information_schema.processlist WHERE info LIKE 'select%' AND state = 'User lock';
|
||||
source include/wait_condition.inc;
|
||||
--error ER_LOCK_DEADLOCK
|
||||
lock table t1 read;
|
||||
|
||||
select release_lock('test');
|
||||
|
||||
--echo # -> Switching to connection 'con1'
|
||||
connection con1;
|
||||
reap;
|
||||
select release_lock('test');
|
||||
unlock tables;
|
||||
|
||||
--echo # cleanup
|
||||
disconnect con1;
|
||||
connection default;
|
||||
drop table t1;
|
||||
|
||||
--echo # check too long identifier names
|
||||
select get_lock(repeat('a', 192), 0);
|
||||
select is_used_lock(repeat('a', 192)) = connection_id();
|
||||
select is_free_lock(repeat('a', 192));
|
||||
select release_lock(repeat('a', 192));
|
||||
--error ER_TOO_LONG_IDENT
|
||||
select get_lock(repeat('a', 193), 0);
|
||||
--error ER_TOO_LONG_IDENT
|
||||
select is_used_lock(repeat('a', 193));
|
||||
--error ER_TOO_LONG_IDENT
|
||||
select is_free_lock(repeat('a', 193));
|
||||
--error ER_TOO_LONG_IDENT
|
||||
select release_lock(repeat('a', 193));
|
||||
|
||||
--echo #
|
||||
--echo # End of 5.5 tests
|
||||
--echo #
|
||||
|
@ -198,7 +198,7 @@ Hybrid_type_traits_integer::fix_length_and_dec(Item *item, Item *arg) const
|
||||
|
||||
void item_init(void)
|
||||
{
|
||||
item_user_lock_init();
|
||||
item_func_sleep_init();
|
||||
uuid_short_init();
|
||||
}
|
||||
|
||||
|
596
sql/item_func.cc
596
sql/item_func.cc
@ -3750,120 +3750,6 @@ udf_handler::~udf_handler()
|
||||
bool udf_handler::get_arguments() { return 0; }
|
||||
#endif /* HAVE_DLOPEN */
|
||||
|
||||
/*
|
||||
** User level locks
|
||||
*/
|
||||
|
||||
mysql_mutex_t LOCK_user_locks;
|
||||
static HASH hash_user_locks;
|
||||
|
||||
class User_level_lock
|
||||
{
|
||||
uchar *key;
|
||||
size_t key_length;
|
||||
|
||||
public:
|
||||
int count;
|
||||
bool locked;
|
||||
mysql_cond_t cond;
|
||||
my_thread_id thread_id;
|
||||
void set_thread(THD *thd) { thread_id= thd->thread_id; }
|
||||
|
||||
User_level_lock(const uchar *key_arg,uint length, ulong id)
|
||||
:key_length(length),count(1),locked(1), thread_id(id)
|
||||
{
|
||||
key= (uchar*) my_memdup(key_arg,length,MYF(0));
|
||||
mysql_cond_init(key_user_level_lock_cond, &cond, NULL);
|
||||
if (key)
|
||||
{
|
||||
if (my_hash_insert(&hash_user_locks,(uchar*) this))
|
||||
{
|
||||
my_free(key);
|
||||
key=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
~User_level_lock()
|
||||
{
|
||||
if (key)
|
||||
{
|
||||
my_hash_delete(&hash_user_locks,(uchar*) this);
|
||||
my_free(key);
|
||||
}
|
||||
mysql_cond_destroy(&cond);
|
||||
}
|
||||
inline bool initialized() { return key != 0; }
|
||||
friend void item_user_lock_release(User_level_lock *ull);
|
||||
friend uchar *ull_get_key(const User_level_lock *ull, size_t *length,
|
||||
my_bool not_used);
|
||||
};
|
||||
|
||||
uchar *ull_get_key(const User_level_lock *ull, size_t *length,
|
||||
my_bool not_used __attribute__((unused)))
|
||||
{
|
||||
*length= ull->key_length;
|
||||
return ull->key;
|
||||
}
|
||||
|
||||
#ifdef HAVE_PSI_INTERFACE
|
||||
static PSI_mutex_key key_LOCK_user_locks;
|
||||
|
||||
static PSI_mutex_info all_user_mutexes[]=
|
||||
{
|
||||
{ &key_LOCK_user_locks, "LOCK_user_locks", PSI_FLAG_GLOBAL}
|
||||
};
|
||||
|
||||
static void init_user_lock_psi_keys(void)
|
||||
{
|
||||
const char* category= "sql";
|
||||
int count;
|
||||
|
||||
if (PSI_server == NULL)
|
||||
return;
|
||||
|
||||
count= array_elements(all_user_mutexes);
|
||||
PSI_server->register_mutex(category, all_user_mutexes, count);
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool item_user_lock_inited= 0;
|
||||
|
||||
void item_user_lock_init(void)
|
||||
{
|
||||
#ifdef HAVE_PSI_INTERFACE
|
||||
init_user_lock_psi_keys();
|
||||
#endif
|
||||
|
||||
mysql_mutex_init(key_LOCK_user_locks, &LOCK_user_locks, MY_MUTEX_INIT_SLOW);
|
||||
my_hash_init(&hash_user_locks,system_charset_info,
|
||||
16,0,0,(my_hash_get_key) ull_get_key,NULL,0);
|
||||
item_user_lock_inited= 1;
|
||||
}
|
||||
|
||||
void item_user_lock_free(void)
|
||||
{
|
||||
if (item_user_lock_inited)
|
||||
{
|
||||
item_user_lock_inited= 0;
|
||||
my_hash_free(&hash_user_locks);
|
||||
mysql_mutex_destroy(&LOCK_user_locks);
|
||||
}
|
||||
}
|
||||
|
||||
void item_user_lock_release(User_level_lock *ull)
|
||||
{
|
||||
ull->locked=0;
|
||||
ull->thread_id= 0;
|
||||
if (--ull->count)
|
||||
mysql_cond_signal(&ull->cond);
|
||||
else
|
||||
delete ull;
|
||||
}
|
||||
|
||||
/**
|
||||
Wait until we are at or past the given position in the master binlog
|
||||
on the slave.
|
||||
*/
|
||||
|
||||
longlong Item_master_pos_wait::val_int()
|
||||
{
|
||||
@ -4010,7 +3896,136 @@ int Interruptible_wait::wait(mysql_cond_t *cond, mysql_mutex_t *mutex)
|
||||
|
||||
|
||||
/**
|
||||
Get a user level lock. If the thread has an old lock this is first released.
|
||||
For locks with EXPLICIT duration, MDL returns a new ticket
|
||||
every time a lock is granted. This allows to implement recursive
|
||||
locks without extra allocation or additional data structures, such
|
||||
as below. However, if there are too many tickets in the same
|
||||
MDL_context, MDL_context::find_ticket() is getting too slow,
|
||||
since it's using a linear search.
|
||||
This is why a separate structure is allocated for a user
|
||||
level lock, and before requesting a new lock from MDL,
|
||||
GET_LOCK() checks thd->ull_hash if such lock is already granted,
|
||||
and if so, simply increments a reference counter.
|
||||
*/
|
||||
|
||||
class User_level_lock
|
||||
{
|
||||
public:
|
||||
MDL_ticket *lock;
|
||||
int refs;
|
||||
};
|
||||
|
||||
|
||||
/** Extract a hash key from User_level_lock. */
|
||||
|
||||
uchar *ull_get_key(const uchar *ptr, size_t *length,
|
||||
my_bool not_used __attribute__((unused)))
|
||||
{
|
||||
User_level_lock *ull = (User_level_lock*) ptr;
|
||||
MDL_key *key = ull->lock->get_key();
|
||||
*length= key->length();
|
||||
return (uchar*) key->ptr();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Release all user level locks for this THD.
|
||||
*/
|
||||
|
||||
void mysql_ull_cleanup(THD *thd)
|
||||
{
|
||||
User_level_lock *ull;
|
||||
DBUG_ENTER("mysql_ull_cleanup");
|
||||
|
||||
for (uint i= 0; i < thd->ull_hash.records; i++)
|
||||
{
|
||||
ull = (User_level_lock*) my_hash_element(&thd->ull_hash, i);
|
||||
thd->mdl_context.release_lock(ull->lock);
|
||||
my_free(ull);
|
||||
}
|
||||
|
||||
my_hash_free(&thd->ull_hash);
|
||||
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Set explicit duration for metadata locks corresponding to
|
||||
user level locks to protect them from being released at the end
|
||||
of transaction.
|
||||
*/
|
||||
|
||||
void mysql_ull_set_explicit_lock_duration(THD *thd)
|
||||
{
|
||||
User_level_lock *ull;
|
||||
DBUG_ENTER("mysql_ull_set_explicit_lock_duration");
|
||||
|
||||
for (uint i= 0; i < thd->ull_hash.records; i++)
|
||||
{
|
||||
ull= (User_level_lock*) my_hash_element(&thd->ull_hash, i);
|
||||
thd->mdl_context.set_lock_duration(ull->lock, MDL_EXPLICIT);
|
||||
}
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
When MDL detects a lock wait timeout, it pushes
|
||||
an error into the statement diagnostics area.
|
||||
For GET_LOCK(), lock wait timeout is not an error,
|
||||
but a special return value (0). NULL is returned in
|
||||
case of error.
|
||||
Capture and suppress lock wait timeout.
|
||||
*/
|
||||
|
||||
class Lock_wait_timeout_handler: public Internal_error_handler
|
||||
{
|
||||
public:
|
||||
Lock_wait_timeout_handler() :m_lock_wait_timeout(false) {}
|
||||
|
||||
bool m_lock_wait_timeout;
|
||||
|
||||
bool handle_condition(THD * /* thd */, uint sql_errno,
|
||||
const char * /* sqlstate */,
|
||||
MYSQL_ERROR::enum_warning_level /* level */,
|
||||
const char *message,
|
||||
MYSQL_ERROR ** /* cond_hdl */);
|
||||
};
|
||||
|
||||
bool
|
||||
Lock_wait_timeout_handler::
|
||||
handle_condition(THD * /* thd */, uint sql_errno,
|
||||
const char * /* sqlstate */,
|
||||
MYSQL_ERROR::enum_warning_level /* level */,
|
||||
const char *message,
|
||||
MYSQL_ERROR ** /* cond_hdl */)
|
||||
{
|
||||
if (sql_errno == ER_LOCK_WAIT_TIMEOUT)
|
||||
{
|
||||
m_lock_wait_timeout= true;
|
||||
return true; /* condition handled */
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static int ull_name_ok(String *name)
|
||||
{
|
||||
if (!name || !name->length())
|
||||
return 0;
|
||||
|
||||
if (name->length() > NAME_LEN)
|
||||
{
|
||||
my_error(ER_TOO_LONG_IDENT, MYF(0), name->c_ptr_safe());
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Get a user level lock.
|
||||
|
||||
@retval
|
||||
1 : Got lock
|
||||
@ -4023,14 +4038,13 @@ int Interruptible_wait::wait(mysql_cond_t *cond, mysql_mutex_t *mutex)
|
||||
longlong Item_func_get_lock::val_int()
|
||||
{
|
||||
DBUG_ASSERT(fixed == 1);
|
||||
String *res=args[0]->val_str(&value);
|
||||
String *res= args[0]->val_str(&value);
|
||||
ulonglong timeout= args[1]->val_int();
|
||||
THD *thd=current_thd;
|
||||
THD *thd= current_thd;
|
||||
User_level_lock *ull;
|
||||
int error;
|
||||
Interruptible_wait timed_cond(thd);
|
||||
DBUG_ENTER("Item_func_get_lock::val_int");
|
||||
|
||||
null_value= 1;
|
||||
/*
|
||||
In slave thread no need to get locks, everything is serialized. Anyway
|
||||
there is no way to make GET_LOCK() work on slave like it did on master
|
||||
@ -4039,104 +4053,70 @@ longlong Item_func_get_lock::val_int()
|
||||
it's not guaranteed to be same as on master.
|
||||
*/
|
||||
if (thd->slave_thread)
|
||||
DBUG_RETURN(1);
|
||||
|
||||
mysql_mutex_lock(&LOCK_user_locks);
|
||||
|
||||
if (!res || !res->length())
|
||||
{
|
||||
mysql_mutex_unlock(&LOCK_user_locks);
|
||||
null_value=1;
|
||||
DBUG_RETURN(0);
|
||||
null_value= 0;
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
if (!ull_name_ok(res))
|
||||
DBUG_RETURN(0);
|
||||
|
||||
DBUG_PRINT("info", ("lock %.*s, thd=%ld", res->length(), res->ptr(),
|
||||
(long) thd->real_id));
|
||||
null_value=0;
|
||||
|
||||
if (thd->ull)
|
||||
/* HASH entries are of type User_level_lock. */
|
||||
if (! my_hash_inited(&thd->ull_hash) &&
|
||||
my_hash_init(&thd->ull_hash, &my_charset_bin,
|
||||
16 /* small hash */, 0, 0, ull_get_key, NULL, 0))
|
||||
{
|
||||
item_user_lock_release(thd->ull);
|
||||
thd->ull=0;
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
if (!(ull= ((User_level_lock *) my_hash_search(&hash_user_locks,
|
||||
(uchar*) res->ptr(),
|
||||
(size_t) res->length()))))
|
||||
MDL_request ull_request;
|
||||
ull_request.init(MDL_key::USER_LOCK, res->c_ptr_safe(), "",
|
||||
MDL_SHARED_NO_WRITE, MDL_EXPLICIT);
|
||||
MDL_key *ull_key = &ull_request.key;
|
||||
|
||||
|
||||
if ((ull= (User_level_lock*)
|
||||
my_hash_search(&thd->ull_hash, ull_key->ptr(), ull_key->length())))
|
||||
{
|
||||
ull= new User_level_lock((uchar*) res->ptr(), (size_t) res->length(),
|
||||
thd->thread_id);
|
||||
if (!ull || !ull->initialized())
|
||||
{
|
||||
delete ull;
|
||||
mysql_mutex_unlock(&LOCK_user_locks);
|
||||
null_value=1; // Probably out of memory
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
ull->set_thread(thd);
|
||||
thd->ull=ull;
|
||||
mysql_mutex_unlock(&LOCK_user_locks);
|
||||
DBUG_PRINT("info", ("made new lock"));
|
||||
DBUG_RETURN(1); // Got new lock
|
||||
/* Recursive lock */
|
||||
ull->refs++;
|
||||
null_value = 0;
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
ull->count++;
|
||||
DBUG_PRINT("info", ("ull->count=%d", ull->count));
|
||||
|
||||
/*
|
||||
Structure is now initialized. Try to get the lock.
|
||||
Set up control struct to allow others to abort locks.
|
||||
*/
|
||||
thd_proc_info(thd, "User lock");
|
||||
thd->mysys_var->current_mutex= &LOCK_user_locks;
|
||||
thd->mysys_var->current_cond= &ull->cond;
|
||||
|
||||
timed_cond.set_timeout(timeout * 1000000000ULL);
|
||||
|
||||
error= 0;
|
||||
thd_wait_begin(thd, THD_WAIT_USER_LOCK);
|
||||
while (ull->locked && !thd->killed)
|
||||
Lock_wait_timeout_handler lock_wait_timeout_handler;
|
||||
thd->push_internal_handler(&lock_wait_timeout_handler);
|
||||
bool error= thd->mdl_context.acquire_lock(&ull_request, timeout);
|
||||
(void) thd->pop_internal_handler();
|
||||
if (error)
|
||||
{
|
||||
DBUG_PRINT("info", ("waiting on lock"));
|
||||
error= timed_cond.wait(&ull->cond, &LOCK_user_locks);
|
||||
if (error == ETIMEDOUT || error == ETIME)
|
||||
{
|
||||
DBUG_PRINT("info", ("lock wait timeout"));
|
||||
break;
|
||||
}
|
||||
error= 0;
|
||||
if (lock_wait_timeout_handler.m_lock_wait_timeout)
|
||||
null_value= 0;
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
thd_wait_end(thd);
|
||||
|
||||
if (ull->locked)
|
||||
ull= (User_level_lock*) my_malloc(sizeof(User_level_lock),
|
||||
MYF(MY_WME|MY_THREAD_SPECIFIC));
|
||||
if (ull == NULL)
|
||||
{
|
||||
if (!--ull->count)
|
||||
{
|
||||
DBUG_ASSERT(0);
|
||||
delete ull; // Should never happen
|
||||
}
|
||||
if (!error) // Killed (thd->killed != 0)
|
||||
{
|
||||
error=1;
|
||||
null_value=1; // Return NULL
|
||||
}
|
||||
thd->mdl_context.release_lock(ull_request.ticket);
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
else // We got the lock
|
||||
|
||||
ull->lock= ull_request.ticket;
|
||||
ull->refs= 1;
|
||||
|
||||
if (my_hash_insert(&thd->ull_hash, (uchar*) ull))
|
||||
{
|
||||
ull->locked=1;
|
||||
ull->set_thread(thd);
|
||||
ull->thread_id= thd->thread_id;
|
||||
thd->ull=ull;
|
||||
error=0;
|
||||
DBUG_PRINT("info", ("got the lock"));
|
||||
thd->mdl_context.release_lock(ull->lock);
|
||||
my_free(ull);
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
mysql_mutex_unlock(&LOCK_user_locks);
|
||||
null_value= 0;
|
||||
|
||||
mysql_mutex_lock(&thd->mysys_var->mutex);
|
||||
thd_proc_info(thd, 0);
|
||||
thd->mysys_var->current_mutex= 0;
|
||||
thd->mysys_var->current_cond= 0;
|
||||
mysql_mutex_unlock(&thd->mysys_var->mutex);
|
||||
|
||||
DBUG_RETURN(!error ? 1 : 0);
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
|
||||
@ -4151,43 +4131,86 @@ longlong Item_func_get_lock::val_int()
|
||||
longlong Item_func_release_lock::val_int()
|
||||
{
|
||||
DBUG_ASSERT(fixed == 1);
|
||||
String *res=args[0]->val_str(&value);
|
||||
User_level_lock *ull;
|
||||
longlong result;
|
||||
THD *thd=current_thd;
|
||||
String *res= args[0]->val_str(&value);
|
||||
THD *thd= current_thd;
|
||||
DBUG_ENTER("Item_func_release_lock::val_int");
|
||||
if (!res || !res->length())
|
||||
null_value= 1;
|
||||
|
||||
if (!ull_name_ok(res))
|
||||
DBUG_RETURN(0);
|
||||
|
||||
DBUG_PRINT("info", ("lock %.*s", res->length(), res->ptr()));
|
||||
|
||||
MDL_key ull_key;
|
||||
ull_key.mdl_key_init(MDL_key::USER_LOCK, res->c_ptr_safe(), "");
|
||||
|
||||
User_level_lock *ull;
|
||||
|
||||
if (!(ull=
|
||||
(User_level_lock*) my_hash_search(&thd->ull_hash,
|
||||
ull_key.ptr(), ull_key.length())))
|
||||
{
|
||||
null_value=1;
|
||||
null_value= thd->mdl_context.get_lock_owner(&ull_key) == 0;
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
DBUG_PRINT("info", ("lock %.*s", res->length(), res->ptr()));
|
||||
null_value=0;
|
||||
null_value= 0;
|
||||
if (--ull->refs == 0)
|
||||
{
|
||||
my_hash_delete(&thd->ull_hash, (uchar*) ull);
|
||||
thd->mdl_context.release_lock(ull->lock);
|
||||
my_free(ull);
|
||||
}
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
result=0;
|
||||
mysql_mutex_lock(&LOCK_user_locks);
|
||||
if (!(ull= ((User_level_lock*) my_hash_search(&hash_user_locks,
|
||||
(const uchar*) res->ptr(),
|
||||
(size_t) res->length()))))
|
||||
{
|
||||
null_value=1;
|
||||
}
|
||||
else
|
||||
{
|
||||
DBUG_PRINT("info", ("ull->locked=%d ull->thread=%lu thd=%lu",
|
||||
(int) ull->locked,
|
||||
(long)ull->thread_id,
|
||||
(long)thd->thread_id));
|
||||
if (ull->locked && current_thd->thread_id == ull->thread_id)
|
||||
{
|
||||
DBUG_PRINT("info", ("release lock"));
|
||||
result=1; // Release is ok
|
||||
item_user_lock_release(ull);
|
||||
thd->ull=0;
|
||||
}
|
||||
}
|
||||
mysql_mutex_unlock(&LOCK_user_locks);
|
||||
DBUG_RETURN(result);
|
||||
|
||||
/**
|
||||
Check a user level lock.
|
||||
|
||||
Sets null_value=TRUE on error.
|
||||
|
||||
@retval
|
||||
1 Available
|
||||
@retval
|
||||
0 Already taken, or error
|
||||
*/
|
||||
|
||||
longlong Item_func_is_free_lock::val_int()
|
||||
{
|
||||
DBUG_ASSERT(fixed == 1);
|
||||
String *res= args[0]->val_str(&value);
|
||||
THD *thd= current_thd;
|
||||
null_value= 1;
|
||||
|
||||
if (!ull_name_ok(res))
|
||||
return 0;
|
||||
|
||||
MDL_key ull_key;
|
||||
ull_key.mdl_key_init(MDL_key::USER_LOCK, res->c_ptr_safe(), "");
|
||||
|
||||
null_value= 0;
|
||||
return thd->mdl_context.get_lock_owner(&ull_key) == 0;
|
||||
}
|
||||
|
||||
|
||||
longlong Item_func_is_used_lock::val_int()
|
||||
{
|
||||
DBUG_ASSERT(fixed == 1);
|
||||
String *res= args[0]->val_str(&value);
|
||||
THD *thd= current_thd;
|
||||
null_value= 1;
|
||||
|
||||
if (!ull_name_ok(res))
|
||||
return 0;
|
||||
|
||||
MDL_key ull_key;
|
||||
ull_key.mdl_key_init(MDL_key::USER_LOCK, res->c_ptr_safe(), "");
|
||||
ulong thread_id = thd->mdl_context.get_lock_owner(&ull_key);
|
||||
if (thread_id == 0)
|
||||
return 0;
|
||||
|
||||
null_value= 0;
|
||||
return thread_id;
|
||||
}
|
||||
|
||||
|
||||
@ -4288,6 +4311,54 @@ void Item_func_benchmark::print(String *str, enum_query_type query_type)
|
||||
}
|
||||
|
||||
|
||||
mysql_mutex_t LOCK_item_func_sleep;
|
||||
|
||||
#ifdef HAVE_PSI_INTERFACE
|
||||
static PSI_mutex_key key_LOCK_item_func_sleep;
|
||||
|
||||
static PSI_mutex_info item_func_sleep_mutexes[]=
|
||||
{
|
||||
{ &key_LOCK_item_func_sleep, "LOCK_user_locks", PSI_FLAG_GLOBAL}
|
||||
};
|
||||
|
||||
|
||||
static void init_item_func_sleep_psi_keys(void)
|
||||
{
|
||||
const char* category= "sql";
|
||||
int count;
|
||||
|
||||
if (PSI_server == NULL)
|
||||
return;
|
||||
|
||||
count= array_elements(item_func_sleep_mutexes);
|
||||
PSI_server->register_mutex(category, item_func_sleep_mutexes, count);
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool item_func_sleep_inited= 0;
|
||||
|
||||
|
||||
void item_func_sleep_init(void)
|
||||
{
|
||||
#ifdef HAVE_PSI_INTERFACE
|
||||
init_item_func_sleep_psi_keys();
|
||||
#endif
|
||||
|
||||
mysql_mutex_init(key_LOCK_item_func_sleep, &LOCK_item_func_sleep, MY_MUTEX_INIT_SLOW);
|
||||
item_func_sleep_inited= 1;
|
||||
}
|
||||
|
||||
|
||||
void item_func_sleep_free(void)
|
||||
{
|
||||
if (item_func_sleep_inited)
|
||||
{
|
||||
item_func_sleep_inited= 0;
|
||||
mysql_mutex_destroy(&LOCK_item_func_sleep);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** This function is just used to create tests with time gaps. */
|
||||
|
||||
longlong Item_func_sleep::val_int()
|
||||
@ -4316,24 +4387,24 @@ longlong Item_func_sleep::val_int()
|
||||
timed_cond.set_timeout((ulonglong) (timeout * 1000000000.0));
|
||||
|
||||
mysql_cond_init(key_item_func_sleep_cond, &cond, NULL);
|
||||
mysql_mutex_lock(&LOCK_user_locks);
|
||||
mysql_mutex_lock(&LOCK_item_func_sleep);
|
||||
|
||||
thd_proc_info(thd, "User sleep");
|
||||
thd->mysys_var->current_mutex= &LOCK_user_locks;
|
||||
thd->mysys_var->current_mutex= &LOCK_item_func_sleep;
|
||||
thd->mysys_var->current_cond= &cond;
|
||||
|
||||
error= 0;
|
||||
thd_wait_begin(thd, THD_WAIT_SLEEP);
|
||||
while (!thd->killed)
|
||||
{
|
||||
error= timed_cond.wait(&cond, &LOCK_user_locks);
|
||||
error= timed_cond.wait(&cond, &LOCK_item_func_sleep);
|
||||
if (error == ETIMEDOUT || error == ETIME)
|
||||
break;
|
||||
error= 0;
|
||||
}
|
||||
thd_wait_end(thd);
|
||||
thd_proc_info(thd, 0);
|
||||
mysql_mutex_unlock(&LOCK_user_locks);
|
||||
mysql_mutex_unlock(&LOCK_item_func_sleep);
|
||||
mysql_mutex_lock(&thd->mysys_var->mutex);
|
||||
thd->mysys_var->current_mutex= 0;
|
||||
thd->mysys_var->current_cond= 0;
|
||||
@ -6208,61 +6279,6 @@ Item *get_system_var(THD *thd, enum_var_type var_type, LEX_STRING name,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Check a user level lock.
|
||||
|
||||
Sets null_value=TRUE on error.
|
||||
|
||||
@retval
|
||||
1 Available
|
||||
@retval
|
||||
0 Already taken, or error
|
||||
*/
|
||||
|
||||
longlong Item_func_is_free_lock::val_int()
|
||||
{
|
||||
DBUG_ASSERT(fixed == 1);
|
||||
String *res=args[0]->val_str(&value);
|
||||
User_level_lock *ull;
|
||||
|
||||
null_value=0;
|
||||
if (!res || !res->length())
|
||||
{
|
||||
null_value=1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
mysql_mutex_lock(&LOCK_user_locks);
|
||||
ull= (User_level_lock *) my_hash_search(&hash_user_locks, (uchar*) res->ptr(),
|
||||
(size_t) res->length());
|
||||
mysql_mutex_unlock(&LOCK_user_locks);
|
||||
if (!ull || !ull->locked)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
longlong Item_func_is_used_lock::val_int()
|
||||
{
|
||||
DBUG_ASSERT(fixed == 1);
|
||||
String *res=args[0]->val_str(&value);
|
||||
User_level_lock *ull;
|
||||
|
||||
null_value=1;
|
||||
if (!res || !res->length())
|
||||
return 0;
|
||||
|
||||
mysql_mutex_lock(&LOCK_user_locks);
|
||||
ull= (User_level_lock *) my_hash_search(&hash_user_locks, (uchar*) res->ptr(),
|
||||
(size_t) res->length());
|
||||
mysql_mutex_unlock(&LOCK_user_locks);
|
||||
if (!ull || !ull->locked)
|
||||
return 0;
|
||||
|
||||
null_value=0;
|
||||
return ull->thread_id;
|
||||
}
|
||||
|
||||
|
||||
longlong Item_func_row_count::val_int()
|
||||
{
|
||||
DBUG_ASSERT(fixed == 1);
|
||||
|
@ -1257,6 +1257,9 @@ public:
|
||||
};
|
||||
|
||||
|
||||
void item_func_sleep_init(void);
|
||||
void item_func_sleep_free(void);
|
||||
|
||||
class Item_func_sleep :public Item_int_func
|
||||
{
|
||||
public:
|
||||
@ -1506,14 +1509,8 @@ public:
|
||||
|
||||
#endif /* HAVE_DLOPEN */
|
||||
|
||||
/*
|
||||
** User level locks
|
||||
*/
|
||||
|
||||
class User_level_lock;
|
||||
void item_user_lock_init(void);
|
||||
void item_user_lock_release(User_level_lock *ull);
|
||||
void item_user_lock_free(void);
|
||||
void mysql_ull_cleanup(THD *thd);
|
||||
void mysql_ull_set_explicit_lock_duration(THD *thd);
|
||||
|
||||
class Item_func_get_lock :public Item_int_func
|
||||
{
|
||||
|
128
sql/mdl.cc
128
sql/mdl.cc
@ -85,7 +85,8 @@ const char *MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]=
|
||||
"Waiting for stored procedure metadata lock",
|
||||
"Waiting for trigger metadata lock",
|
||||
"Waiting for event metadata lock",
|
||||
"Waiting for commit lock"
|
||||
"Waiting for commit lock",
|
||||
"User lock" /* Be compatible with old status. */
|
||||
};
|
||||
|
||||
static bool mdl_initialized= 0;
|
||||
@ -107,6 +108,7 @@ public:
|
||||
void init();
|
||||
void destroy();
|
||||
MDL_lock *find_or_insert(const MDL_key *key);
|
||||
unsigned long get_lock_owner(const MDL_key *key);
|
||||
void remove(MDL_lock *lock);
|
||||
private:
|
||||
bool move_from_hash_to_lock_mutex(MDL_lock *lock);
|
||||
@ -382,6 +384,7 @@ public:
|
||||
bool ignore_lock_priority) const;
|
||||
|
||||
inline static MDL_lock *create(const MDL_key *key);
|
||||
inline unsigned long get_lock_owner() const;
|
||||
|
||||
void reschedule_waiters();
|
||||
|
||||
@ -856,6 +859,43 @@ bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return thread id of the owner of the lock, if it is owned.
|
||||
*/
|
||||
|
||||
unsigned long
|
||||
MDL_map::get_lock_owner(const MDL_key *mdl_key)
|
||||
{
|
||||
MDL_lock *lock;
|
||||
unsigned long res= 0;
|
||||
|
||||
if (mdl_key->mdl_namespace() == MDL_key::GLOBAL ||
|
||||
mdl_key->mdl_namespace() == MDL_key::COMMIT)
|
||||
{
|
||||
lock= (mdl_key->mdl_namespace() == MDL_key::GLOBAL) ? m_global_lock :
|
||||
m_commit_lock;
|
||||
mysql_prlock_rdlock(&lock->m_rwlock);
|
||||
res= lock->get_lock_owner();
|
||||
mysql_prlock_unlock(&lock->m_rwlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
my_hash_value_type hash_value= my_calc_hash(&m_locks,
|
||||
mdl_key->ptr(),
|
||||
mdl_key->length());
|
||||
mysql_mutex_lock(&m_mutex);
|
||||
lock= (MDL_lock*) my_hash_search_using_hash_value(&m_locks,
|
||||
hash_value,
|
||||
mdl_key->ptr(),
|
||||
mdl_key->length());
|
||||
if (lock)
|
||||
res= lock->get_lock_owner();
|
||||
mysql_mutex_unlock(&m_mutex);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Destroy MDL_lock object or delegate this responsibility to
|
||||
whatever thread that holds the last outstanding reference to
|
||||
@ -1621,6 +1661,23 @@ MDL_lock::can_grant_lock(enum_mdl_type type_arg,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Return thread id of the thread to which the first ticket was
|
||||
granted.
|
||||
*/
|
||||
|
||||
inline unsigned long
|
||||
MDL_lock::get_lock_owner() const
|
||||
{
|
||||
Ticket_iterator it(m_granted);
|
||||
MDL_ticket *ticket;
|
||||
|
||||
if ((ticket= it++))
|
||||
return thd_get_thread_id(ticket->get_ctx()->get_thd());
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/** Remove a ticket from waiting or pending queue and wakeup up waiters. */
|
||||
|
||||
void MDL_lock::remove_ticket(Ticket_list MDL_lock::*list, MDL_ticket *ticket)
|
||||
@ -2094,31 +2151,37 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout)
|
||||
|
||||
find_deadlock();
|
||||
|
||||
if (lock->needs_notification(ticket))
|
||||
struct timespec abs_shortwait;
|
||||
set_timespec(abs_shortwait, 1);
|
||||
wait_status= MDL_wait::EMPTY;
|
||||
|
||||
while (cmp_timespec(abs_shortwait, abs_timeout) <= 0)
|
||||
{
|
||||
struct timespec abs_shortwait;
|
||||
set_timespec(abs_shortwait, 1);
|
||||
wait_status= MDL_wait::EMPTY;
|
||||
/* abs_timeout is far away. Wait a short while and notify locks. */
|
||||
wait_status= m_wait.timed_wait(m_thd, &abs_shortwait, FALSE,
|
||||
mdl_request->key.get_wait_state_name());
|
||||
|
||||
while (cmp_timespec(abs_shortwait, abs_timeout) <= 0)
|
||||
if (wait_status != MDL_wait::EMPTY)
|
||||
break;
|
||||
/* Check if the client is gone while we were waiting. */
|
||||
if (! thd_is_connected(m_thd))
|
||||
{
|
||||
/* abs_timeout is far away. Wait a short while and notify locks. */
|
||||
wait_status= m_wait.timed_wait(m_thd, &abs_shortwait, FALSE,
|
||||
mdl_request->key.get_wait_state_name());
|
||||
|
||||
if (wait_status != MDL_wait::EMPTY)
|
||||
break;
|
||||
|
||||
mysql_prlock_wrlock(&lock->m_rwlock);
|
||||
lock->notify_conflicting_locks(this);
|
||||
mysql_prlock_unlock(&lock->m_rwlock);
|
||||
set_timespec(abs_shortwait, 1);
|
||||
/*
|
||||
* The client is disconnected. Don't wait forever:
|
||||
* assume it's the same as a wait timeout, this
|
||||
* ensures all error handling is correct.
|
||||
*/
|
||||
wait_status= MDL_wait::TIMEOUT;
|
||||
break;
|
||||
}
|
||||
if (wait_status == MDL_wait::EMPTY)
|
||||
wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE,
|
||||
mdl_request->key.get_wait_state_name());
|
||||
|
||||
mysql_prlock_wrlock(&lock->m_rwlock);
|
||||
if (lock->needs_notification(ticket))
|
||||
lock->notify_conflicting_locks(this);
|
||||
mysql_prlock_unlock(&lock->m_rwlock);
|
||||
set_timespec(abs_shortwait, 1);
|
||||
}
|
||||
else
|
||||
if (wait_status == MDL_wait::EMPTY)
|
||||
wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE,
|
||||
mdl_request->key.get_wait_state_name());
|
||||
|
||||
@ -2613,7 +2676,7 @@ void MDL_context::release_lock(MDL_ticket *ticket)
|
||||
the corresponding lists, i.e. stored in reverse temporal order.
|
||||
This allows to employ this function to:
|
||||
- back off in case of a lock conflict.
|
||||
- release all locks in the end of a statment or transaction
|
||||
- release all locks in the end of a statement or transaction
|
||||
- rollback to a savepoint.
|
||||
*/
|
||||
|
||||
@ -2724,6 +2787,22 @@ MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Return thread id of the owner of the lock or 0 if
|
||||
there is no owner.
|
||||
@note: Lock type is not considered at all, the function
|
||||
simply checks that there is some lock for the given key.
|
||||
|
||||
@return thread id of the owner of the lock or 0
|
||||
*/
|
||||
|
||||
unsigned long
|
||||
MDL_context::get_lock_owner(MDL_key *key)
|
||||
{
|
||||
return mdl_locks.get_lock_owner(key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Check if we have any pending locks which conflict with existing shared lock.
|
||||
|
||||
@ -2737,6 +2816,11 @@ bool MDL_ticket::has_pending_conflicting_lock() const
|
||||
return m_lock->has_pending_conflicting_lock(m_type);
|
||||
}
|
||||
|
||||
/** Return a key identifying this lock. */
|
||||
MDL_key *MDL_ticket::get_key() const
|
||||
{
|
||||
return &m_lock->key;
|
||||
}
|
||||
|
||||
/**
|
||||
Releases metadata locks that were acquired after a specific savepoint.
|
||||
|
43
sql/mdl.h
43
sql/mdl.h
@ -212,6 +212,7 @@ public:
|
||||
TRIGGER,
|
||||
EVENT,
|
||||
COMMIT,
|
||||
USER_LOCK, /* user level locks. */
|
||||
/* This should be the last ! */
|
||||
NAMESPACE_END };
|
||||
|
||||
@ -492,6 +493,7 @@ public:
|
||||
}
|
||||
enum_mdl_type get_type() const { return m_type; }
|
||||
MDL_lock *get_lock() const { return m_lock; }
|
||||
MDL_key *get_key() const;
|
||||
void downgrade_exclusive_lock(enum_mdl_type type);
|
||||
|
||||
bool has_stronger_or_equal_type(enum_mdl_type type) const;
|
||||
@ -653,6 +655,7 @@ public:
|
||||
bool is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
|
||||
const char *db, const char *name,
|
||||
enum_mdl_type mdl_type);
|
||||
unsigned long get_lock_owner(MDL_key *mdl_key);
|
||||
|
||||
bool has_lock(const MDL_savepoint &mdl_savepoint, MDL_ticket *mdl_ticket);
|
||||
|
||||
@ -721,9 +724,9 @@ private:
|
||||
Lists of MDL tickets:
|
||||
---------------------
|
||||
The entire set of locks acquired by a connection can be separated
|
||||
in three subsets according to their: locks released at the end of
|
||||
statement, at the end of transaction and locks are released
|
||||
explicitly.
|
||||
in three subsets according to their duration: locks released at
|
||||
the end of statement, at the end of transaction and locks are
|
||||
released explicitly.
|
||||
|
||||
Statement and transactional locks are locks with automatic scope.
|
||||
They are accumulated in the course of a transaction, and released
|
||||
@ -732,11 +735,12 @@ private:
|
||||
locks). They must not be (and never are) released manually,
|
||||
i.e. with release_lock() call.
|
||||
|
||||
Locks with explicit duration are taken for locks that span
|
||||
Tickets with explicit duration are taken for locks that span
|
||||
multiple transactions or savepoints.
|
||||
These are: HANDLER SQL locks (HANDLER SQL is
|
||||
transaction-agnostic), LOCK TABLES locks (you can COMMIT/etc
|
||||
under LOCK TABLES, and the locked tables stay locked), and
|
||||
under LOCK TABLES, and the locked tables stay locked), user level
|
||||
locks (GET_LOCK()/RELEASE_LOCK() functions) and
|
||||
locks implementing "global read lock".
|
||||
|
||||
Statement/transactional locks are always prepended to the
|
||||
@ -745,20 +749,19 @@ private:
|
||||
a savepoint, we start popping and releasing tickets from the
|
||||
front until we reach the last ticket acquired after the savepoint.
|
||||
|
||||
Locks with explicit duration stored are not stored in any
|
||||
Locks with explicit duration are not stored in any
|
||||
particular order, and among each other can be split into
|
||||
three sets:
|
||||
four sets:
|
||||
|
||||
[LOCK TABLES locks] [HANDLER locks] [GLOBAL READ LOCK locks]
|
||||
[LOCK TABLES locks] [USER locks] [HANDLER locks] [GLOBAL READ LOCK locks]
|
||||
|
||||
The following is known about these sets:
|
||||
|
||||
* GLOBAL READ LOCK locks are always stored after LOCK TABLES
|
||||
locks and after HANDLER locks. This is because one can't say
|
||||
SET GLOBAL read_only=1 or FLUSH TABLES WITH READ LOCK
|
||||
if one has locked tables. One can, however, LOCK TABLES
|
||||
after having entered the read only mode. Note, that
|
||||
subsequent LOCK TABLES statement will unlock the previous
|
||||
* GLOBAL READ LOCK locks are always stored last.
|
||||
This is because one can't say SET GLOBAL read_only=1 or
|
||||
FLUSH TABLES WITH READ LOCK if one has locked tables. One can,
|
||||
however, LOCK TABLES after having entered the read only mode.
|
||||
Note, that subsequent LOCK TABLES statement will unlock the previous
|
||||
set of tables, but not the GRL!
|
||||
There are no HANDLER locks after GRL locks because
|
||||
SET GLOBAL read_only performs a FLUSH TABLES WITH
|
||||
@ -853,6 +856,18 @@ extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use,
|
||||
extern "C" const char* thd_enter_cond(MYSQL_THD thd, mysql_cond_t *cond,
|
||||
mysql_mutex_t *mutex, const char *msg);
|
||||
extern "C" void thd_exit_cond(MYSQL_THD thd, const char *old_msg);
|
||||
extern "C" unsigned long thd_get_thread_id(const MYSQL_THD thd);
|
||||
|
||||
/**
|
||||
Check if a connection in question is no longer connected.
|
||||
|
||||
@details
|
||||
Replication apply thread is always connected. Otherwise,
|
||||
does a poll on the associated socket to check if the client
|
||||
is gone.
|
||||
*/
|
||||
extern "C" int thd_is_connected(MYSQL_THD thd);
|
||||
|
||||
|
||||
#ifndef DBUG_OFF
|
||||
extern mysql_mutex_t LOCK_open;
|
||||
|
@ -1824,7 +1824,7 @@ void clean_up(bool print_message)
|
||||
#endif
|
||||
query_cache_destroy();
|
||||
hostname_cache_free();
|
||||
item_user_lock_free();
|
||||
item_func_sleep_free();
|
||||
lex_free(); /* Free some memory */
|
||||
item_create_cleanup();
|
||||
if (!opt_noacl)
|
||||
|
@ -335,7 +335,7 @@ extern MYSQL_PLUGIN_IMPORT key_map key_map_full; /* Should be threaded
|
||||
Server mutex locks and condition variables.
|
||||
*/
|
||||
extern mysql_mutex_t
|
||||
LOCK_user_locks, LOCK_status,
|
||||
LOCK_item_func_sleep, LOCK_status,
|
||||
LOCK_error_log, LOCK_delayed_insert, LOCK_short_uuid_generator,
|
||||
LOCK_delayed_status, LOCK_delayed_create, LOCK_crypt, LOCK_timezone,
|
||||
LOCK_slave_list, LOCK_active_mi, LOCK_manager,
|
||||
|
@ -827,6 +827,7 @@ THD::THD()
|
||||
col_access=0;
|
||||
is_slave_error= thread_specific_used= FALSE;
|
||||
my_hash_clear(&handler_tables_hash);
|
||||
my_hash_clear(&ull_hash);
|
||||
tmp_table=0;
|
||||
cuted_fields= 0L;
|
||||
sent_row_count= 0L;
|
||||
@ -866,7 +867,6 @@ THD::THD()
|
||||
net.vio=0;
|
||||
net.buff= 0;
|
||||
client_capabilities= 0; // minimalistic client
|
||||
ull=0;
|
||||
system_thread= NON_SYSTEM_THREAD;
|
||||
cleanup_done= abort_on_warning= 0;
|
||||
peer_port= 0; // For SHOW PROCESSLIST
|
||||
@ -1400,8 +1400,6 @@ void THD::cleanup(void)
|
||||
if (global_read_lock.is_acquired())
|
||||
global_read_lock.unlock_global_read_lock(this);
|
||||
|
||||
/* All metadata locks must have been released by now. */
|
||||
DBUG_ASSERT(!mdl_context.has_locks());
|
||||
if (user_connect)
|
||||
{
|
||||
decrease_user_connections(user_connect);
|
||||
@ -1419,13 +1417,9 @@ void THD::cleanup(void)
|
||||
sp_cache_clear(&sp_proc_cache);
|
||||
sp_cache_clear(&sp_func_cache);
|
||||
|
||||
if (ull)
|
||||
{
|
||||
mysql_mutex_lock(&LOCK_user_locks);
|
||||
item_user_lock_release(ull);
|
||||
mysql_mutex_unlock(&LOCK_user_locks);
|
||||
ull= NULL;
|
||||
}
|
||||
mysql_ull_cleanup(this);
|
||||
/* All metadata locks must have been released by now. */
|
||||
DBUG_ASSERT(!mdl_context.has_locks());
|
||||
|
||||
apc_target.destroy();
|
||||
cleanup_done=1;
|
||||
@ -4001,6 +3995,15 @@ extern "C" unsigned long thd_get_thread_id(const MYSQL_THD thd)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Check if THD socket is still connected.
|
||||
*/
|
||||
extern "C" int thd_is_connected(MYSQL_THD thd)
|
||||
{
|
||||
return thd->is_connected();
|
||||
}
|
||||
|
||||
|
||||
#ifdef INNODB_COMPATIBILITY_HOOKS
|
||||
extern "C" const struct charset_info_st *thd_charset(MYSQL_THD thd)
|
||||
{
|
||||
@ -4321,6 +4324,8 @@ void THD::leave_locked_tables_mode()
|
||||
/* Also ensure that we don't release metadata locks for open HANDLERs. */
|
||||
if (handler_tables_hash.records)
|
||||
mysql_ha_set_explicit_lock_duration(this);
|
||||
if (ull_hash.records)
|
||||
mysql_ull_set_explicit_lock_duration(this);
|
||||
}
|
||||
locked_tables_mode= LTM_NONE;
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ class Lex_input_stream;
|
||||
class Parser_state;
|
||||
class Rows_log_event;
|
||||
class Sroutine_hash_entry;
|
||||
class User_level_lock;
|
||||
class user_var_entry;
|
||||
|
||||
enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE };
|
||||
@ -1682,11 +1681,11 @@ public:
|
||||
|
||||
HASH handler_tables_hash;
|
||||
/*
|
||||
One thread can hold up to one named user-level lock. This variable
|
||||
points to a lock object if the lock is present. See item_func.cc and
|
||||
A thread can hold named user-level locks. This variable
|
||||
contains granted tickets if a lock is present. See item_func.cc and
|
||||
chapter 'Miscellaneous functions', for functions GET_LOCK, RELEASE_LOCK.
|
||||
*/
|
||||
User_level_lock *ull;
|
||||
HASH ull_hash;
|
||||
#ifndef DBUG_OFF
|
||||
uint dbug_sentry; // watch out for memory corruption
|
||||
#endif
|
||||
|
@ -205,6 +205,7 @@ bool reload_acl_and_cache(THD *thd, unsigned long options,
|
||||
DBUG_ASSERT(!thd || thd->locked_tables_mode ||
|
||||
!thd->mdl_context.has_locks() ||
|
||||
thd->handler_tables_hash.records ||
|
||||
thd->ull_hash.records ||
|
||||
thd->global_read_lock.is_acquired());
|
||||
|
||||
/*
|
||||
|
Loading…
x
Reference in New Issue
Block a user