summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIngo Struewing <ingo.struewing@sun.com>2009-09-29 17:38:40 +0200
committerIngo Struewing <ingo.struewing@sun.com>2009-09-29 17:38:40 +0200
commit4d57b851a0335ac098d46dad5b9f698ff2cd3e43 (patch)
tree685de4fd8757394edb5d353f0627eb2390ffc419
parent6a89842e3642729fbac20a92a1655452f4d45487 (diff)
downloadmariadb-git-4d57b851a0335ac098d46dad5b9f698ff2cd3e43.tar.gz
WL#4259 - Debug Sync Facility
Backport from 6.0 to 5.1. Only those sync points are included, which are used in debug_sync.test. The Debug Sync Facility allows to place synchronization points in the code: open_tables(...) DEBUG_SYNC(thd, "after_open_tables"); lock_tables(...) When activated, a sync point can - Send 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". - send 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. Please find more information in the top comment in debug_sync.cc or in the worklog entry. .bzrignore: WL#4259 - Debug Sync Facility Added the symbolic link libmysqld/debug_sync.cc. CMakeLists.txt: WL#4259 - Debug Sync Facility Added definition for ENABLED_DEBUG_SYNC. configure.in: WL#4259 - Debug Sync Facility Added definition for ENABLED_DEBUG_SYNC. include/my_sys.h: WL#4259 - Debug Sync Facility Added definition for the DEBUG_SYNC_C macro. libmysqld/CMakeLists.txt: WL#4259 - Debug Sync Facility Added sql/debug_sync.cc. libmysqld/Makefile.am: WL#4259 - Debug Sync Facility Added sql/debug_sync.cc. mysql-test/include/have_debug_sync.inc: WL#4259 - Debug Sync Facility New include file. mysql-test/mysql-test-run.pl: WL#4259 - Debug Sync Facility Added option --debug_sync_timeout. mysql-test/r/debug_sync.result: WL#4259 - Debug Sync Facility New test result. mysql-test/r/have_debug_sync.require: WL#4259 - Debug Sync Facility New require file. mysql-test/t/debug_sync.test: WL#4259 - Debug Sync Facility New test file. mysys/my_static.c: WL#4259 - Debug Sync Facility Added definition for debug_sync_C_callback_ptr. mysys/thr_lock.c: WL#4259 - Debug Sync Facility Added sync point "wait_for_lock". sql/CMakeLists.txt: WL#4259 - Debug Sync Facility Added debug_sync.cc and debug_sync.h. sql/Makefile.am: WL#4259 - Debug Sync Facility Added debug_sync.cc and debug_sync.h. sql/debug_sync.cc: WL#4259 - Debug Sync Facility New source file. sql/debug_sync.h: WL#4259 - Debug Sync Facility New header file. sql/mysqld.cc: WL#4259 - Debug Sync Facility Added opt_debug_sync_timeout. Added calls to debug_sync_init() and debug_sync_end(). Fixed a purecov comment (unrelated). sql/set_var.cc: WL#4259 - Debug Sync Facility Added server variable "debug_sync". sql/set_var.h: WL#4259 - Debug Sync Facility Added declaration for server variable "debug_sync". sql/share/errmsg.txt: WL#4259 - Debug Sync Facility Added error messages ER_DEBUG_SYNC_TIMEOUT and ER_DEBUG_SYNC_HIT_LIMIT. sql/sql_base.cc: WL#4259 - Debug Sync Facility Added sync points "after_flush_unlock" and "before_lock_tables_takes_lock". sql/sql_class.cc: WL#4259 - Debug Sync Facility Added initialization for debug_sync_control to THD::THD. Added calls to debug_sync_init_thread() and debug_sync_end_thread(). sql/sql_class.h: WL#4259 - Debug Sync Facility Added element debug_sync_control to THD. storage/myisam/myisamchk.c: Fixed a typo in an error message string (unrelated).
-rw-r--r--.bzrignore1
-rwxr-xr-xCMakeLists.txt6
-rw-r--r--configure.in17
-rw-r--r--include/my_sys.h10
-rw-r--r--libmysqld/CMakeLists.txt1
-rw-r--r--libmysqld/Makefile.am1
-rw-r--r--mysql-test/include/have_debug_sync.inc5
-rwxr-xr-xmysql-test/mysql-test-run.pl9
-rw-r--r--mysql-test/r/debug_sync.result277
-rw-r--r--mysql-test/r/have_debug_sync.require2
-rw-r--r--mysql-test/t/debug_sync.test420
-rw-r--r--mysys/my_static.c8
-rw-r--r--mysys/thr_lock.c22
-rwxr-xr-xsql/CMakeLists.txt1
-rw-r--r--sql/Makefile.am2
-rw-r--r--sql/debug_sync.cc1906
-rw-r--r--sql/debug_sync.h60
-rw-r--r--sql/mysqld.cc46
-rw-r--r--sql/set_var.cc6
-rw-r--r--sql/set_var.h15
-rw-r--r--sql/share/errmsg.txt7
-rw-r--r--sql/sql_base.cc4
-rw-r--r--sql/sql_class.cc15
-rw-r--r--sql/sql_class.h5
-rw-r--r--storage/myisam/myisamchk.c2
25 files changed, 2846 insertions, 2 deletions
diff --git a/.bzrignore b/.bzrignore
index 6c6a43dacab..e1e2083e2d2 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -3064,3 +3064,4 @@ sql/share/spanish
sql/share/swedish
sql/share/ukrainian
libmysqld/examples/mysqltest.cc
+libmysqld/debug_sync.cc
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7d91e39eb6d..8c40312e32b 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -66,6 +66,12 @@ IF(EXTRA_DEBUG)
ADD_DEFINITIONS(-D EXTRA_DEBUG)
ENDIF(EXTRA_DEBUG)
+IF(ENABLED_DEBUG_SYNC)
+ ADD_DEFINITIONS(-D ENABLED_DEBUG_SYNC)
+ENDIF(ENABLED_DEBUG_SYNC)
+SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DENABLED_DEBUG_SYNC")
+SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DENABLED_DEBUG_SYNC")
+
# in some places we use DBUG_OFF
SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DDBUG_OFF")
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DDBUG_OFF")
diff --git a/configure.in b/configure.in
index b974d7076d7..bec902f83c1 100644
--- a/configure.in
+++ b/configure.in
@@ -1708,6 +1708,23 @@ else
CXXFLAGS="$OPTIMIZE_CXXFLAGS $CXXFLAGS"
fi
+# Debug Sync Facility. NOTE: depends on 'with_debug'. Must be behind it.
+AC_MSG_CHECKING(if Debug Sync Facility should be enabled.)
+AC_ARG_ENABLE(debug_sync,
+ AS_HELP_STRING([--enable-debug-sync],
+ [Build a version with Debug Sync Facility]),
+ [ enable_debug_sync=$enableval ],
+ [ enable_debug_sync=$with_debug ])
+
+if test "$enable_debug_sync" != "no"
+then
+ AC_DEFINE([ENABLED_DEBUG_SYNC], [1],
+ [If Debug Sync Facility should be enabled])
+ AC_MSG_RESULT([yes])
+else
+ AC_MSG_RESULT([no])
+fi
+
# If we should allow error injection tests
AC_ARG_WITH(error-inject,
AC_HELP_STRING([--with-error-inject],[Enable error injection in MySQL Server]),
diff --git a/include/my_sys.h b/include/my_sys.h
index cb9a24bbd7f..166133251bc 100644
--- a/include/my_sys.h
+++ b/include/my_sys.h
@@ -172,6 +172,16 @@ extern char *my_strndup(const char *from, size_t length,
#define TRASH(A,B) /* nothing */
#endif
+#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);
diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt
index 8500d73863a..bea6f6c0e1f 100644
--- a/libmysqld/CMakeLists.txt
+++ b/libmysqld/CMakeLists.txt
@@ -129,6 +129,7 @@ SET(LIBMYSQLD_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
../sql/sql_list.cc ../sql/sql_load.cc ../sql/sql_locale.cc
../sql/sql_binlog.cc ../sql/sql_manager.cc ../sql/sql_map.cc
../sql/sql_parse.cc ../sql/sql_partition.cc ../sql/sql_plugin.cc
+ ../sql/debug_sync.cc
../sql/sql_prepare.cc ../sql/sql_rename.cc ../sql/sql_repl.cc
../sql/sql_select.cc ../sql/sql_servers.cc
../sql/sql_show.cc ../sql/sql_state.c ../sql/sql_string.cc
diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am
index a9bd8d9e17c..16c45816bbf 100644
--- a/libmysqld/Makefile.am
+++ b/libmysqld/Makefile.am
@@ -74,6 +74,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \
sp_head.cc sp_pcontext.cc sp.cc sp_cache.cc sp_rcontext.cc \
parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc \
rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \
+ debug_sync.cc \
sql_tablespace.cc \
rpl_injector.cc my_user.c partition_info.cc \
sql_servers.cc event_parse_data.cc
diff --git a/mysql-test/include/have_debug_sync.inc b/mysql-test/include/have_debug_sync.inc
new file mode 100644
index 00000000000..7aa5baf3342
--- /dev/null
+++ b/mysql-test/include/have_debug_sync.inc
@@ -0,0 +1,5 @@
+--require r/have_debug_sync.require
+disable_query_log;
+let $value= query_get_value(SHOW VARIABLES LIKE 'debug_sync', Value, 1);
+eval SELECT ('$value' LIKE 'ON %') AS debug_sync;
+enable_query_log;
diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl
index 114b6c84aa3..da94f02fb27 100755
--- a/mysql-test/mysql-test-run.pl
+++ b/mysql-test/mysql-test-run.pl
@@ -226,6 +226,7 @@ my @default_valgrind_args= ("--show-reachable=yes");
my @valgrind_args;
my $opt_valgrind_path;
my $opt_callgrind;
+my $opt_debug_sync_timeout= 300; # Default timeout for WAIT_FOR actions.
our $opt_warnings= 1;
@@ -867,6 +868,7 @@ sub command_line_setup {
'valgrind-option=s' => \@valgrind_args,
'valgrind-path=s' => \$opt_valgrind_path,
'callgrind' => \$opt_callgrind,
+ 'debug-sync-timeout=i' => \$opt_debug_sync_timeout,
# Directories
'tmpdir=s' => \$opt_tmpdir,
@@ -4170,6 +4172,11 @@ sub mysqld_arguments ($$$) {
mtr_add_arg($args, "%s", "--core-file");
}
+ # Enable the debug sync facility, set default wait timeout.
+ # Facility stays disabled if timeout value is zero.
+ mtr_add_arg($args, "--loose-debug-sync-timeout=%s",
+ $opt_debug_sync_timeout);
+
return $args;
}
@@ -5280,6 +5287,8 @@ Misc options
to turn off.
sleep=SECONDS Passed to mysqltest, will be used as fixed sleep time
+ debug-sync-timeout=NUM Set default timeout for WAIT_FOR debug sync
+ actions. Disable facility with NUM=0.
gcov Collect coverage information after the test.
The result is a gcov file per source and header file.
experimental=<file> Refer to list of tests considered experimental;
diff --git a/mysql-test/r/debug_sync.result b/mysql-test/r/debug_sync.result
new file mode 100644
index 00000000000..47e968f79cf
--- /dev/null
+++ b/mysql-test/r/debug_sync.result
@@ -0,0 +1,277 @@
+SET DEBUG_SYNC= 'RESET';
+DROP TABLE IF EXISTS t1;
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: ''
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 EXECUTE 2';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2';
+SET DEBUG_SYNC='p0 SIGNAL s1 EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1 EXECUTE 2';
+SET DEBUG_SYNC='p0 SIGNAL s1 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 EXECUTE 2';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 WAIT_FOR s2';
+SET DEBUG_SYNC='p0 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 CLEAR';
+SET DEBUG_SYNC='p0 TEST';
+SET DEBUG_SYNC='RESET';
+set debug_sync='p0 signal s1 wait_for s2 timeout 6 execute 2 hit_limit 3';
+set debug_sync='p0 signal s1 wait_for s2 timeout 6 execute 2';
+set debug_sync='p0 signal s1 wait_for s2 timeout 6 hit_limit 3';
+set debug_sync='p0 signal s1 wait_for s2 timeout 6';
+set debug_sync='p0 signal s1 wait_for s2 execute 2 hit_limit 3';
+set debug_sync='p0 signal s1 wait_for s2 execute 2';
+set debug_sync='p0 signal s1 wait_for s2 hit_limit 3';
+set debug_sync='p0 signal s1 wait_for s2';
+set debug_sync='p0 signal s1 execute 2 hit_limit 3';
+set debug_sync='p0 signal s1 execute 2';
+set debug_sync='p0 signal s1 hit_limit 3';
+set debug_sync='p0 signal s1';
+set debug_sync='p0 wait_for s2 timeout 6 execute 2 hit_limit 3';
+set debug_sync='p0 wait_for s2 timeout 6 execute 2';
+set debug_sync='p0 wait_for s2 timeout 6 hit_limit 3';
+set debug_sync='p0 wait_for s2 timeout 6';
+set debug_sync='p0 wait_for s2 execute 2 hit_limit 3';
+set debug_sync='p0 wait_for s2 execute 2';
+set debug_sync='p0 wait_for s2 hit_limit 3';
+set debug_sync='p0 wait_for s2';
+set debug_sync='p0 hit_limit 3';
+set debug_sync='p0 clear';
+set debug_sync='p0 test';
+set debug_sync='reset';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6
+ EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC=' p0 SIGNAL s1 WAIT_FOR s2';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 ';
+SET DEBUG_SYNC=' p0 SIGNAL s1 WAIT_FOR s2 ';
+SET DEBUG_SYNC=' p0 SIGNAL s1 WAIT_FOR s2 ';
+SET DEBUG_SYNC='';
+ERROR 42000: Missing synchronization point name
+SET DEBUG_SYNC=' ';
+ERROR 42000: Missing synchronization point name
+SET DEBUG_SYNC='p0';
+ERROR 42000: Missing action after synchronization point name 'p0'
+SET DEBUG_SYNC='p0 EXECUTE 2';
+ERROR 42000: Missing action before EXECUTE
+SET DEBUG_SYNC='p0 TIMEOUT 6 EXECUTE 2';
+ERROR 42000: Illegal or out of order stuff: 'TIMEOUT'
+SET DEBUG_SYNC='p0 TIMEOUT 6';
+ERROR 42000: Illegal or out of order stuff: 'TIMEOUT'
+SET DEBUG_SYNC='p0 WAIT_FOR s2 SIGNAL s1';
+ERROR 42000: Illegal or out of order stuff: 'SIGNAL'
+SET DEBUG_SYNC='p0 WAIT_FOR s2 SIGNAL s1 EXECUTE 2';
+ERROR 42000: Illegal or out of order stuff: 'SIGNAL'
+SET DEBUG_SYNC='p0 WAIT_FOR s2 SIGNAL s1 TIMEOUT 6 EXECUTE 2';
+ERROR 42000: Illegal or out of order stuff: 'SIGNAL'
+SET DEBUG_SYNC='p0 WAIT_FOR s2 SIGNAL s1 TIMEOUT 6';
+ERROR 42000: Illegal or out of order stuff: 'SIGNAL'
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 SIGNAL s1 EXECUTE 2';
+ERROR 42000: Illegal or out of order stuff: 'SIGNAL'
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 SIGNAL s1';
+ERROR 42000: Illegal or out of order stuff: 'SIGNAL'
+SET DEBUG_SYNC='p0 TIMEOUT 6 WAIT_FOR s2 EXECUTE 2';
+ERROR 42000: Illegal or out of order stuff: 'TIMEOUT'
+SET DEBUG_SYNC='p0 TIMEOUT 6 WAIT_FOR s2';
+ERROR 42000: Illegal or out of order stuff: 'TIMEOUT'
+SET DEBUG_SYNC='p0 SIGNAL s1 TIMEOUT 6 EXECUTE 2';
+ERROR 42000: Illegal or out of order stuff: 'TIMEOUT'
+SET DEBUG_SYNC='p0 SIGNAL s1 TIMEOUT 6';
+ERROR 42000: Illegal or out of order stuff: 'TIMEOUT'
+SET DEBUG_SYNC='p0 EXECUTE 2 SIGNAL s1 TIMEOUT 6';
+ERROR 42000: Missing action before EXECUTE
+SET DEBUG_SYNC='p0 TIMEOUT 6 SIGNAL s1';
+ERROR 42000: Illegal or out of order stuff: 'TIMEOUT'
+SET DEBUG_SYNC='p0 EXECUTE 2 TIMEOUT 6 SIGNAL s1';
+ERROR 42000: Missing action before EXECUTE
+SET DEBUG_SYNC='p0 CLEAR HIT_LIMIT 3';
+ERROR 42000: Nothing must follow action CLEAR
+SET DEBUG_SYNC='CLEAR';
+ERROR 42000: Missing action after synchronization point name 'CLEAR'
+SET DEBUG_SYNC='p0 CLEAR p0';
+ERROR 42000: Nothing must follow action CLEAR
+SET DEBUG_SYNC='TEST';
+ERROR 42000: Missing action after synchronization point name 'TEST'
+SET DEBUG_SYNC='p0 TEST p0';
+ERROR 42000: Nothing must follow action TEST
+SET DEBUG_SYNC='p0 RESET';
+ERROR 42000: Illegal or out of order stuff: 'RESET'
+SET DEBUG_SYNC='RESET p0';
+ERROR 42000: Illegal or out of order stuff: 'p0'
+SET DEBUG_SYNC='p0 RESET p0';
+ERROR 42000: Illegal or out of order stuff: 'RESET'
+SET DEBUG_SYNC='p0 SIGNAL ';
+ERROR 42000: Missing signal name after action SIGNAL
+SET DEBUG_SYNC='p0 WAIT_FOR ';
+ERROR 42000: Missing signal name after action WAIT_FOR
+SET DEBUG_SYNC='p0 SIGNAL s1 EXECUTE ';
+ERROR 42000: Missing valid number after EXECUTE
+SET DEBUG_SYNCx='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIT 3';
+ERROR HY000: Unknown system variable 'DEBUG_SYNCx'
+SET DEBUG_SYNC='p0 SIGNAx s1 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIT 3';
+ERROR 42000: Illegal or out of order stuff: 'SIGNAx'
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOx s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIT 3';
+ERROR 42000: Illegal or out of order stuff: 'WAIT_FOx'
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUx 0 EXECUTE 2 HIT_LIMIT 3';
+ERROR 42000: Illegal or out of order stuff: 'TIMEOUx'
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 EXECUTx 2 HIT_LIMIT 3';
+ERROR 42000: Illegal or out of order stuff: 'EXECUTx'
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIx 3';
+ERROR 42000: Illegal or out of order stuff: 'HIT_LIMIx'
+SET DEBUG_SYNC='p0 CLEARx';
+ERROR 42000: Illegal or out of order stuff: 'CLEARx'
+SET DEBUG_SYNC='p0 TESTx';
+ERROR 42000: Illegal or out of order stuff: 'TESTx'
+SET DEBUG_SYNC='RESETx';
+ERROR 42000: Missing action after synchronization point name 'RESETx'
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 0x6 EXECUTE 2 HIT_LIMIT 3';
+ERROR 42000: Missing valid number after TIMEOUT
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 EXECUTE 0x2 HIT_LIMIT 3';
+ERROR 42000: Missing valid number after EXECUTE
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 7 EXECUTE 2 HIT_LIMIT 0x3';
+ERROR 42000: Missing valid number after HIT_LIMIT
+SET DEBUG_SYNC= 7;
+ERROR 42000: Incorrect argument type to variable 'debug_sync'
+SET GLOBAL DEBUG_SYNC= 'p0 CLEAR';
+ERROR HY000: Variable 'debug_sync' is a SESSION variable and can't be used with SET GLOBAL
+SET @myvar= 'now SIGNAL from_myvar';
+SET DEBUG_SYNC= @myvar;
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 'from_myvar'
+SET DEBUG_SYNC= LEFT('now SIGNAL from_function_cut_here', 24);
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 'from_function'
+SET DEBUG_SYNC= 'now SIGNAL something';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 'something'
+SET DEBUG_SYNC= 'now WAIT_FOR nothing TIMEOUT 0';
+Warnings:
+Warning #### debug sync point wait timed out
+SET DEBUG_SYNC= 'now SIGNAL nothing';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 'nothing'
+SET DEBUG_SYNC= 'now WAIT_FOR nothing TIMEOUT 0';
+SET DEBUG_SYNC= 'now SIGNAL something EXECUTE 0';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 'nothing'
+SET DEBUG_SYNC= 'now WAIT_FOR anotherthing TIMEOUT 0 EXECUTE 0';
+SET DEBUG_SYNC= 'now HIT_LIMIT 1';
+ERROR HY000: debug sync point hit limit reached
+SET DEBUG_SYNC= 'RESET';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: ''
+SET DEBUG_SYNC= 'p1abcd SIGNAL s1 EXECUTE 2';
+SET DEBUG_SYNC= 'p2abc SIGNAL s2 EXECUTE 2';
+SET DEBUG_SYNC= 'p9abcdef SIGNAL s9 EXECUTE 2';
+SET DEBUG_SYNC= 'p4a SIGNAL s4 EXECUTE 2';
+SET DEBUG_SYNC= 'p5abcde SIGNAL s5 EXECUTE 2';
+SET DEBUG_SYNC= 'p6ab SIGNAL s6 EXECUTE 2';
+SET DEBUG_SYNC= 'p7 SIGNAL s7 EXECUTE 2';
+SET DEBUG_SYNC= 'p8abcdef SIGNAL s8 EXECUTE 2';
+SET DEBUG_SYNC= 'p3abcdef SIGNAL s3 EXECUTE 2';
+SET DEBUG_SYNC= 'p4a TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 's4'
+SET DEBUG_SYNC= 'p1abcd TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 's1'
+SET DEBUG_SYNC= 'p7 TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 's7'
+SET DEBUG_SYNC= 'p9abcdef TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 's9'
+SET DEBUG_SYNC= 'p3abcdef TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 's3'
+SET DEBUG_SYNC= 'p1abcd CLEAR';
+SET DEBUG_SYNC= 'p2abc CLEAR';
+SET DEBUG_SYNC= 'p5abcde CLEAR';
+SET DEBUG_SYNC= 'p6ab CLEAR';
+SET DEBUG_SYNC= 'p8abcdef CLEAR';
+SET DEBUG_SYNC= 'p9abcdef CLEAR';
+SET DEBUG_SYNC= 'p3abcdef CLEAR';
+SET DEBUG_SYNC= 'p4a CLEAR';
+SET DEBUG_SYNC= 'p7 CLEAR';
+SET DEBUG_SYNC= 'p1abcd TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 's3'
+SET DEBUG_SYNC= 'p7 TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 's3'
+SET DEBUG_SYNC= 'p9abcdef TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: 's3'
+SET DEBUG_SYNC= 'RESET';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+Variable_name Value
+debug_sync ON - current signal: ''
+CREATE USER mysqltest_1@localhost;
+GRANT SUPER ON *.* TO mysqltest_1@localhost;
+connection con1, mysqltest_1
+SET DEBUG_SYNC= 'RESET';
+connection default
+DROP USER mysqltest_1@localhost;
+CREATE USER mysqltest_2@localhost;
+GRANT ALL ON *.* TO mysqltest_2@localhost;
+REVOKE SUPER ON *.* FROM mysqltest_2@localhost;
+connection con1, mysqltest_2
+SET DEBUG_SYNC= 'RESET';
+ERROR 42000: Access denied; you need the SUPER privilege for this operation
+connection default
+DROP USER mysqltest_2@localhost;
+SET DEBUG_SYNC= 'RESET';
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (c1 INT);
+connection con1
+SET DEBUG_SYNC= 'before_lock_tables_takes_lock
+ SIGNAL opened WAIT_FOR flushed';
+INSERT INTO t1 VALUES(1);
+connection default
+SET DEBUG_SYNC= 'now WAIT_FOR opened';
+SET DEBUG_SYNC= 'after_flush_unlock SIGNAL flushed';
+FLUSH TABLE t1;
+connection con1
+connection default
+DROP TABLE t1;
+SET DEBUG_SYNC= 'RESET';
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (c1 INT);
+LOCK TABLE t1 WRITE;
+connection con1
+SET DEBUG_SYNC= 'wait_for_lock SIGNAL locked EXECUTE 2';
+INSERT INTO t1 VALUES (1);
+connection default
+SET DEBUG_SYNC= 'now WAIT_FOR locked';
+UNLOCK TABLES;
+connection con1
+retrieve INSERT result.
+connection default
+DROP TABLE t1;
+SET DEBUG_SYNC= 'RESET';
diff --git a/mysql-test/r/have_debug_sync.require b/mysql-test/r/have_debug_sync.require
new file mode 100644
index 00000000000..c2090bc5657
--- /dev/null
+++ b/mysql-test/r/have_debug_sync.require
@@ -0,0 +1,2 @@
+debug_sync
+1
diff --git a/mysql-test/t/debug_sync.test b/mysql-test/t/debug_sync.test
new file mode 100644
index 00000000000..514e471b603
--- /dev/null
+++ b/mysql-test/t/debug_sync.test
@@ -0,0 +1,420 @@
+###################### t/debug_sync.test ###############################
+# #
+# Testing of the Debug Sync Facility. #
+# #
+# There is important documentation within sql/debug_sync.cc #
+# #
+# Used objects in this test case: #
+# p0 - synchronization point 0. Non-existent dummy sync point. #
+# s1 - signal 1. #
+# s2 - signal 2. #
+# #
+# Creation: #
+# 2008-02-18 istruewing #
+# #
+########################################################################
+
+#
+# We need the Debug Sync Facility.
+#
+--source include/have_debug_sync.inc
+
+#
+# We are checking privileges, which the embedded server cannot do.
+#
+--source include/not_embedded.inc
+
+#
+# Preparative cleanup.
+#
+--disable_warnings
+SET DEBUG_SYNC= 'RESET';
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+#
+# Show the special system variable.
+# It shows ON or OFF depending on the command line option --debug-sync.
+# The test case assumes it is ON (command line option present).
+#
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+
+#
+# Syntax. Valid forms.
+#
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 EXECUTE 2';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2';
+SET DEBUG_SYNC='p0 SIGNAL s1 EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1 EXECUTE 2';
+SET DEBUG_SYNC='p0 SIGNAL s1 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 SIGNAL s1';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 EXECUTE 2';
+SET DEBUG_SYNC='p0 WAIT_FOR s2 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 WAIT_FOR s2';
+SET DEBUG_SYNC='p0 HIT_LIMIT 3';
+SET DEBUG_SYNC='p0 CLEAR';
+SET DEBUG_SYNC='p0 TEST';
+SET DEBUG_SYNC='RESET';
+
+#
+# Syntax. Valid forms. Lower case.
+#
+set debug_sync='p0 signal s1 wait_for s2 timeout 6 execute 2 hit_limit 3';
+set debug_sync='p0 signal s1 wait_for s2 timeout 6 execute 2';
+set debug_sync='p0 signal s1 wait_for s2 timeout 6 hit_limit 3';
+set debug_sync='p0 signal s1 wait_for s2 timeout 6';
+set debug_sync='p0 signal s1 wait_for s2 execute 2 hit_limit 3';
+set debug_sync='p0 signal s1 wait_for s2 execute 2';
+set debug_sync='p0 signal s1 wait_for s2 hit_limit 3';
+set debug_sync='p0 signal s1 wait_for s2';
+set debug_sync='p0 signal s1 execute 2 hit_limit 3';
+set debug_sync='p0 signal s1 execute 2';
+set debug_sync='p0 signal s1 hit_limit 3';
+set debug_sync='p0 signal s1';
+set debug_sync='p0 wait_for s2 timeout 6 execute 2 hit_limit 3';
+set debug_sync='p0 wait_for s2 timeout 6 execute 2';
+set debug_sync='p0 wait_for s2 timeout 6 hit_limit 3';
+set debug_sync='p0 wait_for s2 timeout 6';
+set debug_sync='p0 wait_for s2 execute 2 hit_limit 3';
+set debug_sync='p0 wait_for s2 execute 2';
+set debug_sync='p0 wait_for s2 hit_limit 3';
+set debug_sync='p0 wait_for s2';
+set debug_sync='p0 hit_limit 3';
+set debug_sync='p0 clear';
+set debug_sync='p0 test';
+set debug_sync='reset';
+
+#
+# Syntax. Valid forms. Line wrap, leading, mid, trailing space.
+#
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6
+ EXECUTE 2 HIT_LIMIT 3';
+SET DEBUG_SYNC=' p0 SIGNAL s1 WAIT_FOR s2';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2';
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 ';
+SET DEBUG_SYNC=' p0 SIGNAL s1 WAIT_FOR s2 ';
+SET DEBUG_SYNC=' p0 SIGNAL s1 WAIT_FOR s2 ';
+
+#
+# Syntax. Invalid forms.
+#
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC=' ';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 EXECUTE 2';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 TIMEOUT 6 EXECUTE 2';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 TIMEOUT 6';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 WAIT_FOR s2 SIGNAL s1';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 WAIT_FOR s2 SIGNAL s1 EXECUTE 2';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 WAIT_FOR s2 SIGNAL s1 TIMEOUT 6 EXECUTE 2';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 WAIT_FOR s2 SIGNAL s1 TIMEOUT 6';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 SIGNAL s1 EXECUTE 2';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 SIGNAL s1';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 TIMEOUT 6 WAIT_FOR s2 EXECUTE 2';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 TIMEOUT 6 WAIT_FOR s2';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 SIGNAL s1 TIMEOUT 6 EXECUTE 2';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 SIGNAL s1 TIMEOUT 6';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 EXECUTE 2 SIGNAL s1 TIMEOUT 6';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 TIMEOUT 6 SIGNAL s1';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 EXECUTE 2 TIMEOUT 6 SIGNAL s1';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 CLEAR HIT_LIMIT 3';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='CLEAR';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 CLEAR p0';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='TEST';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 TEST p0';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 RESET';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='RESET p0';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 RESET p0';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 SIGNAL ';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 WAIT_FOR ';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 SIGNAL s1 EXECUTE ';
+
+#
+# Syntax. Invalid keywords used.
+#
+--error ER_UNKNOWN_SYSTEM_VARIABLE
+SET DEBUG_SYNCx='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIT 3';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 SIGNAx s1 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIT 3';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOx s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIT 3';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUx 0 EXECUTE 2 HIT_LIMIT 3';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 EXECUTx 2 HIT_LIMIT 3';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 SIGNAL s1 WAIT_FOR s2 TIMEOUT 6 EXECUTE 2 HIT_LIMIx 3';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 CLEARx';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 TESTx';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='RESETx';
+
+#
+# Syntax. Invalid numbers. Decimal only.
+#
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 0x6 EXECUTE 2 HIT_LIMIT 3';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 6 EXECUTE 0x2 HIT_LIMIT 3';
+--error ER_PARSE_ERROR
+SET DEBUG_SYNC='p0 WAIT_FOR s2 TIMEOUT 7 EXECUTE 2 HIT_LIMIT 0x3';
+
+#
+# Syntax. Invalid value type.
+#
+--error ER_WRONG_TYPE_FOR_VAR
+SET DEBUG_SYNC= 7;
+
+#
+# Syntax. DEBUG_SYNC is a SESSION-only variable.
+#
+--error ER_LOCAL_VARIABLE
+SET GLOBAL DEBUG_SYNC= 'p0 CLEAR';
+
+#
+# Syntax. The variable value does not need to be a string literal.
+#
+SET @myvar= 'now SIGNAL from_myvar';
+SET DEBUG_SYNC= @myvar;
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+#
+SET DEBUG_SYNC= LEFT('now SIGNAL from_function_cut_here', 24);
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+
+#
+# Functional tests.
+#
+# NOTE: There is the special synchronization point 'now'. It is placed
+# immediately after setting of the DEBUG_SYNC variable.
+# So it is executed before the SET statement ends.
+#
+# NOTE: There is only one global signal (say "signal post" or "flag mast").
+# A SIGNAL action writes its signal into it ("sets a flag").
+# The signal persists until explicitly overwritten.
+# To avoid confusion for later tests, it is recommended to clear
+# the signal by signalling "empty" ("setting the 'empty' flag"):
+# SET DEBUG_SYNC= 'now SIGNAL empty';
+# Preferably you can reset the whole facility with:
+# SET DEBUG_SYNC= 'RESET';
+# The signal is then '' (really empty) which connot be done otherwise.
+#
+
+#
+# Time out immediately. This gives just a warning.
+#
+SET DEBUG_SYNC= 'now SIGNAL something';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+# Suppress warning number
+--replace_column 2 ####
+SET DEBUG_SYNC= 'now WAIT_FOR nothing TIMEOUT 0';
+#
+# If signal is present already, TIMEOUT 0 does not give a warning.
+#
+SET DEBUG_SYNC= 'now SIGNAL nothing';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+SET DEBUG_SYNC= 'now WAIT_FOR nothing TIMEOUT 0';
+
+#
+# EXECUTE 0 is effectively a no-op.
+#
+SET DEBUG_SYNC= 'now SIGNAL something EXECUTE 0';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+SET DEBUG_SYNC= 'now WAIT_FOR anotherthing TIMEOUT 0 EXECUTE 0';
+
+#
+# Run into HIT_LIMIT. This gives an error.
+#
+--error ER_DEBUG_SYNC_HIT_LIMIT
+SET DEBUG_SYNC= 'now HIT_LIMIT 1';
+
+#
+# Many actions. Watch the array growing and shrinking in the debug trace:
+# egrep 'query:|debug_sync_action:' mysql-test/var/log/master.trace
+#
+SET DEBUG_SYNC= 'RESET';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+SET DEBUG_SYNC= 'p1abcd SIGNAL s1 EXECUTE 2';
+SET DEBUG_SYNC= 'p2abc SIGNAL s2 EXECUTE 2';
+SET DEBUG_SYNC= 'p9abcdef SIGNAL s9 EXECUTE 2';
+SET DEBUG_SYNC= 'p4a SIGNAL s4 EXECUTE 2';
+SET DEBUG_SYNC= 'p5abcde SIGNAL s5 EXECUTE 2';
+SET DEBUG_SYNC= 'p6ab SIGNAL s6 EXECUTE 2';
+SET DEBUG_SYNC= 'p7 SIGNAL s7 EXECUTE 2';
+SET DEBUG_SYNC= 'p8abcdef SIGNAL s8 EXECUTE 2';
+SET DEBUG_SYNC= 'p3abcdef SIGNAL s3 EXECUTE 2';
+#
+# Execute some actions to show they exist. Each sets a distinct signal.
+#
+SET DEBUG_SYNC= 'p4a TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+SET DEBUG_SYNC= 'p1abcd TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+SET DEBUG_SYNC= 'p7 TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+SET DEBUG_SYNC= 'p9abcdef TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+SET DEBUG_SYNC= 'p3abcdef TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+#
+# Clear the actions.
+#
+SET DEBUG_SYNC= 'p1abcd CLEAR';
+SET DEBUG_SYNC= 'p2abc CLEAR';
+SET DEBUG_SYNC= 'p5abcde CLEAR';
+SET DEBUG_SYNC= 'p6ab CLEAR';
+SET DEBUG_SYNC= 'p8abcdef CLEAR';
+SET DEBUG_SYNC= 'p9abcdef CLEAR';
+SET DEBUG_SYNC= 'p3abcdef CLEAR';
+SET DEBUG_SYNC= 'p4a CLEAR';
+SET DEBUG_SYNC= 'p7 CLEAR';
+#
+# Execute some actions to show they have gone.
+#
+SET DEBUG_SYNC= 'p1abcd TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+SET DEBUG_SYNC= 'p7 TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+SET DEBUG_SYNC= 'p9abcdef TEST';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+#
+# Now cleanup. Actions are clear already, but signal needs to be cleared.
+#
+SET DEBUG_SYNC= 'RESET';
+SHOW VARIABLES LIKE 'DEBUG_SYNC';
+
+#
+# Facility requires SUPER privilege.
+#
+CREATE USER mysqltest_1@localhost;
+GRANT SUPER ON *.* TO mysqltest_1@localhost;
+--echo connection con1, mysqltest_1
+connect (con1,localhost,mysqltest_1,,);
+SET DEBUG_SYNC= 'RESET';
+disconnect con1;
+--echo connection default
+connection default;
+DROP USER mysqltest_1@localhost;
+#
+CREATE USER mysqltest_2@localhost;
+GRANT ALL ON *.* TO mysqltest_2@localhost;
+REVOKE SUPER ON *.* FROM mysqltest_2@localhost;
+--echo connection con1, mysqltest_2
+connect (con1,localhost,mysqltest_2,,);
+--error ER_SPECIFIC_ACCESS_DENIED_ERROR
+SET DEBUG_SYNC= 'RESET';
+disconnect con1;
+--echo connection default
+connection default;
+DROP USER mysqltest_2@localhost;
+
+#
+# Example 1.
+#
+# Preparative cleanup.
+--disable_warnings
+SET DEBUG_SYNC= 'RESET';
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+#
+# Test.
+CREATE TABLE t1 (c1 INT);
+ --echo connection con1
+ connect (con1,localhost,root,,);
+ SET DEBUG_SYNC= 'before_lock_tables_takes_lock
+ SIGNAL opened WAIT_FOR flushed';
+ send INSERT INTO t1 VALUES(1);
+--echo connection default
+connection default;
+SET DEBUG_SYNC= 'now WAIT_FOR opened';
+SET DEBUG_SYNC= 'after_flush_unlock SIGNAL flushed';
+FLUSH TABLE t1;
+ --echo connection con1
+ connection con1;
+ reap;
+ disconnect con1;
+--echo connection default
+connection default;
+DROP TABLE t1;
+
+#
+# Example 2.
+#
+# Preparative cleanup.
+--disable_warnings
+SET DEBUG_SYNC= 'RESET';
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+#
+# Test.
+CREATE TABLE t1 (c1 INT);
+LOCK TABLE t1 WRITE;
+ --echo connection con1
+ connect (con1,localhost,root,,);
+ # Retain action after use. First used by general_log.
+ SET DEBUG_SYNC= 'wait_for_lock SIGNAL locked EXECUTE 2';
+ send INSERT INTO t1 VALUES (1);
+--echo connection default
+connection default;
+# Wait until INSERT waits for lock.
+SET DEBUG_SYNC= 'now WAIT_FOR locked';
+# let INSERT continue.
+UNLOCK TABLES;
+ --echo connection con1
+ connection con1;
+ --echo retrieve INSERT result.
+ reap;
+ disconnect con1;
+--echo connection default
+connection default;
+DROP TABLE t1;
+
+#
+# Cleanup after test case.
+# Otherwise signal would contain 'flushed' here,
+# which could confuse the next test.
+#
+SET DEBUG_SYNC= 'RESET';
+
diff --git a/mysys/my_static.c b/mysys/my_static.c
index d0c20da828a..a21a3d11104 100644
--- a/mysys/my_static.c
+++ b/mysys/my_static.c
@@ -92,6 +92,14 @@ int (*error_handler_hook)(uint error,const char *str,myf MyFlags)=
int (*fatal_error_handler_hook)(uint error,const char *str,myf MyFlags)=
my_message_no_curses;
+#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) */
+
#ifdef __WIN__
/* from my_getsystime.c */
ulonglong query_performance_frequency, query_performance_offset;
diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c
index 31638ecee9a..0e0e93cf220 100644
--- a/mysys/thr_lock.c
+++ b/mysys/thr_lock.c
@@ -398,6 +398,28 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
my_bool can_deadlock= test(data->owner->info->n_cursors);
DBUG_ENTER("wait_for_lock");
+ /*
+ One can use this to signal when a thread is going to wait for a lock.
+ See debug_sync.cc.
+
+ Beware of waiting for a signal here. The lock has aquired its mutex.
+ While waiting on a signal here, the locking thread could not aquire
+ the mutex to release the lock. One could lock up the table
+ completely.
+
+ In detail it works so: When thr_lock() tries to acquire a table
+ lock, it locks the lock->mutex, checks if it can have the lock, and
+ if not, it calls wait_for_lock(). Here it unlocks the table lock
+ while waiting on a condition. The sync point is located before this
+ wait for condition. If we have a waiting action here, we hold the
+ the table locks mutex all the time. Any attempt to look at the table
+ lock by another thread blocks it immediately on lock->mutex. This
+ can easily become an unexpected and unobvious blockage. So be
+ warned: Do not request a WAIT_FOR action for the 'wait_for_lock'
+ sync point unless you really know what you do.
+ */
+ DEBUG_SYNC_C("wait_for_lock");
+
if (!in_wait_list)
{
(*wait->last)=data; /* Wait for lock */
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt
index 6f162f4d84d..7f6074c903c 100755
--- a/sql/CMakeLists.txt
+++ b/sql/CMakeLists.txt
@@ -64,6 +64,7 @@ SET (SQL_SOURCE
sql_error.cc sql_handler.cc sql_help.cc sql_insert.cc sql_lex.cc
sql_list.cc sql_load.cc sql_manager.cc sql_map.cc sql_parse.cc
sql_partition.cc sql_plugin.cc sql_prepare.cc sql_rename.cc
+ debug_sync.cc debug_sync.h
sql_repl.cc sql_select.cc sql_show.cc sql_state.c sql_string.cc
sql_table.cc sql_test.cc sql_trigger.cc sql_udf.cc sql_union.cc
sql_update.cc sql_view.cc strfunc.cc table.cc thr_malloc.cc
diff --git a/sql/Makefile.am b/sql/Makefile.am
index 2a12de2eaf6..0b22481f850 100644
--- a/sql/Makefile.am
+++ b/sql/Makefile.am
@@ -58,6 +58,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
ha_ndbcluster.h ha_ndbcluster_cond.h \
ha_ndbcluster_binlog.h ha_ndbcluster_tables.h \
ha_partition.h rpl_constants.h \
+ debug_sync.h \
opt_range.h protocol.h rpl_tblmap.h rpl_utility.h \
rpl_reporting.h \
log.h sql_show.h rpl_rli.h rpl_mi.h \
@@ -102,6 +103,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
discover.cc time.cc opt_range.cc opt_sum.cc \
records.cc filesort.cc handler.cc \
ha_partition.cc \
+ debug_sync.cc \
sql_db.cc sql_table.cc sql_rename.cc sql_crypt.cc \
sql_load.cc mf_iocache.cc field_conv.cc sql_show.cc \
sql_udf.cc sql_analyse.cc sql_analyse.h sql_cache.cc \
diff --git a/sql/debug_sync.cc b/sql/debug_sync.cc
new file mode 100644
index 00000000000..58ec4af7841
--- /dev/null
+++ b/sql/debug_sync.cc
@@ -0,0 +1,1906 @@
+/* Copyright (C) 2008 MySQL AB, 2008 - 2009 Sun Microsystems, Inc.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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:
+
+ pthread_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)
+ pthread_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)
+ {
+ pthread_mutex_lock(&mutex);
+ thd->enter_cond(&condition_variable, &mutex, new_message);
+ if (!thd->killed [&& !end_of_wait_condition])
+ {
+ [DEBUG_SYNC(thd, "sync_point_name");]
+ pthread_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
+ pthread_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 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
+*/
+
+#include "debug_sync.h"
+
+#if defined(ENABLED_DEBUG_SYNC)
+
+/*
+ Due to weaknesses in our include files, we need to include
+ mysql_priv.h here. To have THD declared, we need to include
+ sql_class.h. This includes log_event.h, which in turn requires
+ declarations from mysql_priv.h (e.g. OPTION_AUTO_IS_NULL).
+ mysql_priv.h includes almost everything, so is sufficient here.
+*/
+#include "mysql_priv.h"
+
+/*
+ Action to perform at a synchronization point.
+ NOTE: This structure is moved around in memory by realloc(), qsort(),
+ and memmove(). Do not add objects with non-trivial constuctors
+ or destructors, which might prevent moving of this structure
+ with these functions.
+*/
+struct st_debug_sync_action
+{
+ ulong activation_count; /* max(hit_limit, execute) */
+ ulong hit_limit; /* hits before kill query */
+ ulong execute; /* executes before self-clear */
+ ulong timeout; /* wait_for timeout */
+ String signal; /* signal to emit */
+ String wait_for; /* signal to wait for */
+ String sync_point; /* sync point name */
+ bool need_sort; /* if new action, array needs sort */
+};
+
+/* Debug sync control. Referenced by THD. */
+struct st_debug_sync_control
+{
+ st_debug_sync_action *ds_action; /* array of actions */
+ uint ds_active; /* # active actions */
+ uint ds_allocated; /* # allocated actions */
+ ulonglong dsp_hits; /* statistics */
+ ulonglong dsp_executed; /* statistics */
+ ulonglong dsp_max_active; /* statistics */
+ /*
+ thd->proc_info points at unsynchronized memory.
+ It must not go away as long as the thread exists.
+ */
+ char ds_proc_info[80]; /* proc_info string */
+};
+
+
+/**
+ Definitions for the debug sync facility.
+ 1. Global string variable to hold a "signal" ("signal post", "flag mast").
+ 2. Global condition variable for signaling and waiting.
+ 3. Global mutex to synchronize access to the above.
+*/
+struct st_debug_sync_globals
+{
+ String ds_signal; /* signal variable */
+ pthread_cond_t ds_cond; /* condition variable */
+ pthread_mutex_t ds_mutex; /* mutex variable */
+ ulonglong dsp_hits; /* statistics */
+ ulonglong dsp_executed; /* statistics */
+ ulonglong dsp_max_active; /* statistics */
+};
+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);
+
+
+/**
+ 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 mysql_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);
+}
+
+
+/**
+ Initialize the debug sync facility at server start.
+
+ @return status
+ @retval 0 ok
+ @retval != 0 error
+*/
+
+int debug_sync_init(void)
+{
+ DBUG_ENTER("debug_sync_init");
+
+ if (opt_debug_sync_timeout)
+ {
+ int rc;
+
+ /* Initialize the global variables. */
+ debug_sync_global.ds_signal.length(0);
+ if ((rc= pthread_cond_init(&debug_sync_global.ds_cond, NULL)) ||
+ (rc= pthread_mutex_init(&debug_sync_global.ds_mutex,
+ MY_MUTEX_INIT_FAST)))
+ DBUG_RETURN(rc); /* purecov: inspected */
+
+ /* Set the call back pointer in C files. */
+ debug_sync_C_callback_ptr= debug_sync_C_callback;
+ }
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ End the debug sync facility.
+
+ @description
+ This is called at server shutdown or after a thread initialization error.
+*/
+
+void debug_sync_end(void)
+{
+ DBUG_ENTER("debug_sync_end");
+
+ /* End the facility only if it had been initialized. */
+ if (debug_sync_C_callback_ptr)
+ {
+ /* Clear the call back pointer in C files. */
+ debug_sync_C_callback_ptr= NULL;
+
+ /* Destroy the global variables. */
+ debug_sync_global.ds_signal.free();
+ (void) pthread_cond_destroy(&debug_sync_global.ds_cond);
+ (void) pthread_mutex_destroy(&debug_sync_global.ds_mutex);
+
+ /* Print statistics. */
+ {
+ char llbuff[22];
+ sql_print_information("Debug sync points hit: %22s",
+ llstr(debug_sync_global.dsp_hits, llbuff));
+ sql_print_information("Debug sync points executed: %22s",
+ llstr(debug_sync_global.dsp_executed, llbuff));
+ sql_print_information("Debug sync points max active per thread: %22s",
+ llstr(debug_sync_global.dsp_max_active, llbuff));
+ }
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/* purecov: begin tested */
+
+/**
+ Disable the facility after lack of memory if no error can be returned.
+
+ @note
+ Do not end the facility here because the global variables can
+ be in use by other threads.
+*/
+
+static void debug_sync_emergency_disable(void)
+{
+ DBUG_ENTER("debug_sync_emergency_disable");
+
+ opt_debug_sync_timeout= 0;
+
+ DBUG_PRINT("debug_sync",
+ ("Debug Sync Facility disabled due to lack of memory."));
+ sql_print_error("Debug Sync Facility disabled due to lack of memory.");
+
+ DBUG_VOID_RETURN;
+}
+
+/* purecov: end */
+
+
+/**
+ Initialize the debug sync facility at thread start.
+
+ @param[in] thd thread handle
+*/
+
+void debug_sync_init_thread(THD *thd)
+{
+ DBUG_ENTER("debug_sync_init_thread");
+ DBUG_ASSERT(thd);
+
+ if (opt_debug_sync_timeout)
+ {
+ thd->debug_sync_control= (st_debug_sync_control*)
+ my_malloc(sizeof(st_debug_sync_control), MYF(MY_WME | MY_ZEROFILL));
+ if (!thd->debug_sync_control)
+ {
+ /*
+ Error is reported by my_malloc().
+ We must disable the facility. We have no way to return an error.
+ */
+ debug_sync_emergency_disable(); /* purecov: tested */
+ }
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ End the debug sync facility at thread end.
+
+ @param[in] thd thread handle
+*/
+
+void debug_sync_end_thread(THD *thd)
+{
+ DBUG_ENTER("debug_sync_end_thread");
+ DBUG_ASSERT(thd);
+
+ if (thd->debug_sync_control)
+ {
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+
+ /*
+ This synchronization point can be used to synchronize on thread end.
+ This is the latest point in a THD's life, where this can be done.
+ */
+ DEBUG_SYNC(thd, "thread_end");
+
+ if (ds_control->ds_action)
+ {
+ st_debug_sync_action *action= ds_control->ds_action;
+ st_debug_sync_action *action_end= action + ds_control->ds_allocated;
+ for (; action < action_end; action++)
+ {
+ action->signal.free();
+ action->wait_for.free();
+ action->sync_point.free();
+ }
+ my_free(ds_control->ds_action, MYF(0));
+ }
+
+ /* Statistics. */
+ pthread_mutex_lock(&debug_sync_global.ds_mutex);
+ debug_sync_global.dsp_hits+= ds_control->dsp_hits;
+ debug_sync_global.dsp_executed+= ds_control->dsp_executed;
+ if (debug_sync_global.dsp_max_active < ds_control->dsp_max_active)
+ debug_sync_global.dsp_max_active= ds_control->dsp_max_active;
+ pthread_mutex_unlock(&debug_sync_global.ds_mutex);
+
+ my_free(ds_control, MYF(0));
+ thd->debug_sync_control= NULL;
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Move a string by length.
+
+ @param[out] to buffer for the resulting string
+ @param[in] to_end end of buffer
+ @param[in] from source string
+ @param[in] length number of bytes to copy
+
+ @return pointer to end of copied string
+*/
+
+static char *debug_sync_bmove_len(char *to, char *to_end,
+ const char *from, size_t length)
+{
+ DBUG_ASSERT(to);
+ DBUG_ASSERT(to_end);
+ DBUG_ASSERT(!length || from);
+ set_if_smaller(length, (size_t) (to_end - to));
+ memcpy(to, from, length);
+ return (to + length);
+}
+
+
+#if !defined(DBUG_OFF)
+
+/**
+ Create a string that describes an action.
+
+ @param[out] result buffer for the resulting string
+ @param[in] size size of result buffer
+ @param[in] action action to describe
+*/
+
+static void debug_sync_action_string(char *result, uint size,
+ st_debug_sync_action *action)
+{
+ char *wtxt= result;
+ char *wend= wtxt + size - 1; /* Allow emergency '\0'. */
+ DBUG_ASSERT(result);
+ DBUG_ASSERT(action);
+
+ /* If an execute count is present, signal or wait_for are needed too. */
+ DBUG_ASSERT(!action->execute ||
+ action->signal.length() || action->wait_for.length());
+
+ if (action->execute)
+ {
+ if (action->signal.length())
+ {
+ wtxt= debug_sync_bmove_len(wtxt, wend, STRING_WITH_LEN("SIGNAL "));
+ wtxt= debug_sync_bmove_len(wtxt, wend, action->signal.ptr(),
+ action->signal.length());
+ }
+ if (action->wait_for.length())
+ {
+ if ((wtxt == result) && (wtxt < wend))
+ *(wtxt++)= ' ';
+ wtxt= debug_sync_bmove_len(wtxt, wend, STRING_WITH_LEN(" WAIT_FOR "));
+ wtxt= debug_sync_bmove_len(wtxt, wend, action->wait_for.ptr(),
+ action->wait_for.length());
+
+ if (action->timeout != opt_debug_sync_timeout)
+ {
+ wtxt+= my_snprintf(wtxt, wend - wtxt, " TIMEOUT %lu", action->timeout);
+ }
+ }
+ if (action->execute != 1)
+ {
+ wtxt+= my_snprintf(wtxt, wend - wtxt, " EXECUTE %lu", action->execute);
+ }
+ }
+ if (action->hit_limit)
+ {
+ wtxt+= my_snprintf(wtxt, wend - wtxt, "%sHIT_LIMIT %lu",
+ (wtxt == result) ? "" : " ", action->hit_limit);
+ }
+
+ /*
+ If (wtxt == wend) string may not be terminated.
+ There is one byte left for an emergency termination.
+ */
+ *wtxt= '\0';
+}
+
+
+/**
+ Print actions.
+
+ @param[in] thd thread handle
+*/
+
+static void debug_sync_print_actions(THD *thd)
+{
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+ uint idx;
+ DBUG_ENTER("debug_sync_print_actions");
+ DBUG_ASSERT(thd);
+
+ if (!ds_control)
+ return;
+
+ for (idx= 0; idx < ds_control->ds_active; idx++)
+ {
+ const char *dsp_name= ds_control->ds_action[idx].sync_point.c_ptr();
+ char action_string[256];
+
+ debug_sync_action_string(action_string, sizeof(action_string),
+ ds_control->ds_action + idx);
+ DBUG_PRINT("debug_sync_list", ("%s %s", dsp_name, action_string));
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+#endif /* !defined(DBUG_OFF) */
+
+
+/**
+ Compare two actions by sync point name length, string.
+
+ @param[in] arg1 reference to action1
+ @param[in] arg2 reference to action2
+
+ @return difference
+ @retval == 0 length1/string1 is same as length2/string2
+ @retval < 0 length1/string1 is smaller
+ @retval > 0 length1/string1 is bigger
+*/
+
+static int debug_sync_qsort_cmp(const void* arg1, const void* arg2)
+{
+ st_debug_sync_action *action1= (st_debug_sync_action*) arg1;
+ st_debug_sync_action *action2= (st_debug_sync_action*) arg2;
+ int diff;
+ DBUG_ASSERT(action1);
+ DBUG_ASSERT(action2);
+
+ if (!(diff= action1->sync_point.length() - action2->sync_point.length()))
+ diff= memcmp(action1->sync_point.ptr(), action2->sync_point.ptr(),
+ action1->sync_point.length());
+
+ return diff;
+}
+
+
+/**
+ Find a debug sync action.
+
+ @param[in] actionarr array of debug sync actions
+ @param[in] quantity number of actions in array
+ @param[in] dsp_name name of debug sync point to find
+ @param[in] name_len length of name of debug sync point
+
+ @return action
+ @retval != NULL found sync point in array
+ @retval NULL not found
+
+ @description
+ Binary search. Array needs to be sorted by length, sync point name.
+*/
+
+static st_debug_sync_action *debug_sync_find(st_debug_sync_action *actionarr,
+ int quantity,
+ const char *dsp_name,
+ uint name_len)
+{
+ st_debug_sync_action *action;
+ int low ;
+ int high ;
+ int mid ;
+ int diff ;
+ DBUG_ASSERT(actionarr);
+ DBUG_ASSERT(dsp_name);
+ DBUG_ASSERT(name_len);
+
+ low= 0;
+ high= quantity;
+
+ while (low < high)
+ {
+ mid= (low + high) / 2;
+ action= actionarr + mid;
+ if (!(diff= name_len - action->sync_point.length()) &&
+ !(diff= memcmp(dsp_name, action->sync_point.ptr(), name_len)))
+ return action;
+ if (diff > 0)
+ low= mid + 1;
+ else
+ high= mid - 1;
+ }
+
+ if (low < quantity)
+ {
+ action= actionarr + low;
+ if ((name_len == action->sync_point.length()) &&
+ !memcmp(dsp_name, action->sync_point.ptr(), name_len))
+ return action;
+ }
+
+ return NULL;
+}
+
+
+/**
+ Reset the debug sync facility.
+
+ @param[in] thd thread handle
+
+ @description
+ Remove all actions of this thread.
+ Clear the global signal.
+*/
+
+static void debug_sync_reset(THD *thd)
+{
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+ DBUG_ENTER("debug_sync_reset");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(ds_control);
+
+ /* Remove all actions of this thread. */
+ ds_control->ds_active= 0;
+
+ /* Clear the global signal. */
+ pthread_mutex_lock(&debug_sync_global.ds_mutex);
+ debug_sync_global.ds_signal.length(0);
+ pthread_mutex_unlock(&debug_sync_global.ds_mutex);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Remove a debug sync action.
+
+ @param[in] ds_control control object
+ @param[in] action action to be removed
+
+ @description
+ Removing an action mainly means to decrement the ds_active counter.
+ But if the action is between other active action in the array, then
+ the array needs to be shrinked. The active actions above the one to
+ be removed have to be moved down by one slot.
+*/
+
+static void debug_sync_remove_action(st_debug_sync_control *ds_control,
+ st_debug_sync_action *action)
+{
+ uint dsp_idx= action - ds_control->ds_action;
+ DBUG_ENTER("debug_sync_remove_action");
+ DBUG_ASSERT(ds_control);
+ DBUG_ASSERT(ds_control == current_thd->debug_sync_control);
+ DBUG_ASSERT(action);
+ DBUG_ASSERT(dsp_idx < ds_control->ds_active);
+
+ /* Decrement the number of currently active actions. */
+ ds_control->ds_active--;
+
+ /*
+ If this was not the last active action in the array, we need to
+ shift remaining active actions down to keep the array gap-free.
+ Otherwise binary search might fail or take longer than necessary at
+ least. Also new actions are always put to the end of the array.
+ */
+ if (ds_control->ds_active > dsp_idx)
+ {
+ /*
+ Do not make save_action an object of class st_debug_sync_action.
+ Its destructor would tamper with the String pointers.
+ */
+ uchar save_action[sizeof(st_debug_sync_action)];
+
+ /*
+ Copy the to-be-removed action object to temporary storage before
+ the shift copies the string pointers over. Do not use assignment
+ because it would use assignment operator methods for the Strings.
+ This would copy the strings. The shift below overwrite the string
+ pointers without freeing them first. By using memmove() we save
+ the pointers, which are overwritten by the shift.
+ */
+ memmove(save_action, action, sizeof(st_debug_sync_action));
+
+ /* Move actions down. */
+ memmove(ds_control->ds_action + dsp_idx,
+ ds_control->ds_action + dsp_idx + 1,
+ (ds_control->ds_active - dsp_idx) *
+ sizeof(st_debug_sync_action));
+
+ /*
+ Copy back the saved action object to the now free array slot. This
+ replaces the double references of String pointers that have been
+ produced by the shift. Again do not use an assignment operator to
+ avoid string allocation/copy.
+ */
+ memmove(ds_control->ds_action + ds_control->ds_active, save_action,
+ sizeof(st_debug_sync_action));
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Get a debug sync action.
+
+ @param[in] thd thread handle
+ @param[in] dsp_name debug sync point name
+ @param[in] name_len length of sync point name
+
+ @return action
+ @retval != NULL ok
+ @retval NULL error
+
+ @description
+ Find the debug sync action for a debug sync point or make a new one.
+*/
+
+static st_debug_sync_action *debug_sync_get_action(THD *thd,
+ const char *dsp_name,
+ uint name_len)
+{
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+ st_debug_sync_action *action;
+ DBUG_ENTER("debug_sync_get_action");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(dsp_name);
+ DBUG_ASSERT(name_len);
+ DBUG_ASSERT(ds_control);
+ DBUG_PRINT("debug_sync", ("sync_point: '%.*s'", (int) name_len, dsp_name));
+ DBUG_PRINT("debug_sync", ("active: %u allocated: %u",
+ ds_control->ds_active, ds_control->ds_allocated));
+
+ /* There cannot be more active actions than allocated. */
+ DBUG_ASSERT(ds_control->ds_active <= ds_control->ds_allocated);
+ /* If there are active actions, the action array must be present. */
+ DBUG_ASSERT(!ds_control->ds_active || ds_control->ds_action);
+
+ /* Try to reuse existing action if there is one for this sync point. */
+ if (ds_control->ds_active &&
+ (action= debug_sync_find(ds_control->ds_action, ds_control->ds_active,
+ dsp_name, name_len)))
+ {
+ /* Reuse an already active sync point action. */
+ DBUG_ASSERT((uint)(action - ds_control->ds_action) < ds_control->ds_active);
+ DBUG_PRINT("debug_sync", ("reuse action idx: %ld",
+ (long) (action - ds_control->ds_action)));
+ }
+ else
+ {
+ /* Create a new action. */
+ int dsp_idx= ds_control->ds_active++;
+ set_if_bigger(ds_control->dsp_max_active, ds_control->ds_active);
+ if (ds_control->ds_active > ds_control->ds_allocated)
+ {
+ uint new_alloc= ds_control->ds_active + 3;
+ void *new_action= my_realloc(ds_control->ds_action,
+ new_alloc * sizeof(st_debug_sync_action),
+ MYF(MY_WME | MY_ALLOW_ZERO_PTR));
+ if (!new_action)
+ {
+ /* Error is reported by my_malloc(). */
+ goto err; /* purecov: tested */
+ }
+ ds_control->ds_action= (st_debug_sync_action*) new_action;
+ ds_control->ds_allocated= new_alloc;
+ /* Clear memory as we do not run string constructors here. */
+ bzero((uchar*) (ds_control->ds_action + dsp_idx),
+ (new_alloc - dsp_idx) * sizeof(st_debug_sync_action));
+ }
+ DBUG_PRINT("debug_sync", ("added action idx: %u", dsp_idx));
+ action= ds_control->ds_action + dsp_idx;
+ if (action->sync_point.copy(dsp_name, name_len, system_charset_info))
+ {
+ /* Error is reported by my_malloc(). */
+ goto err; /* purecov: tested */
+ }
+ action->need_sort= TRUE;
+ }
+ DBUG_ASSERT(action >= ds_control->ds_action);
+ DBUG_ASSERT(action < ds_control->ds_action + ds_control->ds_active);
+ DBUG_PRINT("debug_sync", ("action: 0x%lx array: 0x%lx count: %u",
+ (long) action, (long) ds_control->ds_action,
+ ds_control->ds_active));
+
+ DBUG_RETURN(action);
+
+ /* purecov: begin tested */
+ err:
+ DBUG_RETURN(NULL);
+ /* purecov: end */
+}
+
+
+/**
+ Set a debug sync action.
+
+ @param[in] thd thread handle
+ @param[in] action synchronization action
+
+ @return status
+ @retval FALSE ok
+ @retval TRUE error
+
+ @description
+ This is called from the debug sync parser. It arms the action for
+ the requested sync point. If the action parsed into an empty action,
+ it is removed instead.
+
+ Setting an action for a sync point means to make the sync point
+ active. When it is hit it will execute this action.
+
+ Before parsing, we "get" an action object. This is placed at the
+ end of the thread's action array unless the requested sync point
+ has an action already.
+
+ Then the parser fills the action object from the request string.
+
+ Finally the action is "set" for the sync point. If it was parsed
+ to be empty, it is removed from the array. If it did belong to a
+ sync point before, the sync point becomes inactive. If the action
+ became non-empty and it did not belong to a sync point before (it
+ was added at the end of the action array), the action array needs
+ to be sorted by sync point.
+
+ If the sync point name is "now", it is executed immediately.
+*/
+
+static bool debug_sync_set_action(THD *thd, st_debug_sync_action *action)
+{
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+ bool is_dsp_now= FALSE;
+ DBUG_ENTER("debug_sync_set_action");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(action);
+ DBUG_ASSERT(ds_control);
+
+ action->activation_count= max(action->hit_limit, action->execute);
+ if (!action->activation_count)
+ {
+ debug_sync_remove_action(ds_control, action);
+ DBUG_PRINT("debug_sync", ("action cleared"));
+ }
+ else
+ {
+ const char *dsp_name= action->sync_point.c_ptr();
+ DBUG_EXECUTE("debug_sync", {
+ /* Functions as DBUG_PRINT args can change keyword and line nr. */
+ const char *sig_emit= action->signal.c_ptr();
+ const char *sig_wait= action->wait_for.c_ptr();
+ DBUG_PRINT("debug_sync",
+ ("sync_point: '%s' activation_count: %lu hit_limit: %lu "
+ "execute: %lu timeout: %lu signal: '%s' wait_for: '%s'",
+ dsp_name, action->activation_count,
+ action->hit_limit, action->execute, action->timeout,
+ sig_emit, sig_wait));});
+
+ /* Check this before sorting the array. action may move. */
+ is_dsp_now= !my_strcasecmp(system_charset_info, dsp_name, "now");
+
+ if (action->need_sort)
+ {
+ action->need_sort= FALSE;
+ /* Sort actions by (name_len, name). */
+ my_qsort(ds_control->ds_action, ds_control->ds_active,
+ sizeof(st_debug_sync_action), debug_sync_qsort_cmp);
+ }
+ }
+ DBUG_EXECUTE("debug_sync_list", debug_sync_print_actions(thd););
+
+ /* Execute the special sync point 'now' if activated above. */
+ if (is_dsp_now)
+ {
+ DEBUG_SYNC(thd, "now");
+ /*
+ If HIT_LIMIT for sync point "now" was 1, the execution of the sync
+ point decremented it to 0. In this case the following happened:
+
+ - an error message was reported with my_error() and
+ - the statement was killed with thd->killed= THD::KILL_QUERY.
+
+ If a statement reports an error, it must not call send_ok().
+ The calling functions will not call send_ok(), if we return TRUE
+ from this function.
+
+ thd->killed is also set if the wait is interrupted from a
+ KILL or KILL QUERY statement. In this case, no error is reported
+ and shall not be reported as a result of SET DEBUG_SYNC.
+ Hence, we check for the first condition above.
+ */
+ if (thd->is_error())
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Extract a token from a string.
+
+ @param[out] token_p returns start of token
+ @param[out] token_length_p returns length of token
+ @param[in,out] ptr current string pointer, adds '\0' terminators
+
+ @return string pointer or NULL
+ @retval != NULL ptr behind token terminator or at string end
+ @retval NULL no token found in remainder of string
+
+ @note
+ This function assumes that the string is in system_charset_info,
+ that this charset is single byte for ASCII NUL ('\0'), that no
+ character except of ASCII NUL ('\0') contains a byte with value 0,
+ and that ASCII NUL ('\0') is used as the string terminator.
+
+ This function needs to return tokens that are terminated with ASCII
+ NUL ('\0'). The tokens are used in my_strcasecmp(). Unfortunately
+ there is no my_strncasecmp().
+
+ To return the last token without copying it, we require the input
+ string to be nul terminated.
+
+ @description
+ This function skips space characters at string begin.
+
+ It returns a pointer to the first non-space character in *token_p.
+
+ If no non-space character is found before the string terminator
+ ASCII NUL ('\0'), the function returns NULL. *token_p and
+ *token_length_p remain unchanged in this case (they are not set).
+
+ The function takes a space character or an ASCII NUL ('\0') as a
+ terminator of the token. The space character could be multi-byte.
+
+ It returns the length of the token in bytes, excluding the
+ terminator, in *token_length_p.
+
+ If the terminator of the token is ASCII NUL ('\0'), it returns a
+ pointer to the terminator (string end).
+
+ If the terminator is a space character, it replaces the the first
+ byte of the terminator character by ASCII NUL ('\0'), skips the (now
+ corrupted) terminator character, and skips all following space
+ characters. It returns a pointer to the next non-space character or
+ to the string terminator ASCII NUL ('\0').
+*/
+
+static char *debug_sync_token(char **token_p, uint *token_length_p, char *ptr)
+{
+ DBUG_ASSERT(token_p);
+ DBUG_ASSERT(token_length_p);
+ DBUG_ASSERT(ptr);
+
+ /* Skip leading space */
+ while (my_isspace(system_charset_info, *ptr))
+ ptr+= my_mbcharlen(system_charset_info, (uchar) *ptr);
+
+ if (!*ptr)
+ {
+ ptr= NULL;
+ goto end;
+ }
+
+ /* Get token start. */
+ *token_p= ptr;
+
+ /* Find token end. */
+ while (*ptr && !my_isspace(system_charset_info, *ptr))
+ ptr+= my_mbcharlen(system_charset_info, (uchar) *ptr);
+
+ /* Get token length. */
+ *token_length_p= ptr - *token_p;
+
+ /* If necessary, terminate token. */
+ if (*ptr)
+ {
+ /* Get terminator character length. */
+ uint mbspacelen= my_mbcharlen(system_charset_info, (uchar) *ptr);
+
+ /* Terminate token. */
+ *ptr= '\0';
+
+ /* Skip the terminator. */
+ ptr+= mbspacelen;
+
+ /* Skip trailing space */
+ while (my_isspace(system_charset_info, *ptr))
+ ptr+= my_mbcharlen(system_charset_info, (uchar) *ptr);
+ }
+
+ end:
+ return ptr;
+}
+
+
+/**
+ Extract a number from a string.
+
+ @param[out] number_p returns number
+ @param[in] actstrptr current pointer in action string
+
+ @return string pointer or NULL
+ @retval != NULL ptr behind token terminator or at string end
+ @retval NULL no token found or token is not valid number
+
+ @note
+ The same assumptions about charset apply as for debug_sync_token().
+
+ @description
+ This function fetches a token from the string and converts it
+ into a number.
+
+ If there is no token left in the string, or the token is not a valid
+ decimal number, NULL is returned. The result in *number_p is
+ undefined in this case.
+*/
+
+static char *debug_sync_number(ulong *number_p, char *actstrptr)
+{
+ char *ptr;
+ char *ept;
+ char *token;
+ uint token_length;
+ DBUG_ASSERT(number_p);
+ DBUG_ASSERT(actstrptr);
+
+ /* Get token from string. */
+ if (!(ptr= debug_sync_token(&token, &token_length, actstrptr)))
+ goto end;
+
+ *number_p= strtoul(token, &ept, 10);
+ if (*ept)
+ ptr= NULL;
+
+ end:
+ return ptr;
+}
+
+
+/**
+ Evaluate a debug sync action string.
+
+ @param[in] thd thread handle
+ @param[in,out] action_str action string to receive '\0' terminators
+
+ @return status
+ @retval FALSE ok
+ @retval TRUE error
+
+ @description
+ This is called when the DEBUG_SYNC system variable is set.
+ Parse action string, build a debug sync action, activate it.
+
+ Before parsing, we "get" an action object. This is placed at the
+ end of the thread's action array unless the requested sync point
+ has an action already.
+
+ Then the parser fills the action object from the request string.
+
+ Finally the action is "set" for the sync point. This means that the
+ sync point becomes active or inactive, depending on the action
+ values.
+
+ @note
+ The input string needs to be ASCII NUL ('\0') terminated. We split
+ nul-terminated tokens in it without copy.
+
+ @see the function comment of debug_sync_token() for more constraints
+ for the string.
+*/
+
+static bool debug_sync_eval_action(THD *thd, char *action_str)
+{
+ st_debug_sync_action *action= NULL;
+ const char *errmsg;
+ char *ptr;
+ char *token;
+ uint token_length;
+ DBUG_ENTER("debug_sync_eval_action");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(action_str);
+
+ /*
+ Get debug sync point name. Or a special command.
+ */
+ if (!(ptr= debug_sync_token(&token, &token_length, action_str)))
+ {
+ errmsg= "Missing synchronization point name";
+ goto err;
+ }
+
+ /*
+ If there is a second token, the first one is the sync point name.
+ */
+ if (*ptr)
+ {
+ /* Get an action object to collect the requested action parameters. */
+ action= debug_sync_get_action(thd, token, token_length);
+ if (!action)
+ {
+ /* Error message is sent. */
+ DBUG_RETURN(TRUE); /* purecov: tested */
+ }
+ }
+
+ /*
+ Get kind of action to be taken at sync point.
+ */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ {
+ /* No action present. Try special commands. Token unchanged. */
+
+ /*
+ Try RESET.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "RESET"))
+ {
+ /* It is RESET. Reset all actions and global signal. */
+ debug_sync_reset(thd);
+ goto end;
+ }
+
+ /* Token unchanged. It still contains sync point name. */
+ errmsg= "Missing action after synchronization point name '%.*s'";
+ goto err;
+ }
+
+ /*
+ Check for pseudo actions first. Start with actions that work on
+ an existing action.
+ */
+ DBUG_ASSERT(action);
+
+ /*
+ Try TEST.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "TEST"))
+ {
+ /* It is TEST. Nothing must follow it. */
+ if (*ptr)
+ {
+ errmsg= "Nothing must follow action TEST";
+ goto err;
+ }
+
+ /* Execute sync point. */
+ debug_sync(thd, action->sync_point.ptr(), action->sync_point.length());
+ /* Fix statistics. This was not a real hit of the sync point. */
+ thd->debug_sync_control->dsp_hits--;
+ goto end;
+ }
+
+ /*
+ Now check for actions that define a new action.
+ Initialize action. Do not use bzero(). Strings may have malloced.
+ */
+ action->activation_count= 0;
+ action->hit_limit= 0;
+ action->execute= 0;
+ action->timeout= 0;
+ action->signal.length(0);
+ action->wait_for.length(0);
+
+ /*
+ Try CLEAR.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "CLEAR"))
+ {
+ /* It is CLEAR. Nothing must follow it. */
+ if (*ptr)
+ {
+ errmsg= "Nothing must follow action CLEAR";
+ goto err;
+ }
+
+ /* Set (clear/remove) action. */
+ goto set_action;
+ }
+
+ /*
+ Now check for real sync point actions.
+ */
+
+ /*
+ Try SIGNAL.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "SIGNAL"))
+ {
+ /* It is SIGNAL. Signal name must follow. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ {
+ errmsg= "Missing signal name after action SIGNAL";
+ goto err;
+ }
+ if (action->signal.copy(token, token_length, system_charset_info))
+ {
+ /* Error is reported by my_malloc(). */
+ /* purecov: begin tested */
+ errmsg= NULL;
+ goto err;
+ /* purecov: end */
+ }
+
+ /* Set default for EXECUTE option. */
+ action->execute= 1;
+
+ /* Get next token. If none follows, set action. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ goto set_action;
+ }
+
+ /*
+ Try WAIT_FOR.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "WAIT_FOR"))
+ {
+ /* It is WAIT_FOR. Wait_for signal name must follow. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ {
+ errmsg= "Missing signal name after action WAIT_FOR";
+ goto err;
+ }
+ if (action->wait_for.copy(token, token_length, system_charset_info))
+ {
+ /* Error is reported by my_malloc(). */
+ /* purecov: begin tested */
+ errmsg= NULL;
+ goto err;
+ /* purecov: end */
+ }
+
+ /* Set default for EXECUTE and TIMEOUT options. */
+ action->execute= 1;
+ action->timeout= opt_debug_sync_timeout;
+
+ /* Get next token. If none follows, set action. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ goto set_action;
+
+ /*
+ Try TIMEOUT.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "TIMEOUT"))
+ {
+ /* It is TIMEOUT. Number must follow. */
+ if (!(ptr= debug_sync_number(&action->timeout, ptr)))
+ {
+ errmsg= "Missing valid number after TIMEOUT";
+ goto err;
+ }
+
+ /* Get next token. If none follows, set action. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ goto set_action;
+ }
+ }
+
+ /*
+ Try EXECUTE.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "EXECUTE"))
+ {
+ /*
+ EXECUTE requires either SIGNAL and/or WAIT_FOR to be present.
+ In this case action->execute has been preset to 1.
+ */
+ if (!action->execute)
+ {
+ errmsg= "Missing action before EXECUTE";
+ goto err;
+ }
+
+ /* Number must follow. */
+ if (!(ptr= debug_sync_number(&action->execute, ptr)))
+ {
+ errmsg= "Missing valid number after EXECUTE";
+ goto err;
+ }
+
+ /* Get next token. If none follows, set action. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ goto set_action;
+ }
+
+ /*
+ Try HIT_LIMIT.
+ */
+ if (!my_strcasecmp(system_charset_info, token, "HIT_LIMIT"))
+ {
+ /* Number must follow. */
+ if (!(ptr= debug_sync_number(&action->hit_limit, ptr)))
+ {
+ errmsg= "Missing valid number after HIT_LIMIT";
+ goto err;
+ }
+
+ /* Get next token. If none follows, set action. */
+ if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
+ goto set_action;
+ }
+
+ errmsg= "Illegal or out of order stuff: '%.*s'";
+
+ err:
+ if (errmsg)
+ {
+ /*
+ NOTE: errmsg must either have %.*s or none % at all.
+ It can be NULL if an error message is already reported
+ (e.g. by my_malloc()).
+ */
+ set_if_smaller(token_length, 64); /* Limit error message length. */
+ my_printf_error(ER_PARSE_ERROR, errmsg, MYF(0), token_length, token);
+ }
+ if (action)
+ debug_sync_remove_action(thd->debug_sync_control, action);
+ DBUG_RETURN(TRUE);
+
+ set_action:
+ DBUG_RETURN(debug_sync_set_action(thd, action));
+
+ end:
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Check if the system variable 'debug_sync' can be set.
+
+ @param[in] thd thread handle
+ @param[in] var set variable request
+
+ @return status
+ @retval FALSE ok, variable can be set
+ @retval TRUE error, variable cannot be set
+*/
+
+bool sys_var_debug_sync::check(THD *thd, set_var *var)
+{
+ DBUG_ENTER("sys_var_debug_sync::check");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(var);
+
+ /*
+ Variable can be set for the session only.
+
+ This could be changed later. Then we need to have a global array of
+ actions in addition to the thread local ones. SET GLOBAL would
+ manage the global array, SET [SESSION] the local array. A sync point
+ would need to look for a local and a global action. Setting and
+ executing of global actions need to be protected by a mutex.
+
+ The purpose of global actions could be to allow synchronizing with
+ connectionless threads that cannot execute SET statements.
+ */
+ if (var->type == OPT_GLOBAL)
+ {
+ my_error(ER_LOCAL_VARIABLE, MYF(0), name);
+ DBUG_RETURN(TRUE);
+ }
+
+ /*
+ Do not check for disabled facility. Test result should not
+ unnecessarily differ from enabled facility.
+ */
+
+ /*
+ Facility requires SUPER privilege. Sync points could be inside
+ global mutexes (e.g. LOCK_open). Waiting there forever would
+ stall the whole server.
+ */
+ DBUG_RETURN(check_global_access(thd, SUPER_ACL));
+}
+
+
+/**
+ Set the system variable 'debug_sync'.
+
+ @param[in] thd thread handle
+ @param[in] var set variable request
+
+ @return status
+ @retval FALSE ok, variable is set
+ @retval TRUE error, variable could not be set
+
+ @note
+ "Setting" of the system variable 'debug_sync' does not mean to
+ assign a value to it as usual. Instead a debug sync action is parsed
+ from the input string and stored apart from the variable value.
+
+ @note
+ For efficiency reasons, the action string parser places '\0'
+ terminators in the string. So we need to take a copy here.
+*/
+
+bool sys_var_debug_sync::update(THD *thd, set_var *var)
+{
+ char *val_str;
+ String *val_ptr;
+ String val_buf;
+ DBUG_ENTER("sys_var_debug_sync::update");
+ DBUG_ASSERT(thd);
+
+ /*
+ Depending on the value type (string literal, user variable, ...)
+ val_buf receives a copy of the value or not. But we always need
+ a copy. So we take a copy, if it is not done by val_str().
+ If val_str() puts a copy into val_buf, then it returns &val_buf,
+ otherwise it returns a pointer to the string object that we need
+ to copy.
+ */
+ val_ptr= var ? var->value->val_str(&val_buf) : &val_buf;
+ if (val_ptr != &val_buf)
+ {
+ val_buf.copy(*val_ptr);
+ }
+ val_str= val_buf.c_ptr();
+ DBUG_PRINT("debug_sync", ("set action: '%s'", val_str));
+
+ /*
+ debug_sync_eval_action() places '\0' in the string, which itself
+ must be '\0' terminated.
+ */
+ DBUG_RETURN(opt_debug_sync_timeout ?
+ debug_sync_eval_action(thd, val_str) :
+ FALSE);
+}
+
+
+/**
+ Retrieve the value of the system variable 'debug_sync'.
+
+ @param[in] thd thread handle
+ @param[in] type variable type, unused
+ @param[in] base variable base, unused
+
+ @return string
+ @retval != NULL ok, string pointer
+ @retval NULL memory allocation error
+
+ @note
+ The value of the system variable 'debug_sync' reflects if
+ the facility is enabled ("ON") or disabled (default, "OFF").
+
+ When "ON", the current signal is added.
+*/
+
+uchar *sys_var_debug_sync::value_ptr(THD *thd,
+ enum_var_type type __attribute__((unused)),
+ LEX_STRING *base __attribute__((unused)))
+{
+ char *value;
+ DBUG_ENTER("sys_var_debug_sync::value_ptr");
+ DBUG_ASSERT(thd);
+
+ if (opt_debug_sync_timeout)
+ {
+ static char on[]= "ON - current signal: '";
+
+ // Ensure exclusive access to debug_sync_global.ds_signal
+ pthread_mutex_lock(&debug_sync_global.ds_mutex);
+
+ size_t lgt= (sizeof(on) /* includes '\0' */ +
+ debug_sync_global.ds_signal.length() + 1 /* for '\'' */);
+ char *vend;
+ char *vptr;
+
+ if ((value= (char*) alloc_root(thd->mem_root, lgt)))
+ {
+ vend= value + lgt - 1; /* reserve space for '\0'. */
+ vptr= debug_sync_bmove_len(value, vend, STRING_WITH_LEN(on));
+ vptr= debug_sync_bmove_len(vptr, vend, debug_sync_global.ds_signal.ptr(),
+ debug_sync_global.ds_signal.length());
+ if (vptr < vend)
+ *(vptr++)= '\'';
+ *vptr= '\0'; /* We have one byte reserved for the worst case. */
+ }
+ pthread_mutex_unlock(&debug_sync_global.ds_mutex);
+ }
+ else
+ {
+ /* purecov: begin tested */
+ value= strmake_root(thd->mem_root, STRING_WITH_LEN("OFF"));
+ /* purecov: end */
+ }
+
+ DBUG_RETURN((uchar*) value);
+}
+
+
+/**
+ Execute requested action at a synchronization point.
+
+ @param[in] thd thread handle
+ @param[in] action action to be executed
+
+ @note
+ This is to be called only if activation count > 0.
+*/
+
+static void debug_sync_execute(THD *thd, st_debug_sync_action *action)
+{
+ IF_DBUG(const char *dsp_name= action->sync_point.c_ptr());
+ IF_DBUG(const char *sig_emit= action->signal.c_ptr());
+ IF_DBUG(const char *sig_wait= action->wait_for.c_ptr());
+ DBUG_ENTER("debug_sync_execute");
+ DBUG_ASSERT(thd);
+ DBUG_ASSERT(action);
+ DBUG_PRINT("debug_sync",
+ ("sync_point: '%s' activation_count: %lu hit_limit: %lu "
+ "execute: %lu timeout: %lu signal: '%s' wait_for: '%s'",
+ dsp_name, action->activation_count, action->hit_limit,
+ action->execute, action->timeout, sig_emit, sig_wait));
+
+ DBUG_ASSERT(action->activation_count);
+ action->activation_count--;
+
+ if (action->execute)
+ {
+ const char *old_proc_info;
+
+ action->execute--;
+
+ /*
+ If we will be going to wait, set proc_info for the PROCESSLIST table.
+ Do this before emitting the signal, so other threads can see it
+ if they awake before we enter_cond() below.
+ */
+ if (action->wait_for.length())
+ {
+ st_debug_sync_control *ds_control= thd->debug_sync_control;
+ strxnmov(ds_control->ds_proc_info, sizeof(ds_control->ds_proc_info)-1,
+ "debug sync point: ", action->sync_point.c_ptr(), NullS);
+ old_proc_info= thd->proc_info;
+ thd_proc_info(thd, ds_control->ds_proc_info);
+ }
+
+ /*
+ Take mutex to ensure that only one thread access
+ debug_sync_global.ds_signal at a time. Need to take mutex for
+ read access too, to create a memory barrier in order to avoid that
+ threads just reads an old cached version of the signal.
+ */
+ pthread_mutex_lock(&debug_sync_global.ds_mutex);
+
+ if (action->signal.length())
+ {
+ /* Copy the signal to the global variable. */
+ if (debug_sync_global.ds_signal.copy(action->signal))
+ {
+ /*
+ Error is reported by my_malloc().
+ We must disable the facility. We have no way to return an error.
+ */
+ debug_sync_emergency_disable(); /* purecov: tested */
+ }
+ /* Wake threads waiting in a sync point. */
+ pthread_cond_broadcast(&debug_sync_global.ds_cond);
+ DBUG_PRINT("debug_sync_exec", ("signal '%s' at: '%s'",
+ sig_emit, dsp_name));
+ } /* end if (action->signal.length()) */
+
+ if (action->wait_for.length())
+ {
+ pthread_mutex_t *old_mutex;
+ pthread_cond_t *old_cond;
+ int error= 0;
+ struct timespec abstime;
+
+ /*
+ We don't use enter_cond()/exit_cond(). They do not save old
+ mutex and cond. This would prohibit the use of DEBUG_SYNC
+ between other places of enter_cond() and exit_cond().
+ */
+ old_mutex= thd->mysys_var->current_mutex;
+ old_cond= thd->mysys_var->current_cond;
+ thd->mysys_var->current_mutex= &debug_sync_global.ds_mutex;
+ thd->mysys_var->current_cond= &debug_sync_global.ds_cond;
+
+ set_timespec(abstime, action->timeout);
+ DBUG_EXECUTE("debug_sync_exec", {
+ /* Functions as DBUG_PRINT args can change keyword and line nr. */
+ const char *sig_glob= debug_sync_global.ds_signal.c_ptr();
+ DBUG_PRINT("debug_sync_exec",
+ ("wait for '%s' at: '%s' curr: '%s'",
+ sig_wait, dsp_name, sig_glob));});
+
+ /*
+ Wait until global signal string matches the wait_for string.
+ Interrupt when thread or query is killed or facility disabled.
+ The facility can become disabled when some thread cannot get
+ the required dynamic memory allocated.
+ */
+ while (stringcmp(&debug_sync_global.ds_signal, &action->wait_for) &&
+ !thd->killed && opt_debug_sync_timeout)
+ {
+ error= pthread_cond_timedwait(&debug_sync_global.ds_cond,
+ &debug_sync_global.ds_mutex,
+ &abstime);
+ DBUG_EXECUTE("debug_sync", {
+ /* Functions as DBUG_PRINT args can change keyword and line nr. */
+ const char *sig_glob= debug_sync_global.ds_signal.c_ptr();
+ DBUG_PRINT("debug_sync",
+ ("awoke from %s global: %s error: %d",
+ sig_wait, sig_glob, error));});
+ if (error == ETIMEDOUT || error == ETIME)
+ {
+ push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_DEBUG_SYNC_TIMEOUT, ER(ER_DEBUG_SYNC_TIMEOUT));
+ break;
+ }
+ error= 0;
+ }
+ DBUG_EXECUTE("debug_sync_exec",
+ if (thd->killed)
+ DBUG_PRINT("debug_sync_exec",
+ ("killed %d from '%s' at: '%s'",
+ thd->killed, sig_wait, dsp_name));
+ else
+ DBUG_PRINT("debug_sync_exec",
+ ("%s from '%s' at: '%s'",
+ error ? "timeout" : "resume",
+ sig_wait, dsp_name)););
+
+ /*
+ We don't use enter_cond()/exit_cond(). They do not save old
+ mutex and cond. This would prohibit the use of DEBUG_SYNC
+ between other places of enter_cond() and exit_cond(). The
+ protected mutex must always unlocked _before_ mysys_var->mutex
+ is locked. (See comment in THD::exit_cond().)
+ */
+ pthread_mutex_unlock(&debug_sync_global.ds_mutex);
+ pthread_mutex_lock(&thd->mysys_var->mutex);
+ thd->mysys_var->current_mutex= old_mutex;
+ thd->mysys_var->current_cond= old_cond;
+ thd_proc_info(thd, old_proc_info);
+ pthread_mutex_unlock(&thd->mysys_var->mutex);
+
+ }
+ else
+ {
+ /* In case we don't wait, we just release the mutex. */
+ pthread_mutex_unlock(&debug_sync_global.ds_mutex);
+ } /* end if (action->wait_for.length()) */
+
+ } /* end if (action->execute) */
+
+ /* hit_limit is zero for infinite. Don't decrement unconditionally. */
+ if (action->hit_limit)
+ {
+ if (!--action->hit_limit)
+ {
+ thd->killed= THD::KILL_QUERY;
+ my_error(ER_DEBUG_SYNC_HIT_LIMIT, MYF(0));
+ }
+ DBUG_PRINT("debug_sync_exec", ("hit_limit: %lu at: '%s'",
+ action->hit_limit, dsp_name));
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Execute requested action at a synchronization point.
+
+ @param[in] thd thread handle
+ @param[in] sync_point_name name of synchronization point
+ @param[in] name_len length of sync point name
+*/
+
+void debug_sync(THD *thd, const char *sync_point_name, size_t name_len)
+{
+ 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);
+ DBUG_PRINT("debug_sync_point", ("hit: '%s'", sync_point_name));
+
+ /* Statistics. */
+ ds_control->dsp_hits++;
+
+ if (ds_control->ds_active &&
+ (action= debug_sync_find(ds_control->ds_action, ds_control->ds_active,
+ sync_point_name, name_len)) &&
+ action->activation_count)
+ {
+ /* Sync point is active (action exists). */
+ debug_sync_execute(thd, action);
+
+ /* Statistics. */
+ ds_control->dsp_executed++;
+
+ /* If action became inactive, remove it to shrink the search array. */
+ if (!action->activation_count)
+ debug_sync_remove_action(ds_control, action);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+#endif /* defined(ENABLED_DEBUG_SYNC) */
diff --git a/sql/debug_sync.h b/sql/debug_sync.h
new file mode 100644
index 00000000000..f4cd0b364cf
--- /dev/null
+++ b/sql/debug_sync.h
@@ -0,0 +1,60 @@
+#ifndef DEBUG_SYNC_INCLUDED
+#define DEBUG_SYNC_INCLUDED
+
+/* Copyright (C) 2008 Sun Microsystems, Inc.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/**
+ @file
+
+ Declarations for the Debug Sync Facility. See debug_sync.cc for details.
+*/
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include <my_global.h>
+
+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 uint opt_debug_sync_timeout;
+
+/* Default WAIT_FOR timeout if command line option is given without argument. */
+#define DEBUG_SYNC_DEFAULT_WAIT_TIMEOUT 300
+
+/* Debug Sync prototypes. See debug_sync.cc. */
+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);
+
+#else /* defined(ENABLED_DEBUG_SYNC) */
+
+#define DEBUG_SYNC(_thd_, _sync_point_name_) /* disabled DEBUG_SYNC */
+
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+
+#endif /* DEBUG_SYNC_INCLUDED */
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index 3433b54cb29..896be4a7f19 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -26,6 +26,7 @@
#include "mysqld_suffix.h"
#include "mysys_err.h"
#include "events.h"
+#include "debug_sync.h"
#include "../storage/myisam/ha_myisam.h"
@@ -486,6 +487,9 @@ my_bool lower_case_file_system= 0;
my_bool opt_large_pages= 0;
my_bool opt_myisam_use_mmap= 0;
uint opt_large_page_size= 0;
+#if defined(ENABLED_DEBUG_SYNC)
+uint opt_debug_sync_timeout= 0;
+#endif /* defined(ENABLED_DEBUG_SYNC) */
my_bool opt_old_style_user_limits= 0, trust_function_creators= 0;
/*
True if there is at least one per-hour limit for some user, so we should
@@ -1333,6 +1337,10 @@ void clean_up(bool print_message)
#ifdef USE_REGEX
my_regex_end();
#endif
+#if defined(ENABLED_DEBUG_SYNC)
+ /* End the debug sync facility. See debug_sync.cc. */
+ debug_sync_end();
+#endif /* defined(ENABLED_DEBUG_SYNC) */
#if !defined(EMBEDDED_LIBRARY)
if (!opt_bootstrap)
@@ -3464,6 +3472,12 @@ static int init_common_variables(const char *conf_file_name, int argc,
sys_var_slow_log_path.value= my_strdup(s, MYF(0));
sys_var_slow_log_path.value_length= strlen(s);
+#if defined(ENABLED_DEBUG_SYNC)
+ /* Initialize the debug sync facility. See debug_sync.cc. */
+ if (debug_sync_init())
+ return 1; /* purecov: tested */
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+
#if (ENABLE_TEMP_POOL)
if (use_temp_pool && bitmap_init(&temp_pool,0,1024,1))
return 1;
@@ -4858,7 +4872,7 @@ void create_thread_to_handle_connection(THD *thd)
handle_one_connection,
(void*) thd)))
{
- /* purify: begin inspected */
+ /* purecov: begin inspected */
DBUG_PRINT("error",
("Can't create thread to handle request (error %d)",
error));
@@ -5671,6 +5685,9 @@ enum options_mysqld
OPT_SECURE_FILE_PRIV,
OPT_MIN_EXAMINED_ROW_LIMIT,
OPT_LOG_SLOW_SLAVE_STATEMENTS,
+#if defined(ENABLED_DEBUG_SYNC)
+ OPT_DEBUG_SYNC_TIMEOUT,
+#endif /* defined(ENABLED_DEBUG_SYNC) */
OPT_OLD_MODE,
OPT_SLAVE_EXEC_MODE,
OPT_GENERAL_LOG_FILE,
@@ -6442,6 +6459,14 @@ log and this option does nothing anymore.",
"Decision to use in heuristic recover process. Possible values are COMMIT or ROLLBACK.",
(uchar**) &opt_tc_heuristic_recover, (uchar**) &opt_tc_heuristic_recover,
0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
+#if defined(ENABLED_DEBUG_SYNC)
+ {"debug-sync-timeout", OPT_DEBUG_SYNC_TIMEOUT,
+ "Enable the debug sync facility "
+ "and optionally specify a default wait timeout in seconds. "
+ "A zero value keeps the facility disabled.",
+ (uchar**) &opt_debug_sync_timeout, 0,
+ 0, GET_UINT, OPT_ARG, 0, 0, UINT_MAX, 0, 0, 0},
+#endif /* defined(ENABLED_DEBUG_SYNC) */
{"temp-pool", OPT_TEMP_POOL,
#if (ENABLE_TEMP_POOL)
"Using this option will cause most temporary files created to use a small set of names, rather than a unique name for each new file.",
@@ -7626,6 +7651,9 @@ static int mysql_init_variables(void)
bzero((uchar*) &mysql_tmpdir_list, sizeof(mysql_tmpdir_list));
bzero((char *) &global_status_var, sizeof(global_status_var));
opt_large_pages= 0;
+#if defined(ENABLED_DEBUG_SYNC)
+ opt_debug_sync_timeout= 0;
+#endif /* defined(ENABLED_DEBUG_SYNC) */
key_map_full.set_all();
/* Character sets */
@@ -8360,6 +8388,22 @@ mysqld_get_one_option(int optid,
lower_case_table_names= argument ? atoi(argument) : 1;
lower_case_table_names_used= 1;
break;
+#if defined(ENABLED_DEBUG_SYNC)
+ case OPT_DEBUG_SYNC_TIMEOUT:
+ /*
+ Debug Sync Facility. See debug_sync.cc.
+ Default timeout for WAIT_FOR action.
+ Default value is zero (facility disabled).
+ If option is given without an argument, supply a non-zero value.
+ */
+ if (!argument)
+ {
+ /* purecov: begin tested */
+ opt_debug_sync_timeout= DEBUG_SYNC_DEFAULT_WAIT_TIMEOUT;
+ /* purecov: end */
+ }
+ break;
+#endif /* defined(ENABLED_DEBUG_SYNC) */
}
return 0;
}
diff --git a/sql/set_var.cc b/sql/set_var.cc
index cd01f6c8624..b80bdde9670 100644
--- a/sql/set_var.cc
+++ b/sql/set_var.cc
@@ -625,6 +625,12 @@ static sys_var_long_ptr sys_table_cache_size(&vars, "table_open_cache",
&table_cache_size);
static sys_var_long_ptr sys_table_lock_wait_timeout(&vars, "table_lock_wait_timeout",
&table_lock_wait_timeout);
+
+#if defined(ENABLED_DEBUG_SYNC)
+/* Debug Sync Facility. Implemented in debug_sync.cc. */
+static sys_var_debug_sync sys_debug_sync(&vars, "debug_sync");
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+
static sys_var_long_ptr sys_thread_cache_size(&vars, "thread_cache_size",
&thread_cache_size);
#if HAVE_POOL_OF_THREADS == 1
diff --git a/sql/set_var.h b/sql/set_var.h
index a54591b0fd6..fa747107870 100644
--- a/sql/set_var.h
+++ b/sql/set_var.h
@@ -637,6 +637,21 @@ public:
};
#endif /* DBUG_OFF */
+#if defined(ENABLED_DEBUG_SYNC)
+/* Debug Sync Facility. Implemented in debug_sync.cc. */
+class sys_var_debug_sync :public sys_var_thd
+{
+public:
+ sys_var_debug_sync(sys_var_chain *chain, const char *name_arg)
+ :sys_var_thd(name_arg)
+ { chain_sys_var(chain); }
+ bool check(THD *thd, set_var *var);
+ bool update(THD *thd, set_var *var);
+ SHOW_TYPE show_type() { return SHOW_CHAR; }
+ bool check_update_type(Item_result type) { return type != STRING_RESULT; }
+ uchar *value_ptr(THD *thd, enum_var_type type, LEX_STRING *base);
+};
+#endif /* defined(ENABLED_DEBUG_SYNC) */
/* some variables that require special handling */
diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt
index fdad2a44b68..a17ad94ba82 100644
--- a/sql/share/errmsg.txt
+++ b/sql/share/errmsg.txt
@@ -6206,3 +6206,10 @@ ER_TOO_MANY_CONCURRENT_TRXS
WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED
eng "Non-ASCII separator arguments are not fully supported"
+
+ER_DEBUG_SYNC_TIMEOUT
+ eng "debug sync point wait timed out"
+ ger "Debug Sync Point Wartezeit überschritten"
+ER_DEBUG_SYNC_HIT_LIMIT
+ eng "debug sync point hit limit reached"
+ ger "Debug Sync Point Hit Limit erreicht"
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index f55d3fc5006..af3ae03424e 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -17,6 +17,7 @@
/* Basic functions needed by many modules */
#include "mysql_priv.h"
+#include "debug_sync.h"
#include "sql_select.h"
#include "sp_head.h"
#include "sp.h"
@@ -950,6 +951,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
close_old_data_files(thd,thd->open_tables,1,1);
mysql_ha_flush(thd);
+ DEBUG_SYNC(thd, "after_flush_unlock");
bool found=1;
/* Wait until all threads has closed all the tables we had locked */
@@ -5289,6 +5291,8 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
}
}
+ DEBUG_SYNC(thd, "before_lock_tables_takes_lock");
+
if (! (thd->lock= mysql_lock_tables(thd, start, (uint) (ptr - start),
lock_flag, need_reopen)))
{
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index daef5a26742..f75dc2cb88a 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -42,6 +42,7 @@
#include "sp_rcontext.h"
#include "sp_cache.h"
+#include "debug_sync.h"
/*
The following is used to initialise Table_ident with a internal
@@ -584,6 +585,9 @@ THD::THD()
derived_tables_processing(FALSE),
spcont(NULL),
m_parser_state(NULL)
+#if defined(ENABLED_DEBUG_SYNC)
+ , debug_sync_control(0)
+#endif /* defined(ENABLED_DEBUG_SYNC) */
{
ulong tmp;
@@ -832,6 +836,11 @@ void THD::init(void)
reset_current_stmt_binlog_row_based();
bzero((char *) &status_var, sizeof(status_var));
sql_log_bin_toplevel= options & OPTION_BIN_LOG;
+
+#if defined(ENABLED_DEBUG_SYNC)
+ /* Initialize the Debug Sync Facility. See debug_sync.cc. */
+ debug_sync_init_thread(this);
+#endif /* defined(ENABLED_DEBUG_SYNC) */
}
@@ -911,6 +920,12 @@ void THD::cleanup(void)
lock=locked_tables; locked_tables=0;
close_thread_tables(this);
}
+
+#if defined(ENABLED_DEBUG_SYNC)
+ /* End the Debug Sync Facility. See debug_sync.cc. */
+ debug_sync_end_thread(this);
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+
mysql_ha_cleanup(this);
delete_dynamic(&user_var_events);
hash_free(&user_vars);
diff --git a/sql/sql_class.h b/sql/sql_class.h
index c38eb17f191..27b59bde7be 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -1896,6 +1896,11 @@ public:
partition_info *work_part_info;
#endif
+#if defined(ENABLED_DEBUG_SYNC)
+ /* Debug Sync facility. See debug_sync.cc. */
+ struct st_debug_sync_control *debug_sync_control;
+#endif /* defined(ENABLED_DEBUG_SYNC) */
+
THD();
~THD();
diff --git a/storage/myisam/myisamchk.c b/storage/myisam/myisamchk.c
index 270bcf7f6ce..611fb6325c8 100644
--- a/storage/myisam/myisamchk.c
+++ b/storage/myisam/myisamchk.c
@@ -837,7 +837,7 @@ static int myisamchk(MI_CHECK *param, char * filename)
mi_check_print_error(param,"'%s' is marked as crashed after last repair",filename);
break;
case HA_ERR_OLD_FILE:
- mi_check_print_error(param,"'%s' is a old type of MyISAM-table", filename);
+ mi_check_print_error(param,"'%s' is an old type of MyISAM-table", filename);
break;
case HA_ERR_END_OF_FILE:
mi_check_print_error(param,"Couldn't read complete header from '%s'", filename);