Commit 0d5adca0 authored by Sergei Golubchik's avatar Sergei Golubchik

debug_sync is now a service, available to dynamically loaded plugins.

new make target - abi_update

libservices/HOWTO:
  remove references to Makefile.am
  small tweaks
parent 20e70668
......@@ -55,6 +55,17 @@ IF(CMAKE_COMPILER_IS_GNUCC AND RUN_ABI_CHECK)
VERBATIM
)
ADD_CUSTOM_TARGET(abi_update
COMMAND ${CMAKE_COMMAND}
-DCOMPILER=${COMPILER}
-DABI_UPDATE=1
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
-DBINARY_DIR=${CMAKE_BINARY_DIR}
"-DABI_HEADERS=${API_PREPROCESSOR_HEADER}"
-P ${CMAKE_SOURCE_DIR}/cmake/do_abi_check.cmake
VERBATIM
)
ADD_CUSTOM_TARGET(abi_check_all
COMMAND ${CMAKE_COMMAND}
-DCOMPILER=${COMPILER}
......
......@@ -75,8 +75,12 @@ FOREACH(file ${ABI_HEADERS})
EXECUTE_PROCESS(
COMMAND diff -w ${file}.pp ${abi_check_out} RESULT_VARIABLE result)
IF(NOT ${result} EQUAL 0)
MESSAGE(FATAL_ERROR
"ABI check found difference between ${file}.pp and ${abi_check_out}")
IF(ABI_UPDATE)
EXECUTE_PROCESS(COMMAND mv -v ${abi_check_out} ${file}.pp)
ELSE(ABI_UPDATE)
MESSAGE(FATAL_ERROR
"ABI check found difference between ${file}.pp and ${abi_check_out}")
ENDIF(ABI_UPDATE)
ENDIF()
FILE(REMOVE ${abi_check_out})
ENDFOREACH()
......
......@@ -157,16 +157,6 @@ extern char *my_strndup(const char *from, size_t length, myf MyFlags);
extern int sf_leaking_memory; /* set to 1 to disable memleak detection */
#if defined(ENABLED_DEBUG_SYNC)
extern void (*debug_sync_C_callback_ptr)(const char *, size_t);
#define DEBUG_SYNC_C(_sync_point_name_) do { \
if (debug_sync_C_callback_ptr != NULL) \
(*debug_sync_C_callback_ptr)(STRING_WITH_LEN(_sync_point_name_)); } \
while(0)
#else
#define DEBUG_SYNC_C(_sync_point_name_)
#endif /* defined(ENABLED_DEBUG_SYNC) */
#ifdef HAVE_LARGE_PAGES
extern uint my_get_large_page_size(void);
extern uchar * my_large_malloc(size_t size, myf my_flags);
......
......@@ -72,7 +72,7 @@ typedef struct st_mysql_xid MYSQL_XID;
#define MYSQL_PLUGIN_INTERFACE_VERSION 0x0103
/* MariaDB plugin interface version */
#define MARIA_PLUGIN_INTERFACE_VERSION 0x0102
#define MARIA_PLUGIN_INTERFACE_VERSION 0x0103
/*
The allowable types of plugins
......
......@@ -80,6 +80,8 @@ void thd_progress_next_stage(void* thd);
void thd_progress_end(void* thd);
const char *set_thd_proc_info(void*, const char * info, const char *func,
const char *file, unsigned int line);
#include <mysql/service_debug_sync.h>
extern void (*debug_sync_C_callback_ptr)(void*, const char *, size_t);
struct st_mysql_xid {
long formatID;
long gtrid_length;
......
......@@ -80,6 +80,8 @@ void thd_progress_next_stage(void* thd);
void thd_progress_end(void* thd);
const char *set_thd_proc_info(void*, const char * info, const char *func,
const char *file, unsigned int line);
#include <mysql/service_debug_sync.h>
extern void (*debug_sync_C_callback_ptr)(void*, const char *, size_t);
struct st_mysql_xid {
long formatID;
long gtrid_length;
......
......@@ -80,6 +80,8 @@ void thd_progress_next_stage(void* thd);
void thd_progress_end(void* thd);
const char *set_thd_proc_info(void*, const char * info, const char *func,
const char *file, unsigned int line);
#include <mysql/service_debug_sync.h>
extern void (*debug_sync_C_callback_ptr)(void*, const char *, size_t);
struct st_mysql_xid {
long formatID;
long gtrid_length;
......
#ifndef MYSQL_SERVICE_DEBUG_SYNC_INCLUDED
/* Copyright (c) 2009, 2010, Oracle and/or its affiliates.
Copyright (c) 2012, Monty Program Ab
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
/**
@file
== Debug Sync Facility ==
The Debug Sync Facility allows placement of synchronization points in
the server code by using the DEBUG_SYNC macro:
open_tables(...)
DEBUG_SYNC(thd, "after_open_tables");
lock_tables(...)
When activated, a sync point can
- Emit a signal and/or
- Wait for a signal
Nomenclature:
- signal: A value of a global variable that persists
until overwritten by a new signal. The global
variable can also be seen as a "signal post"
or "flag mast". Then the signal is what is
attached to the "signal post" or "flag mast".
- emit a signal: Assign the value (the signal) to the global
variable ("set a flag") and broadcast a
global condition to wake those waiting for
a signal.
- wait for a signal: Loop over waiting for the global condition until
the global value matches the wait-for signal.
By default, all sync points are inactive. They do nothing (except to
burn a couple of CPU cycles for checking if they are active).
A sync point becomes active when an action is requested for it.
To do so, put a line like this in the test case file:
SET DEBUG_SYNC= 'after_open_tables SIGNAL opened WAIT_FOR flushed';
This activates the sync point 'after_open_tables'. It requests it to
emit the signal 'opened' and wait for another thread to emit the signal
'flushed' when the thread's execution runs through the sync point.
For every sync point there can be one action per thread only. Every
thread can request multiple actions, but only one per sync point. In
other words, a thread can activate multiple sync points.
Here is an example how to activate and use the sync points:
--connection conn1
SET DEBUG_SYNC= 'after_open_tables SIGNAL opened WAIT_FOR flushed';
send INSERT INTO t1 VALUES(1);
--connection conn2
SET DEBUG_SYNC= 'now WAIT_FOR opened';
SET DEBUG_SYNC= 'after_abort_locks SIGNAL flushed';
FLUSH TABLE t1;
When conn1 runs through the INSERT statement, it hits the sync point
'after_open_tables'. It notices that it is active and executes its
action. It emits the signal 'opened' and waits for another thread to
emit the signal 'flushed'.
conn2 waits immediately at the special sync point 'now' for another
thread to emit the 'opened' signal.
A signal remains in effect until it is overwritten. If conn1 signals
'opened' before conn2 reaches 'now', conn2 will still find the 'opened'
signal. It does not wait in this case.
When conn2 reaches 'after_abort_locks', it signals 'flushed', which lets
conn1 awake.
Normally the activation of a sync point is cleared when it has been
executed. Sometimes it is necessary to keep the sync point active for
another execution. You can add an execute count to the action:
SET DEBUG_SYNC= 'name SIGNAL sig EXECUTE 3';
This sets the signal point's activation counter to 3. Each execution
decrements the counter. After the third execution the sync point
becomes inactive.
One of the primary goals of this facility is to eliminate sleeps from
the test suite. In most cases it should be possible to rewrite test
cases so that they do not need to sleep. (But this facility cannot
synchronize multiple processes.) However, to support test development,
and as a last resort, sync point waiting times out. There is a default
timeout, but it can be overridden:
SET DEBUG_SYNC= 'name WAIT_FOR sig TIMEOUT 10 EXECUTE 2';
TIMEOUT 0 is special: If the signal is not present, the wait times out
immediately.
When a wait timed out (even on TIMEOUT 0), a warning is generated so
that it shows up in the test result.
You can throw an error message and kill the query when a synchronization
point is hit a certain number of times:
SET DEBUG_SYNC= 'name HIT_LIMIT 3';
Or combine it with signal and/or wait:
SET DEBUG_SYNC= 'name SIGNAL sig EXECUTE 2 HIT_LIMIT 3';
Here the first two hits emit the signal, the third hit returns the error
message and kills the query.
For cases where you are not sure that an action is taken and thus
cleared in any case, you can force to clear (deactivate) a sync point:
SET DEBUG_SYNC= 'name CLEAR';
If you want to clear all actions and clear the global signal, use:
SET DEBUG_SYNC= 'RESET';
This is the only way to reset the global signal to an empty string.
For testing of the facility itself you can execute a sync point just
as if it had been hit:
SET DEBUG_SYNC= 'name TEST';
=== Formal Syntax ===
The string to "assign" to the DEBUG_SYNC variable can contain:
{RESET |
<sync point name> TEST |
<sync point name> CLEAR |
<sync point name> {{SIGNAL <signal name> |
WAIT_FOR <signal name> [TIMEOUT <seconds>]}
[EXECUTE <count>] &| HIT_LIMIT <count>}
Here '&|' means 'and/or'. This means that one of the sections
separated by '&|' must be present or both of them.
=== Activation/Deactivation ===
The facility is an optional part of the MySQL server.
It is enabled in a debug server by default.
./configure --enable-debug-sync
The Debug Sync Facility, when compiled in, is disabled by default. It
can be enabled by a mysqld command line option:
--debug-sync-timeout[=default_wait_timeout_value_in_seconds]
'default_wait_timeout_value_in_seconds' is the default timeout for the
WAIT_FOR action. If set to zero, the facility stays disabled.
The facility is enabled by default in the test suite, but can be
disabled with:
mysql-test-run.pl ... --debug-sync-timeout=0 ...
Likewise the default wait timeout can be set:
mysql-test-run.pl ... --debug-sync-timeout=10 ...
The command line option influences the readable value of the system
variable 'debug_sync'.
* If the facility is not compiled in, the system variable does not exist.
* If --debug-sync-timeout=0 the value of the variable reads as "OFF".
* Otherwise the value reads as "ON - current signal: " followed by the
current signal string, which can be empty.
The readable variable value is the same, regardless if read as global
or session value.
Setting the 'debug-sync' system variable requires 'SUPER' privilege.
You can never read back the string that you assigned to the variable,
unless you assign the value that the variable does already have. But
that would give a parse error. A syntactically correct string is
parsed into a debug sync action and stored apart from the variable value.
=== Implementation ===
Pseudo code for a sync point:
#define DEBUG_SYNC(thd, sync_point_name)
if (unlikely(opt_debug_sync_timeout))
debug_sync(thd, STRING_WITH_LEN(sync_point_name))
The sync point performs a binary search in a sorted array of actions
for this thread.
The SET DEBUG_SYNC statement adds a requested action to the array or
overwrites an existing action for the same sync point. When it adds a
new action, the array is sorted again.
=== A typical synchronization pattern ===
There are quite a few places in MySQL, where we use a synchronization
pattern like this:
mysql_mutex_lock(&mutex);
thd->enter_cond(&condition_variable, &mutex, new_message);
#if defined(ENABLE_DEBUG_SYNC)
if (!thd->killed && !end_of_wait_condition)
DEBUG_SYNC(thd, "sync_point_name");
#endif
while (!thd->killed && !end_of_wait_condition)
mysql_cond_wait(&condition_variable, &mutex);
thd->exit_cond(old_message);
Here some explanations:
thd->enter_cond() is used to register the condition variable and the
mutex in thd->mysys_var. This is done to allow the thread to be
interrupted (killed) from its sleep. Another thread can find the
condition variable to signal and mutex to use for synchronization in
this thread's THD::mysys_var.
thd->enter_cond() requires the mutex to be acquired in advance.
thd->exit_cond() unregisters the condition variable and mutex and
releases the mutex.
If you want to have a Debug Sync point with the wait, please place it
behind enter_cond(). Only then you can safely decide, if the wait will
be taken. Also you will have THD::proc_info correct when the sync
point emits a signal. DEBUG_SYNC sets its own proc_info, but restores
the previous one before releasing its internal mutex. As soon as
another thread sees the signal, it does also see the proc_info from
before entering the sync point. In this case it will be "new_message",
which is associated with the wait that is to be synchronized.
In the example above, the wait condition is repeated before the sync
point. This is done to skip the sync point, if no wait takes place.
The sync point is before the loop (not inside the loop) to have it hit
once only. It is possible that the condition variable is signaled
multiple times without the wait condition to be true.
A bit off-topic: At some places, the loop is taken around the whole
synchronization pattern:
while (!thd->killed && !end_of_wait_condition)
{
mysql_mutex_lock(&mutex);
thd->enter_cond(&condition_variable, &mutex, new_message);
if (!thd->killed [&& !end_of_wait_condition])
{
[DEBUG_SYNC(thd, "sync_point_name");]
mysql_cond_wait(&condition_variable, &mutex);
}
thd->exit_cond(old_message);
}
Note that it is important to repeat the test for thd->killed after
enter_cond(). Otherwise the killing thread may kill this thread after
it tested thd->killed in the loop condition and before it registered
the condition variable and mutex in enter_cond(). In this case, the
killing thread does not know that this thread is going to wait on a
condition variable. It would just set THD::killed. But if we would not
test it again, we would go asleep though we are killed. If the killing
thread would kill us when we are after the second test, but still
before sleeping, we hold the mutex, which is registered in mysys_var.
The killing thread would try to acquire the mutex before signaling
the condition variable. Since the mutex is only released implicitly in
mysql_cond_wait(), the signaling happens at the right place. We
have a safe synchronization.
=== Co-work with the DBUG facility ===
When running the MySQL test suite with the --debug-dbug command line
option, the Debug Sync Facility writes trace messages to the DBUG
trace. The following shell commands proved very useful in extracting
relevant information:
egrep 'query:|debug_sync_exec:' mysql-test/var/log/mysqld.1.trace
It shows all executed SQL statements and all actions executed by
synchronization points.
Sometimes it is also useful to see, which synchronization points have
been run through (hit) with or without executing actions. Then add
"|debug_sync_point:" to the egrep pattern.
=== Further reading ===
For a discussion of other methods to synchronize threads see
http://forge.mysql.com/wiki/MySQL_Internals_Test_Synchronization
For complete syntax tests, functional tests, and examples see the test
case debug_sync.test.
See also http://forge.mysql.com/worklog/task.php?id=4259
*/
#ifndef MYSQL_ABI_CHECK
#include <stdlib.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifdef MYSQL_DYNAMIC_PLUGIN
extern void (*debug_sync_service)(MYSQL_THD, const char *, size_t);
#else
#define debug_sync_service debug_sync_C_callback_ptr
extern void (*debug_sync_C_callback_ptr)(MYSQL_THD, const char *, size_t);
#endif
#ifdef ENABLED_DEBUG_SYNC
#define DEBUG_SYNC(thd, name) \
do { \
if (debug_sync_service) \
debug_sync_service(thd, name, sizeof(name)-1); \
} while(0)
#else
#define DEBUG_SYNC(thd,name) do { } while(0)
#endif
/* compatibility macro */
#define DEBUG_SYNC_C(name) DEBUG_SYNC(NULL, name)
#ifdef __cplusplus
}
#endif
#define MYSQL_SERVICE_DEBUG_SYNC_INCLUDED
#endif
......@@ -23,7 +23,7 @@ extern "C" {
#include <mysql/service_thd_wait.h>
#include <mysql/service_thread_scheduler.h>
#include <mysql/service_progress_report.h>
#include <mysql/service_debug_sync.h>
#ifdef __cplusplus
}
......
......@@ -24,3 +24,4 @@
#define VERSION_thd_wait 0x0100
#define VERSION_my_thread_scheduler 0x0100
#define VERSION_progress_report 0x0100
#define VERSION_debug_sync 0x1000
......@@ -20,7 +20,8 @@ SET(MYSQLSERVICES_SOURCES
thd_alloc_service.c
thd_wait_service.c
my_thread_scheduler_service.c
progress_report_service.c)
progress_report_service.c
debug_sync_service.c)
ADD_CONVENIENCE_LIBRARY(mysqlservices ${MYSQLSERVICES_SOURCES})
INSTALL(TARGETS mysqlservices DESTINATION ${INSTALL_LIBDIR} COMPONENT Development)
......@@ -34,19 +34,19 @@ into a service "foo" you need to
#endif
extern struct foo_service_st {
int (*foo_func1_type)(...); /* fix the prototype as appropriate */
void (*foo_func2_type)(...); /* fix the prototype as appropriate */
int (*foo_func1_ptr)(...); /* fix the prototype as appropriate */
void (*foo_func2_ptr)(...); /* fix the prototype as appropriate */
} *foo_service;
#ifdef MYSQL_DYNAMIC_PLUGIN
#define foo_func1(...) foo_service->foo_func1_type(...)
#define foo_func2(...) foo_service->foo_func2_type(...)
#define foo_func1(...) foo_service->foo_func1_ptr(...)
#define foo_func2(...) foo_service->foo_func2_ptr(...)
#else
int foo_func1_type(...); /* fix the prototype as appropriate */
void foo_func2_type(...); /* fix the prototype as appropriate */
int foo_func1(...); /* fix the prototype as appropriate */
void foo_func2(...); /* fix the prototype as appropriate */
#endif
......@@ -64,27 +64,26 @@ include them in it, e.g. if you use size_t - #include <stdlib.h>
it should also declare all the accompanying data structures, as necessary
(e.g. thd_alloc_service declares MYSQL_LEX_STRING).
3. add the new file to include/Makefile.am (pkginclude_HEADERS)
4. add the new file to include/mysql/services.h
5. increase the minor plugin ABI version in include/mysql/plugin.h
(MYSQL_PLUGIN_INTERFACE_VERSION = MYSQL_PLUGIN_INTERFACE_VERSION+1)
6. add the version of your service to include/service_versions.h:
3. add the new file to include/mysql/services.h
4. increase the minor plugin ABI version in include/mysql/plugin.h
(MARIA_PLUGIN_INTERFACE_VERSION = MARIA_PLUGIN_INTERFACE_VERSION+1)
5. add the version of your service to include/service_versions.h:
==================================================================
#define VERSION_foo 0x0100
==================================================================
7. create a new file libservices/foo_service.h using the following template:
6. create a new file libservices/foo_service.h using the following template:
==================================================================
/* GPL header */
#include <service_versions.h>
SERVICE_VERSION *foo_service= (void*)VERSION_foo;
==================================================================
8. add the new file to libservices/CMakeLists.txt (MYSQLSERVICES_SOURCES)
9. add the new file to libservices/Makefile.am (libmysqlservices_a_SOURCES)
10. and finally, register your service for dynamic linking in
sql/sql_plugin_services.h
10.1 fill in the service structure:
7. add the new file to libservices/CMakeLists.txt (MYSQLSERVICES_SOURCES)
8. Add all new files to repository (bzr add)
9. and finally, register your service for dynamic linking in
sql/sql_plugin_services.h as follows:
9.1 fill in the service structure:
==================================================================
static struct foo_service_st foo_handler = {
foo_func1,
......@@ -92,7 +91,7 @@ it should also declare all the accompanying data structures, as necessary
}
==================================================================
10.2 and add it to the list of services
9.2 and add it to the list of services
==================================================================
{ "foo_service", VERSION_foo, &foo_handler }
......
/* Copyright (c) 2012, Monty Program Ab
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <service_versions.h>
SERVICE_VERSION *debug_sync_service= (void*)VERSION_debug_sync;
......@@ -15,7 +15,7 @@ PLUGIN_STATUS ACTIVE
PLUGIN_TYPE STORAGE ENGINE
PLUGIN_TYPE_VERSION #
PLUGIN_LIBRARY ha_example.so
PLUGIN_LIBRARY_VERSION 1.2
PLUGIN_LIBRARY_VERSION 1.3
PLUGIN_AUTHOR Brian Aker, MySQL AB
PLUGIN_DESCRIPTION Example storage engine
PLUGIN_LICENSE GPL
......@@ -28,7 +28,7 @@ PLUGIN_STATUS ACTIVE
PLUGIN_TYPE DAEMON
PLUGIN_TYPE_VERSION #
PLUGIN_LIBRARY ha_example.so
PLUGIN_LIBRARY_VERSION 1.2
PLUGIN_LIBRARY_VERSION 1.3
PLUGIN_AUTHOR Sergei Golubchik
PLUGIN_DESCRIPTION Unusable Daemon
PLUGIN_LICENSE GPL
......@@ -57,7 +57,7 @@ PLUGIN_STATUS DELETED
PLUGIN_TYPE STORAGE ENGINE
PLUGIN_TYPE_VERSION #
PLUGIN_LIBRARY ha_example.so
PLUGIN_LIBRARY_VERSION 1.2
PLUGIN_LIBRARY_VERSION 1.3
PLUGIN_AUTHOR Brian Aker, MySQL AB
PLUGIN_DESCRIPTION Example storage engine
PLUGIN_LICENSE GPL
......
......@@ -90,13 +90,7 @@ static const char *proc_info_dummy(void *a __attribute__((unused)),
/* this is to be able to call set_thd_proc_info from the C code */
const char *(*proc_info_hook)(void *, const char *, const char *, const char *,
const unsigned int)= proc_info_dummy;
#if defined(ENABLED_DEBUG_SYNC)
/**
Global pointer to be set if callback function is defined
(e.g. in mysqld). See sql/debug_sync.cc.
*/
void (*debug_sync_C_callback_ptr)(const char *, size_t);
#endif /* defined(ENABLED_DEBUG_SYNC) */
void (*debug_sync_C_callback_ptr)(MYSQL_THD, const char *, size_t)= 0;
/* How to disable options */
my_bool my_disable_locking=0;
......
......@@ -13,307 +13,7 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
/**
== Debug Sync Facility ==
The Debug Sync Facility allows placement of synchronization points in
the server code by using the DEBUG_SYNC macro:
open_tables(...)
DEBUG_SYNC(thd, "after_open_tables");
lock_tables(...)
When activated, a sync point can
- Emit a signal and/or
- Wait for a signal
Nomenclature:
- signal: A value of a global variable that persists
until overwritten by a new signal. The global
variable can also be seen as a "signal post"
or "flag mast". Then the signal is what is
attached to the "signal post" or "flag mast".
- emit a signal: Assign the value (the signal) to the global
variable ("set a flag") and broadcast a
global condition to wake those waiting for
a signal.
- wait for a signal: Loop over waiting for the global condition until
the global value matches the wait-for signal.
By default, all sync points are inactive. They do nothing (except to
burn a couple of CPU cycles for checking if they are active).
A sync point becomes active when an action is requested for it.
To do so, put a line like this in the test case file:
SET DEBUG_SYNC= 'after_open_tables SIGNAL opened WAIT_FOR flushed';
This activates the sync point 'after_open_tables'. It requests it to
emit the signal 'opened' and wait for another thread to emit the signal
'flushed' when the thread's execution runs through the sync point.
For every sync point there can be one action per thread only. Every
thread can request multiple actions, but only one per sync point. In
other words, a thread can activate multiple sync points.
Here is an example how to activate and use the sync points:
--connection conn1
SET DEBUG_SYNC= 'after_open_tables SIGNAL opened WAIT_FOR flushed';
send INSERT INTO t1 VALUES(1);
--connection conn2
SET DEBUG_SYNC= 'now WAIT_FOR opened';
SET DEBUG_SYNC= 'after_abort_locks SIGNAL flushed';
FLUSH TABLE t1;
When conn1 runs through the INSERT statement, it hits the sync point
'after_open_tables'. It notices that it is active and executes its
action. It emits the signal 'opened' and waits for another thread to
emit the signal 'flushed'.
conn2 waits immediately at the special sync point 'now' for another
thread to emit the 'opened' signal.
A signal remains in effect until it is overwritten. If conn1 signals
'opened' before conn2 reaches 'now', conn2 will still find the 'opened'
signal. It does not wait in this case.
When conn2 reaches 'after_abort_locks', it signals 'flushed', which lets
conn1 awake.
Normally the activation of a sync point is cleared when it has been
executed. Sometimes it is necessary to keep the sync point active for
another execution. You can add an execute count to the action:
SET DEBUG_SYNC= 'name SIGNAL sig EXECUTE 3';
This sets the signal point's activation counter to 3. Each execution
decrements the counter. After the third execution the sync point
becomes inactive.
One of the primary goals of this facility is to eliminate sleeps from
the test suite. In most cases it should be possible to rewrite test
cases so that they do not need to sleep. (But this facility cannot
synchronize multiple processes.) However, to support test development,
and as a last resort, sync point waiting times out. There is a default
timeout, but it can be overridden:
SET DEBUG_SYNC= 'name WAIT_FOR sig TIMEOUT 10 EXECUTE 2';
TIMEOUT 0 is special: If the signal is not present, the wait times out
immediately.
When a wait timed out (even on TIMEOUT 0), a warning is generated so
that it shows up in the test result.
You can throw an error message and kill the query when a synchronization
point is hit a certain number of times:
SET DEBUG_SYNC= 'name HIT_LIMIT 3';
Or combine it with signal and/or wait:
SET DEBUG_SYNC= 'name SIGNAL sig EXECUTE 2 HIT_LIMIT 3';
Here the first two hits emit the signal, the third hit returns the error
message and kills the query.
For cases where you are not sure that an action is taken and thus
cleared in any case, you can force to clear (deactivate) a sync point:
SET DEBUG_SYNC= 'name CLEAR';
If you want to clear all actions and clear the global signal, use:
SET DEBUG_SYNC= 'RESET';
This is the only way to reset the global signal to an empty string.
For testing of the facility itself you can execute a sync point just
as if it had been hit:
SET DEBUG_SYNC= 'name TEST';
=== Formal Syntax ===
The string to "assign" to the DEBUG_SYNC variable can contain:
{RESET |
<sync point name> TEST |
<sync point name> CLEAR |
<sync point name> {{SIGNAL <signal name> |
WAIT_FOR <signal name> [TIMEOUT <seconds>]}
[EXECUTE <count>] &| HIT_LIMIT <count>}
Here '&|' means 'and/or'. This means that one of the sections
separated by '&|' must be present or both of them.
=== Activation/Deactivation ===
The facility is an optional part of the MySQL server.
It is enabled in a debug server by default.
./configure --enable-debug-sync
The Debug Sync Facility, when compiled in, is disabled by default. It
can be enabled by a mysqld command line option:
--debug-sync-timeout[=default_wait_timeout_value_in_seconds]
'default_wait_timeout_value_in_seconds' is the default timeout for the
WAIT_FOR action. If set to zero, the facility stays disabled.
The facility is enabled by default in the test suite, but can be
disabled with:
mysql-test-run.pl ... --debug-sync-timeout=0 ...
Likewise the default wait timeout can be set:
mysql-test-run.pl ... --debug-sync-timeout=10 ...
The command line option influences the readable value of the system
variable 'debug_sync'.
* If the facility is not compiled in, the system variable does not exist.
* If --debug-sync-timeout=0 the value of the variable reads as "OFF".
* Otherwise the value reads as "ON - current signal: " followed by the
current signal string, which can be empty.
The readable variable value is the same, regardless if read as global
or session value.
Setting the 'debug-sync' system variable requires 'SUPER' privilege.
You can never read back the string that you assigned to the variable,
unless you assign the value that the variable does already have. But
that would give a parse error. A syntactically correct string is
parsed into a debug sync action and stored apart from the variable value.
=== Implementation ===
Pseudo code for a sync point:
#define DEBUG_SYNC(thd, sync_point_name)
if (unlikely(opt_debug_sync_timeout))
debug_sync(thd, STRING_WITH_LEN(sync_point_name))
The sync point performs a binary search in a sorted array of actions
for this thread.
The SET DEBUG_SYNC statement adds a requested action to the array or
overwrites an existing action for the same sync point. When it adds a
new action, the array is sorted again.
=== A typical synchronization pattern ===
There are quite a few places in MySQL, where we use a synchronization
pattern like this:
mysql_mutex_lock(&mutex);
thd->enter_cond(&condition_variable, &mutex, new_message);
#if defined(ENABLE_DEBUG_SYNC)
if (!thd->killed && !end_of_wait_condition)
DEBUG_SYNC(thd, "sync_point_name");
#endif
while (!thd->killed && !end_of_wait_condition)
mysql_cond_wait(&condition_variable, &mutex);
thd->exit_cond(old_message);
Here some explanations:
thd->enter_cond() is used to register the condition variable and the
mutex in thd->mysys_var. This is done to allow the thread to be
interrupted (killed) from its sleep. Another thread can find the
condition variable to signal and mutex to use for synchronization in
this thread's THD::mysys_var.
thd->enter_cond() requires the mutex to be acquired in advance.
thd->exit_cond() unregisters the condition variable and mutex and
releases the mutex.
If you want to have a Debug Sync point with the wait, please place it
behind enter_cond(). Only then you can safely decide, if the wait will
be taken. Also you will have THD::proc_info correct when the sync
point emits a signal. DEBUG_SYNC sets its own proc_info, but restores
the previous one before releasing its internal mutex. As soon as
another thread sees the signal, it does also see the proc_info from
before entering the sync point. In this case it will be "new_message",
which is associated with the wait that is to be synchronized.
In the example above, the wait condition is repeated before the sync
point. This is done to skip the sync point, if no wait takes place.
The sync point is before the loop (not inside the loop) to have it hit
once only. It is possible that the condition variable is signaled
multiple times without the wait condition to be true.
A bit off-topic: At some places, the loop is taken around the whole
synchronization pattern:
while (!thd->killed && !end_of_wait_condition)
{
mysql_mutex_lock(&mutex);
thd->enter_cond(&condition_variable, &mutex, new_message);
if (!thd->killed [&& !end_of_wait_condition])
{
[DEBUG_SYNC(thd, "sync_point_name");]
mysql_cond_wait(&condition_variable, &mutex);
}
thd->exit_cond(old_message);
}
Note that it is important to repeat the test for thd->killed after
enter_cond(). Otherwise the killing thread may kill this thread after
it tested thd->killed in the loop condition and before it registered
the condition variable and mutex in enter_cond(). In this case, the
killing thread does not know that this thread is going to wait on a
condition variable. It would just set THD::killed. But if we would not
test it again, we would go asleep though we are killed. If the killing
thread would kill us when we are after the second test, but still
before sleeping, we hold the mutex, which is registered in mysys_var.
The killing thread would try to acquire the mutex before signaling
the condition variable. Since the mutex is only released implicitly in
mysql_cond_wait(), the signaling happens at the right place. We
have a safe synchronization.
=== Co-work with the DBUG facility ===
When running the MySQL test suite with the --debug-dbug command line
option, the Debug Sync Facility writes trace messages to the DBUG
trace. The following shell commands proved very useful in extracting
relevant information:
egrep 'query:|debug_sync_exec:' mysql-test/var/log/mysqld.1.trace
It shows all executed SQL statements and all actions executed by
synchronization points.
Sometimes it is also useful to see, which synchronization points have
been run through (hit) with or without executing actions. Then add
"|debug_sync_point:" to the egrep pattern.
=== Further reading ===
For a discussion of other methods to synchronize threads see
http://forge.mysql.com/wiki/MySQL_Internals_Test_Synchronization
For complete syntax tests, functional tests, and examples see the test
case debug_sync.test.
See also worklog entry WL#4259 - Test Synchronization Facility
*/
/* see include/mysql/service_debug_sync.h for debug sync documentation */
#include "debug_sync.h"
......@@ -382,57 +82,16 @@ struct st_debug_sync_globals
};
static st_debug_sync_globals debug_sync_global; /* All globals in one object */
/**
Callback pointer for C files.
*/
extern "C" void (*debug_sync_C_callback_ptr)(const char *, size_t);
extern uint opt_debug_sync_timeout;
/**
Callbacks from C files.
*/
C_MODE_START
static void debug_sync_C_callback(const char *, size_t);
static void debug_sync(THD *thd, const char *sync_point_name, size_t name_len);
static int debug_sync_qsort_cmp(const void *, const void *);
C_MODE_END
/**
Callback for debug sync, to be used by C files. See thr_lock.c for example.
@description
We cannot place a sync point directly in C files (like those in mysys or
certain storage engines written mostly in C like MyISAM or Maria). Because
they are C code and do not include sql_priv.h. So they do not know the
macro DEBUG_SYNC(thd, sync_point_name). The macro needs a 'thd' argument.
Hence it cannot be used in files outside of the sql/ directory.
The workaround is to call back simple functions like this one from
non-sql/ files.
We want to allow modules like thr_lock to be used without sql/ and
especially without Debug Sync. So we cannot just do a simple call
of the callback function. Instead we provide a global pointer in
the other file, which is to be set to the callback by Debug Sync.
If the pointer is not set, no call back will be done. If Debug
Sync sets the pointer to a callback function like this one, it will
be called. That way thr_lock.c does not have an undefined reference
to Debug Sync and can be used without it. Debug Sync, in contrast,
has an undefined reference to that pointer and thus requires
thr_lock to be linked too. But this is not a problem as it is part
of the MySQL server anyway.
@note
The callback pointer in C files is set only if debug sync is
initialized. And this is done only if opt_debug_sync_timeout is set.
*/
static void debug_sync_C_callback(const char *sync_point_name,
size_t name_len)
{
if (unlikely(opt_debug_sync_timeout))
debug_sync(current_thd, sync_point_name, name_len);
}
#ifdef HAVE_PSI_INTERFACE
static PSI_mutex_key key_debug_sync_globals_ds_mutex;
......@@ -495,7 +154,7 @@ int debug_sync_init(void)
DBUG_RETURN(rc); /* purecov: inspected */
/* Set the call back pointer in C files. */
debug_sync_C_callback_ptr= debug_sync_C_callback;
debug_sync_C_callback_ptr= debug_sync;
}
DBUG_RETURN(0);
......@@ -1857,12 +1516,14 @@ static void debug_sync_execute(THD *thd, st_debug_sync_action *action)
@param[in] name_len length of sync point name
*/
void debug_sync(THD *thd, const char *sync_point_name, size_t name_len)
static void debug_sync(THD *thd, const char *sync_point_name, size_t name_len)
{
if (!thd)
thd= current_thd;
st_debug_sync_control *ds_control= thd->debug_sync_control;
st_debug_sync_action *action;
DBUG_ENTER("debug_sync");
DBUG_ASSERT(thd);
DBUG_ASSERT(sync_point_name);
DBUG_ASSERT(name_len);
DBUG_ASSERT(ds_control);
......
......@@ -32,15 +32,6 @@ class THD;
#if defined(ENABLED_DEBUG_SYNC)
/* Macro to be put in the code at synchronization points. */
#define DEBUG_SYNC(_thd_, _sync_point_name_) \
do { if (unlikely(opt_debug_sync_timeout)) \
debug_sync(_thd_, STRING_WITH_LEN(_sync_point_name_)); \
} while (0)
/* Command line option --debug-sync-timeout. See mysqld.cc. */
extern MYSQL_PLUGIN_IMPORT uint opt_debug_sync_timeout;
/* Default WAIT_FOR timeout if command line option is given without argument. */
#define DEBUG_SYNC_DEFAULT_WAIT_TIMEOUT 300
......@@ -49,13 +40,8 @@ extern int debug_sync_init(void);
extern void debug_sync_end(void);
extern void debug_sync_init_thread(THD *thd);
extern void debug_sync_end_thread(THD *thd);
extern void debug_sync(THD *thd, const char *sync_point_name, size_t name_len);
extern bool debug_sync_set_action(THD *thd, const char *action_str, size_t len);
#else /* defined(ENABLED_DEBUG_SYNC) */
#define DEBUG_SYNC(_thd_, _sync_point_name_) /* disabled DEBUG_SYNC */
#endif /* defined(ENABLED_DEBUG_SYNC) */
#endif /* DEBUG_SYNC_INCLUDED */
......@@ -1339,7 +1339,7 @@ static int get_master_version_and_clock(MYSQL* mysql, Master_info* mi)
const char act[]=
"now "
"wait_for signal.get_unix_timestamp";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(debug_sync_service);
DBUG_ASSERT(!debug_sync_set_action(current_thd,
STRING_WITH_LEN(act)));
};);
......@@ -1389,7 +1389,7 @@ static int get_master_version_and_clock(MYSQL* mysql, Master_info* mi)
const char act[]=
"now "
"wait_for signal.get_server_id";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(debug_sync_service);
DBUG_ASSERT(!debug_sync_set_action(current_thd,
STRING_WITH_LEN(act)));
};);
......@@ -3034,7 +3034,7 @@ connected:
const char act[]=
"now "
"wait_for signal.io_thread_let_running";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(debug_sync_service);
DBUG_ASSERT(!debug_sync_set_action(thd,
STRING_WITH_LEN(act)));
};);
......
......@@ -2934,7 +2934,7 @@ end_with_restore_list:
const char act2[]=
"now "
"signal signal.continued";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(debug_sync_service);
DBUG_ASSERT(!debug_sync_set_action(thd,
STRING_WITH_LEN(act1)));
DBUG_ASSERT(!debug_sync_set_action(thd,
......
......@@ -1520,6 +1520,10 @@ int plugin_init(int *argc, char **argv, int flags)
goto err;
}
/* prepare debug_sync service */
DBUG_ASSERT(strcmp(list_of_services[5].name, "debug_sync_service") == 0);
list_of_services[5].service= *(void**)&debug_sync_C_callback_ptr;
mysql_mutex_lock(&LOCK_plugin);
initialized= 1;
......
......@@ -61,5 +61,6 @@ static struct st_service_ref list_of_services[]=
{ "thd_wait_service", VERSION_thd_wait, &thd_wait_handler },
{ "my_thread_scheduler_service", VERSION_my_thread_scheduler, &my_thread_scheduler_handler },
{ "progress_report_service", VERSION_progress_report, &progress_report_handler },
{ "debug_sync_service", VERSION_debug_sync, 0 } // updated in plugin_init()
};
......@@ -904,7 +904,7 @@ impossible position";
const char act[]=
"now "
"wait_for signal.continue";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(debug_sync_service);
DBUG_ASSERT(!debug_sync_set_action(thd,
STRING_WITH_LEN(act)));
const char act2[]=
......
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