summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mysql-test/r/show_explain.result20
-rw-r--r--mysql-test/t/show_explain.test20
-rw-r--r--sql/my_apc.cc109
-rw-r--r--sql/my_apc.h39
-rw-r--r--sql/sql_class.cc4
-rw-r--r--sql/sql_show.cc5
6 files changed, 119 insertions, 78 deletions
diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result
index 515118b1cd8..d6c46c81c79 100644
--- a/mysql-test/r/show_explain.result
+++ b/mysql-test/r/show_explain.result
@@ -596,4 +596,24 @@ count(*)
212
set debug_dbug='';
drop table t1;
+#
+# MDEV-297: SHOW EXPLAIN: Server gets stuck until timeout occurs while
+# executing SHOW INDEX and SHOW EXPLAIN in parallel
+#
+CREATE TABLE t1(a INT, b INT, c INT, KEY(a), KEY(b), KEY(c));
+INSERT INTO t1 (a) VALUES (3),(1),(5),(1);
+set @show_explain_probe_select_id=1;
+set debug_dbug='d,show_explain_probe_join_exec_start';
+SHOW INDEX FROM t1;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE STATISTICS ALL NULL NULL NULL NULL NULL Skip_open_table; Scanned all databases
+Warnings:
+Note 1003 SHOW INDEX FROM t1
+Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
+t1 1 a 1 a A NULL NULL NULL YES BTREE
+t1 1 b 1 b A NULL NULL NULL YES BTREE
+t1 1 c 1 c A NULL NULL NULL YES BTREE
+set debug_dbug='';
+DROP TABLE t1;
drop table t0;
diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test
index 2baccea93be..5605e304bcc 100644
--- a/mysql-test/t/show_explain.test
+++ b/mysql-test/t/show_explain.test
@@ -581,6 +581,26 @@ reap;
set debug_dbug='';
drop table t1;
+--echo #
+--echo # MDEV-297: SHOW EXPLAIN: Server gets stuck until timeout occurs while
+--echo # executing SHOW INDEX and SHOW EXPLAIN in parallel
+--echo #
+CREATE TABLE t1(a INT, b INT, c INT, KEY(a), KEY(b), KEY(c));
+INSERT INTO t1 (a) VALUES (3),(1),(5),(1);
+
+set @show_explain_probe_select_id=1;
+set debug_dbug='d,show_explain_probe_join_exec_start';
+
+send SHOW INDEX FROM t1;
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+set debug_dbug='';
+
+DROP TABLE t1;
+
## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a
## thread and served together.
diff --git a/sql/my_apc.cc b/sql/my_apc.cc
index 30adbaad1b6..d5e3eb080a2 100644
--- a/sql/my_apc.cc
+++ b/sql/my_apc.cc
@@ -9,6 +9,8 @@
#include <my_pthread.h>
#include <my_sys.h>
+#include "my_apc.h"
+
#else
#include "sql_priv.h"
@@ -16,7 +18,6 @@
#endif
-//#include "my_apc.h"
/*
Standalone testing:
@@ -33,12 +34,10 @@
any call requests to it.
Initial state after initialization is 'disabled'.
*/
-void Apc_target::init()
+void Apc_target::init(mysql_mutex_t *target_mutex)
{
- // todo: should use my_pthread_... functions instead?
DBUG_ASSERT(!enabled);
- (void)pthread_mutex_init(&LOCK_apc_queue, MY_MUTEX_INIT_SLOW);
-
+ LOCK_thd_data_ptr= target_mutex;
#ifndef DBUG_OFF
n_calls_processed= 0;
#endif
@@ -51,7 +50,6 @@ void Apc_target::init()
void Apc_target::destroy()
{
DBUG_ASSERT(!enabled);
- pthread_mutex_destroy(&LOCK_apc_queue);
}
@@ -60,9 +58,8 @@ void Apc_target::destroy()
*/
void Apc_target::enable()
{
- pthread_mutex_lock(&LOCK_apc_queue);
+ /* Ok to do without getting/releasing the mutex: */
enabled++;
- pthread_mutex_unlock(&LOCK_apc_queue);
}
@@ -76,16 +73,16 @@ void Apc_target::enable()
void Apc_target::disable()
{
bool process= FALSE;
- pthread_mutex_lock(&LOCK_apc_queue);
+ mysql_mutex_lock(LOCK_thd_data_ptr);
if (!(--enabled))
process= TRUE;
- pthread_mutex_unlock(&LOCK_apc_queue);
+ mysql_mutex_unlock(LOCK_thd_data_ptr);
if (process)
process_apc_requests();
}
-/* (internal) Put request into the request list */
+/* [internal] Put request qe into the request list */
void Apc_target::enqueue_request(Call_request *qe)
{
@@ -108,7 +105,7 @@ void Apc_target::enqueue_request(Call_request *qe)
/*
- (internal) Remove given request from the request queue.
+ [internal] Remove request qe from the request queue.
The request is not necessarily first in the queue.
*/
@@ -131,11 +128,14 @@ void Apc_target::dequeue_request(Call_request *qe)
/*
- Make an APC (Async Procedure Call) in another thread.
+ Make an APC (Async Procedure Call) to another thread.
+
+ - The caller is responsible for making sure he's not calling to the same
+ thread.
+
+ - The caller should have locked target_thread_mutex.
+
- The caller is responsible for making sure he's not calling an Apc_target
- that is serviced by the same thread it is called from.
-
psergey-todo: Should waits here be KILLable? (it seems one needs
to use thd->enter_cond() calls to be killable)
*/
@@ -146,7 +146,6 @@ bool Apc_target::make_apc_call(apc_func_t func, void *func_arg,
bool res= TRUE;
*timed_out= FALSE;
- pthread_mutex_lock(&LOCK_apc_queue);
if (enabled)
{
/* Create and post the request */
@@ -154,51 +153,48 @@ bool Apc_target::make_apc_call(apc_func_t func, void *func_arg,
apc_request.func= func;
apc_request.func_arg= func_arg;
apc_request.processed= FALSE;
- (void)pthread_cond_init(&apc_request.COND_request, NULL);
- (void)pthread_mutex_init(&apc_request.LOCK_request, MY_MUTEX_INIT_SLOW);
- pthread_mutex_lock(&apc_request.LOCK_request);
+ mysql_cond_init(0 /* do not track in PS */, &apc_request.COND_request, NULL);
enqueue_request(&apc_request);
apc_request.what="enqueued by make_apc_call";
- pthread_mutex_unlock(&LOCK_apc_queue);
struct timespec abstime;
const int timeout= timeout_sec;
set_timespec(abstime, timeout);
-
+
int wait_res= 0;
/* todo: how about processing other errors here? */
while (!apc_request.processed && (wait_res != ETIMEDOUT))
{
- wait_res= pthread_cond_timedwait(&apc_request.COND_request,
- &apc_request.LOCK_request, &abstime);
+ /* We own LOCK_thd_data_ptr */
+ wait_res= mysql_cond_timedwait(&apc_request.COND_request,
+ LOCK_thd_data_ptr, &abstime);
+ // &apc_request.LOCK_request, &abstime);
}
if (!apc_request.processed)
{
- /* The wait has timed out. Remove the request from the queue */
+ /*
+ The wait has timed out. Remove the request from the queue (ok to do
+ because we own LOCK_thd_data_ptr.
+ */
apc_request.processed= TRUE;
- *timed_out= TRUE;
- pthread_mutex_unlock(&apc_request.LOCK_request);
- //psergey-todo: "Whoa rare event" refers to this part, right? put a comment.
- pthread_mutex_lock(&LOCK_apc_queue);
dequeue_request(&apc_request);
- pthread_mutex_unlock(&LOCK_apc_queue);
+ *timed_out= TRUE;
res= TRUE;
}
else
{
/* Request was successfully executed and dequeued by the target thread */
- pthread_mutex_unlock(&apc_request.LOCK_request);
res= FALSE;
}
+ mysql_mutex_unlock(LOCK_thd_data_ptr);
/* Destroy all APC request data */
- pthread_mutex_destroy(&apc_request.LOCK_request);
- pthread_cond_destroy(&apc_request.COND_request);
+ mysql_cond_destroy(&apc_request.COND_request);
}
else
{
- pthread_mutex_unlock(&LOCK_apc_queue);
+ mysql_mutex_unlock(LOCK_thd_data_ptr);
}
return res;
}
@@ -211,58 +207,37 @@ bool Apc_target::make_apc_call(apc_func_t func, void *func_arg,
void Apc_target::process_apc_requests()
{
+ if (!get_first_in_queue())
+ return;
+
while (1)
{
Call_request *request;
-
- pthread_mutex_lock(&LOCK_apc_queue);
+
+ mysql_mutex_lock(LOCK_thd_data_ptr);
if (!(request= get_first_in_queue()))
{
- pthread_mutex_unlock(&LOCK_apc_queue);
+ /* No requests in the queue */
+ mysql_mutex_unlock(LOCK_thd_data_ptr);
break;
}
- request->what="seen by process_apc_requests";
- pthread_mutex_lock(&request->LOCK_request);
-
- if (request->processed)
- {
- /*
- We can get here when
- - the requestor thread has been waiting for this request
- - the wait has timed out
- - it has set request->done=TRUE
- - it has released LOCK_request, because its next action
- will be to remove the request from the queue, however,
- it could not attempt to lock the queue while holding the lock on
- request, because that would deadlock with this function
- (we here first lock the queue and then lock the request)
- */
- pthread_mutex_unlock(&request->LOCK_request);
- pthread_mutex_unlock(&LOCK_apc_queue);
- fprintf(stderr, "Whoa rare event #1!\n");
- continue;
- }
/*
- Remove the request from the queue (we're holding its lock so we can be
+ Remove the request from the queue (we're holding queue lock so we can be
sure that request owner won't try to remove it)
*/
request->what="dequeued by process_apc_requests";
dequeue_request(request);
request->processed= TRUE;
- pthread_mutex_unlock(&LOCK_apc_queue);
-
request->func(request->func_arg);
request->what="func called by process_apc_requests";
#ifndef DBUG_OFF
n_calls_processed++;
#endif
-
- pthread_cond_signal(&request->COND_request);
-
- pthread_mutex_unlock(&request->LOCK_request);
+ mysql_cond_signal(&request->COND_request);
+ mysql_mutex_unlock(LOCK_thd_data_ptr);
}
}
@@ -280,6 +255,7 @@ volatile int apcs_missed=0;
volatile int apcs_timed_out=0;
Apc_target apc_target;
+mysql_mutex_t target_mutex;
int int_rand(int size)
{
@@ -290,7 +266,8 @@ int int_rand(int size)
void *test_apc_service_thread(void *ptr)
{
my_thread_init();
- apc_target.init();
+ mysql_mutex_init(0, &target_mutex, MY_MUTEX_INIT_FAST);
+ apc_target.init(&target_mutex);
apc_target.enable();
started= TRUE;
fprintf(stderr, "# test_apc_service_thread started\n");
diff --git a/sql/my_apc.h b/sql/my_apc.h
index 0698703ad40..8e6980fa855 100644
--- a/sql/my_apc.h
+++ b/sql/my_apc.h
@@ -22,15 +22,16 @@
*/
/*
- Target for asynchronous calls.
+ Target for asynchronous procedue calls (APCs).
*/
class Apc_target
{
+ mysql_mutex_t *LOCK_thd_data_ptr;
public:
Apc_target() : enabled(0), apc_calls(NULL) /*, call_queue_size(0)*/ {}
~Apc_target() { DBUG_ASSERT(!enabled && !apc_calls);}
- void init();
+ void init(mysql_mutex_t *target_mutex);
void destroy();
void enable();
void disable();
@@ -68,14 +69,31 @@ private:
/*
Circular, double-linked list of all enqueued call requests.
We use this structure, because we
- - process requests sequentially
+ - process requests sequentially (i.e. they are removed from the front)
- a thread that has posted a request may time out (or be KILLed) and
- cancel the request, which means we'll need to remove its request at
- arbitrary point in time.
+ cancel the request, which means we need a fast request-removal
+ operation.
*/
Call_request *apc_calls;
+
- pthread_mutex_t LOCK_apc_queue;
+ /*
+ This mutex is used to
+ - make queue put/remove operations atomic (one must be in posession of the
+ mutex when putting/removing something from the queue)
+
+ - make sure that nobody enqueues a request onto an Apc_target which has
+ disabled==TRUE. The idea is:
+ = requestor must be in possession of the mutex and check that
+ disabled==FALSE when he is putting his request into the queue.
+ = When the owner (ie. service) thread changes the Apc_target from
+ enabled to disabled, it will acquire the mutex, disable the
+ Apc_target (preventing any new requests), and then serve all pending
+ requests.
+ That way, we will never have the situation where the Apc_target is
+ disabled, but there are some un-served requests.
+ */
+ //pthread_mutex_t LOCK_apc_queue;
class Call_request
{
@@ -84,13 +102,16 @@ private:
void *func_arg; /* Argument to pass it */
bool processed;
- pthread_mutex_t LOCK_request;
- pthread_cond_t COND_request;
+ //pthread_mutex_t LOCK_request;
+ //pthread_cond_t COND_request;
+
+ /* Condition that will be signalled when the request has been served */
+ mysql_cond_t COND_request;
Call_request *next;
Call_request *prev;
- const char *what; /* State of the request */
+ const char *what; /* (debug) state of the request */
};
void enqueue_request(Call_request *qe);
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index 2ad4e77405d..f477c0fe0ec 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -1196,7 +1196,7 @@ void THD::init(void)
/* Initialize the Debug Sync Facility. See debug_sync.cc. */
debug_sync_init_thread(this);
#endif /* defined(ENABLED_DEBUG_SYNC) */
- apc_target.init();
+ apc_target.init(&LOCK_thd_data);
}
@@ -1372,6 +1372,8 @@ THD::~THD()
{
THD_CHECK_SENTRY(this);
DBUG_ENTER("~THD()");
+ //psergey-todo: assert that the queue is disabled and empty.
+
/* Ensure that no one is using THD */
mysql_mutex_lock(&LOCK_thd_data);
mysys_var=0; // Safety (shouldn't be needed)
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index 87f46abb23c..5079e82fa40 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -2063,7 +2063,8 @@ void mysqld_show_explain(THD *thd, ulong thread_id)
explain_req.target_thd= tmp;
explain_req.request_thd= thd;
explain_req.failed_to_produce= FALSE;
-
+
+ /* Ok, we have a lock on target->LOCK_thd_data, can call: */
bres= tmp->apc_target.make_apc_call(Show_explain_request::get_explain_data,
(void*)&explain_req,
timeout_sec, &timed_out);
@@ -2090,7 +2091,7 @@ void mysqld_show_explain(THD *thd, ulong thread_id)
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
ER_YES, explain_req.query_str.c_ptr_safe());
}
- mysql_mutex_unlock(&tmp->LOCK_thd_data);
+ //mysql_mutex_unlock(&tmp->LOCK_thd_data);
if (!bres)
{
explain_buf->flush_data();